จะเรียกคุณสมบัติของคลาสฐานได้อย่างไรหากคุณสมบัตินี้ถูกเขียนทับในคลาสที่ได้รับ


89

ฉันกำลังเปลี่ยนคลาสของฉันจากการใช้ getters และ setters อย่างกว้างขวางเป็นการใช้คุณสมบัติ pythonic มากขึ้น

แต่ตอนนี้ฉันติดขัดเพราะ getters หรือ setters ก่อนหน้าของฉันบางคนจะเรียกเมธอดที่สอดคล้องกันของคลาสพื้นฐานแล้วดำเนินการอย่างอื่น แต่จะสำเร็จด้วยสรรพคุณได้อย่างไร? จะเรียกคุณสมบัติ getter หรือ setter ในคลาสพาเรนต์ได้อย่างไร?

แน่นอนว่าการเรียกใช้แอตทริบิวต์นั้นจะทำให้เกิดการเรียกซ้ำไม่สิ้นสุด

class Foo(object):

    @property
    def bar(self):
        return 5

    @bar.setter
    def bar(self, a):
        print a

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return self.bar # --> recursion!

    @bar.setter
    def bar(self, c):
        # perform the same action
        # as in the base class
        self.bar = c    # --> recursion!
        # then do something else
        print 'something else'

fb = FooBar()
fb.bar = 7

คำตอบ:


104

คุณอาจคิดว่าคุณสามารถเรียกใช้ฟังก์ชันคลาสพื้นฐานซึ่งเรียกโดยคุณสมบัติ:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar(self)

แม้ว่านี่จะเป็นสิ่งที่ชัดเจนที่สุดในการลองฉันคิดว่ามันไม่ได้ผลเพราะบาร์เป็นคุณสมบัติไม่ใช่สิ่งที่เรียกได้

แต่คุณสมบัติเป็นเพียงวัตถุโดยมีเมธอด getter เพื่อค้นหาแอตทริบิวต์ที่เกี่ยวข้อง:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)

เหตุใดฉันจึงควรเรียกFoo.bar.fset(self, c)ตัวเซ็ตเตอร์ที่สืบทอดมา ทำไมไม่Foo.bar.fset(c)- โดยไม่มี "ตัวตน" - ฉันคิดว่าสิ่งนี้จะผ่านไปโดยปริยาย?
nerdoc

2
ฉันได้รับ TypeError: วัตถุ 'คุณสมบัติ' ไม่สามารถ
เรียกใช้ได้

@nerdoc selfได้รับความหมายจากโซ่ตรงFoo.bar.fsetไหน?
Tadhg McDonald-Jensen

เพียงแค่ความคิด - ตัวตนของ AFAIK มักจะผ่านไปโดยปริยายเสมอ หากคุณทำfoo = Foo()\ foo.bar(c)ไม่มีการส่งผ่านตัวเองแต่bar ()รับจาก Python ฉันไม่ใช่ผู้เชี่ยวชาญ Python แต่ก็เป็นมือใหม่ไม่มากก็น้อยเช่นกัน มันเป็นเพียงความคิด
nerdoc

selfจะถูกส่งผ่านโดยปริยายเมื่อเมธอดถูกเรียกใช้ในอินสแตนซ์ของคลาสเท่านั้น ตัวอย่างเช่นถ้าฉันมีคลาสที่Aมีเมธอดb(self, arg)และฉันสร้างอินสแตนซ์การc = A()โทรc.b(arg)จะเทียบเท่ากับA.b(c, arg)
TallChuck

56

Superควรทำเคล็ดลับ:

return super().bar

ใน Python 2.x คุณต้องใช้ไวยากรณ์ verbose เพิ่มเติม:

return super(FooBar, self).bar

1
TypeError: super () ใช้เวลาอย่างน้อย 1 อาร์กิวเมนต์ (ให้ 0)
Aaron Maenpaa

