Làm thế nào để gọi một thuộc tính của lớp cơ sở nếu thuộc tính này đang bị ghi đè trong lớp dẫn xuất?


87

Tôi đang thay đổi một số lớp của mình từ việc sử dụng rộng rãi các getters và setters sang sử dụng các thuộc tính pythonic hơn.

Nhưng bây giờ tôi bị mắc kẹt vì một số getters hoặc setters trước đây của tôi sẽ gọi phương thức tương ứng của lớp cơ sở và sau đó thực hiện một cái gì đó khác. Nhưng làm thế nào điều này có thể được thực hiện với các thuộc tính? Làm thế nào để gọi thuộc tính getter hoặc setter trong lớp cha?

Tất nhiên chỉ cần gọi thuộc tính chính nó sẽ cho phép đệ quy vô hạn.

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

Câu trả lời:


100

Bạn có thể nghĩ rằng bạn có thể gọi hàm lớp cơ sở được gọi bởi thuộc tính:

class FooBar(Foo):

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

Mặc dù tôi nghĩ đây là điều rõ ràng nhất để thử - nó không hoạt động vì bar là một thuộc tính, không phải là một thứ có thể gọi được.

Nhưng một thuộc tính chỉ là một đối tượng, với một phương thức getter để tìm thuộc tính tương ứng:

class FooBar(Foo):

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

Tại sao tôi nên gọi Foo.bar.fset(self, c)trong một setter kế thừa? Tại sao không Foo.bar.fset(c)- không có "cái tôi" - Tôi nghĩ rằng điều này được mặc nhiên thông qua?
nerdoc

2
Tôi nhận được một Lỗi Loại: 'sở hữu' đối tượng không phải là callable
hithwen

@nerdoc selflấy ngụ ý từ chuỗi ở Foo.bar.fsetđâu?
Tadhg McDonald-Jensen

Chỉ là một suy nghĩ - AFAIK bản thân luôn được mặc nhiên cho qua. Nếu bạn thực hiện một foo = Foo()\ foo.bar(c)thì không có tự được chuyển, nhưng bar () nhận nó từ Python. Tôi không phải là chuyên gia Python, ít nhiều cũng là người mới bắt đầu. Nó chỉ là một suy nghĩ.
nerdoc

selfchỉ được truyền ngầm khi phương thức được gọi trên một thể hiện của một lớp. Ví dụ, nếu tôi có một lớp học Avới một phương pháp b(self, arg), và tôi có thể tạo một thể hiện c = A(), kêu gọi sau đó c.b(arg)là tương đương vớiA.b(c, arg)
TallChuck

53

Siêu nên thực hiện thủ thuật:

return super().bar

Trong Python 2.x, bạn cần sử dụng cú pháp dài dòng hơn:

return super(FooBar, self).bar

1
Lỗi Loại: super () phải mất ít nhất 1 lập luận (0 nhất định)
Aaron Maenpaa

4
Tôi đoán (tốt, đánh giá theo liên kết: P), câu trả lời này có liên quan đến python3. Trong python3 super () có thể nhận 0 đối số, có.
shylent

6
super (). bar dường như hoạt động tốt cho getter, nhưng không hoạt động để gán thông qua thuộc tính cơ sở trong một setter bị ghi đè. . Nếu tôi làm siêu () bar = 3 tôi get AttributeError: 'siêu' đối tượng không có thuộc tính 'bar'
Rob Smallshire

1
Tốt điểm Rob, không biết điều đó. Đây là thông tin thêm: stackoverflow.com/questions/10810369/…
Pankrat

2
Trong khi điều này hoạt động để nhận, nó không thành công khi thiết lập với AttributeError.
Ethan Furman

24

Có một giải pháp thay thế sử dụng superkhông yêu cầu tham chiếu rõ ràng đến tên lớp cơ sở.

Lớp cơ sở 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

Trong B, truy cập vào thuộc tính getter của lớp cha A:

Như những người khác đã trả lời, đó là:

super(B, self).prop

Hoặc trong Python 3:

super().prop

Điều này trả về giá trị được trả về bởi getter của thuộc tính, không phải bản thân getter nhưng nó đủ để mở rộng getter.

Trong B, truy cập bộ thiết lập thuộc tính của lớp cha A:

Khuyến nghị tốt nhất mà tôi đã thấy cho đến nay là:

A.prop.fset(self, value)

Tôi tin rằng cái này tốt hơn:

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

Trong ví dụ này, cả hai tùy chọn đều tương đương nhưng sử dụng super có ưu điểm là độc lập với các lớp cơ sở của B. Nếu Bđược kế thừa từ một Clớp cũng mở rộng thuộc tính, bạn sẽ không phải cập nhật Bmã của.

Mã đầy đủ của B mở rộng thuộc tính của 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)

Một caveat:

Trừ khi thuộc tính của bạn không có setter, bạn phải xác định cả setter và getter Bngay cả khi bạn chỉ thay đổi hành vi của một trong số chúng.


Xin chào, điều này rất hữu ích. Tôi có một câu hỏi tiếp theo cho điều này. Thiết lập này hoạt động tốt; tuy nhiên, nó ngừng hoạt động khi tôi đặt init cho B để xác định các thuộc tính bổ sung. Có cách nào để có một init riêng biệt cho B không? Cảm ơn bạn
Sang

Bạn có thể vui lòng giải thích cách super(B, self.__class__)hoạt động chính xác với super(class, class)? Nó được ghi lại ở đâu?
nghệ

Tôi đã đề xuất một số chỉnh sửa nhỏ cho câu trả lời này trong câu trả lời của mình
Eric

4

thử

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

Mặc dù tôi không chắc liệu python có hỗ trợ gọi thuộc tính lớp cơ sở hay không. Thuộc tính thực sự là một đối tượng có thể gọi được, được thiết lập với hàm được chỉ định và sau đó thay thế tên đó trong lớp. Điều này có thể dễ dàng có nghĩa là không có siêu chức năng nào có sẵn.

Tuy nhiên, bạn luôn có thể chuyển đổi cú pháp của mình để sử dụng hàm 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

Điều này hoạt động tốt nếu bạn viết lớp cơ sở. Nhưng điều gì sẽ xảy ra nếu bạn mở rộng lớp cơ sở của bên thứ ba sử dụng cùng một tên cho thuộc tính và getter?
akaihola

1

Một số cải tiến nhỏ cho câu trả lời của Maxime :

  • Sử dụng __class__để tránh viết B. Lưu ý rằng đó self.__class__là kiểu thời gian chạy của self, nhưng __class__ không có self tên của định nghĩa lớp bao quanh. super()là cách viết tắt của super(__class__, self).
  • Sử dụng __set__thay vì fset. Cái sau là cụ thể cho propertys, nhưng cái trước áp dụng cho tất cả các đối tượng giống thuộc tính (bộ mô tả).
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)

0

Bạn có thể sử dụng mẫu sau:

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)

Ghi chú! Tất cả các phương thức thuộc tính phải được định nghĩa lại cùng nhau. Nếu không muốn xác định lại tất cả các phương pháp, hãy sử dụng mẫu sau để thay thế:

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()

(đó là trừ khi tôi thiếu điều gì đó trong lời giải thích của bạn)


1
bạn là: anh đang nói về các thuộc tính, phương pháp không đơn giản
akaihola
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.