4
ฉันเดาว่า (ตัดสินโดยลิงค์: P) คำตอบนี้เกี่ยวข้องกับ python3 ใน python3 super () สามารถรับอาร์กิวเมนต์เป็นศูนย์ได้ใช่
shylent

6
แถบ super () ดูเหมือนจะทำงานได้ดีสำหรับ getter แต่ไม่ทำงานสำหรับการมอบหมายผ่านคุณสมบัติพื้นฐานในตัวตั้งค่าที่ถูกแทนที่ ถ้าฉันทำ super (). bar = 3 ฉันได้รับ AttributeError: วัตถุ 'super' ไม่มีแอตทริบิวต์ 'bar'
Rob Smallshire

1
จุดดีร็อบไม่รู้เรื่องนั้น ข้อมูลเพิ่มเติมstackoverflow.com/questions/10810369/…
ปานรัตน์

2
แม้ว่าสิ่งนี้จะใช้ได้ผล แต่ก็ล้มเหลวในการตั้งค่าด้วยAttributeErrorไฟล์.
Ethan Furman

27

มีทางเลือกในการใช้superที่ไม่จำเป็นต้องอ้างอิงชื่อคลาสพื้นฐานอย่างชัดเจน

คลาสพื้นฐาน A:

class A(object):
    def __init__(self):
        self._prop = None

    @property
    def prop(self):
        return self._prop

    @prop.setter
    def prop(self, value):
        self._prop = value

class B(A):
    # we want to extend prop here
    pass

ใน B การเข้าถึง getter คุณสมบัติของคลาสแม่ A:

ตามที่คนอื่นตอบไปแล้วนั่นคือ:

super(B, self).prop

หรือใน Python 3:

super().prop

สิ่งนี้ส่งคืนค่าที่ส่งคืนโดย getter ของคุณสมบัติไม่ใช่ getter เอง แต่ก็เพียงพอที่จะขยาย getter

ใน B การเข้าถึงตัวตั้งค่าคุณสมบัติของคลาสแม่ A:

คำแนะนำที่ดีที่สุดที่ฉันเคยเห็นมีดังต่อไปนี้:

A.prop.fset(self, value)

ฉันเชื่อว่าอันนี้ดีกว่า:

super(B, self.__class__).prop.fset(self, value)

ในตัวอย่างนี้ทั้งสองทางเลือกเทียบเท่า Bแต่ใช้ซุปเปอร์มีความได้เปรียบของการเป็นอิสระจากการเรียนฐานของ ถ้าBจะสืบทอดจากCคลาสยังขยายคุณสมบัติคุณจะไม่ต้องอัปเดตBรหัสของ

รหัสเต็มของ B ขยายคุณสมบัติของ A:

class B(A):
    @property
    def prop(self):
        value = super(B, self).prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(B, self.__class__).prop.fset(self, value)

ข้อแม้ประการหนึ่ง:

เว้นแต่คุณสมบัติของคุณจะไม่มี setter คุณต้องกำหนดทั้ง setter และ getter Bแม้ว่าคุณจะเปลี่ยนพฤติกรรมของหนึ่งในนั้นก็ตาม


สวัสดีนี่เป็นประโยชน์มาก ฉันมีคำถามติดตามเรื่องนี้ การตั้งค่านี้ทำงานได้ดี อย่างไรก็ตามมันหยุดทำงานเมื่อฉันตั้งค่าinitสำหรับ B เพื่อกำหนดคุณสมบัติเพิ่มเติม มีวิธีที่จะมีการเริ่มต้นแยกต่างหากสำหรับ B หรือไม่? ขอบคุณ
แสง

คุณช่วยอธิบายวิธีการsuper(B, self.__class__)ทำงานของไฟล์super(class, class)หรือไม่? เอกสารอยู่ที่ไหน?
ศิลปะ

ฉันได้แนะนำการปรับแต่งเล็กน้อยสำหรับคำตอบนี้ในคำตอบของฉัน
Eric

4

ลอง

@property
def bar:
    return super(FooBar, self).bar

แม้ว่าฉันไม่แน่ใจว่า python รองรับการเรียกคุณสมบัติคลาสพื้นฐานหรือไม่ คุณสมบัติเป็นอ็อบเจ็กต์ที่เรียกได้ซึ่งตั้งค่าด้วยฟังก์ชันที่ระบุแล้วแทนที่ชื่อนั้นในคลาส นี่อาจหมายความว่าไม่มีฟังก์ชันพิเศษที่พร้อมใช้งาน

คุณสามารถเปลี่ยนไวยากรณ์ของคุณเพื่อใช้ฟังก์ชัน property () ได้ตลอดเวลาแม้ว่า:

class Foo(object):

    def _getbar(self):
        return 5

    def _setbar(self, a):
        print a

    bar = property(_getbar, _setbar)

class FooBar(Foo):

    def _getbar(self):
        # return the same value
        # as in the base class
        return super(FooBar, self)._getbar()

    def bar(self, c):
        super(FooBar, self)._setbar(c)
        print "Something else"

    bar = property(_getbar, _setbar)

fb = FooBar()
fb.bar = 7

สิ่งนี้ใช้ได้ดีถ้าคุณเขียนคลาสพื้นฐาน แต่ถ้าคุณขยายคลาสพื้นฐานของบุคคลที่สามที่ใช้ชื่อเดียวกันสำหรับคุณสมบัติและ getter?
akaihola

2

การปรับปรุงเล็กน้อยสำหรับคำตอบของ Maxime :

  • การใช้ __class__Bเพื่อหลีกเลี่ยงการเขียน โปรดสังเกตว่าself.__class__เป็นชนิดรันไทม์ของselfแต่__class__ ไม่มี selfคือชื่อของนิยามคลาสที่ปิดล้อม เป็นชวเลขsuper()super(__class__, self)
  • ใช้__set__แทนfset. ตัวหลังมีความเฉพาะเจาะจงสำหรับpropertys แต่ก่อนหน้านี้ใช้กับอ็อบเจ็กต์ที่มีลักษณะคล้ายคุณสมบัติทั้งหมด (ตัวอธิบาย)
class B(A):
    @property
    def prop(self):
        value = super().prop
        # do something with / modify value here
        return value

    @prop.setter
    def prop(self, value):
        # do something with / modify value here
        super(__class__, self.__class__).prop.__set__(self, value)

1

คุณสามารถใช้เทมเพลตต่อไปนี้:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1
  
class Child(Parent):

    #getter
    @property
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

บันทึก! วิธีคุณสมบัติทั้งหมดจะต้องถูกกำหนดใหม่ร่วมกัน หากไม่ต้องการกำหนดวิธีการทั้งหมดใหม่ให้ใช้เทมเพลตต่อไปนี้แทน:

class Parent():
    def __init__(self, value):
        self.__prop1 = value

    #getter
    @property
    def prop1(self):
        return self.__prop1

    #setter
    @prop1.setter
    def prop1(self, value):
        self.__prop1 = value

    #deleter
    @prop1.deleter
    def prop1(self):
        del self.__prop1


class Child(Parent):

    #getter
    @Parent.prop1.getter
    def prop1(self):
        return super(Child, Child).prop1.__get__(self)

    #setter
    @Parent.prop1.setter
    def prop1(self, value):
        super(Child, Child).prop1.__set__(self, value)

    #deleter
    @Parent.prop1.deleter
    def prop1(self):
        super(Child, Child).prop1.__delete__(self)

-4
    class Base(object):
      def method(self):
        print "Base method was called"

    class Derived(Base):
      def method(self):
        super(Derived,self).method()
        print "Derived method was called"

    d = Derived()
    d.method()

(นั่นคือนอกจากฉันจะพลาดบางอย่างจากคำอธิบายของคุณ)


1
คุณคือเขากำลังพูดถึงคุณสมบัติไม่ใช่วิธีธรรมดา
akaihola
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.