Jak wywołać właściwość klasy bazowej, jeśli ta właściwość jest nadpisywana w klasie pochodnej?


87

Zmieniam niektóre z moich klas z szerokiego użycia metod pobierających i ustawiających na bardziej Pythonowe użycie właściwości.

Ale teraz utknąłem, ponieważ niektóre z moich poprzednich metod pobierających lub ustawiających wywoływałyby odpowiednią metodę klasy bazowej, a następnie wykonywałyby coś innego. Ale jak można to osiągnąć za pomocą właściwości? Jak wywołać metodę pobierającą lub ustawiającą właściwość w klasie nadrzędnej?

Oczywiście samo wywołanie atrybutu daje nieskończoną rekurencję.

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

Odpowiedzi:


100

Możesz pomyśleć, że możesz wywołać funkcję klasy bazowej, która jest wywoływana przez właściwość:

class FooBar(Foo):

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

Chociaż wydaje mi się, że jest to najbardziej oczywista rzecz, którą należy wypróbować - nie działa, ponieważ bar jest własnością, a nie wywoływaną.

Ale właściwość to tylko obiekt, z metodą pobierającą do znalezienia odpowiedniego atrybutu:

class FooBar(Foo):

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

Dlaczego mam wezwać Foo.bar.fset(self, c)setera odziedziczonego? Dlaczego nie Foo.bar.fset(c)- bez „ja” - myślałem, że to zostało w domyśle przekazane?
— nerdoc

2
Otrzymuję TypeError: obiekt „property” nie jest wywoływalny
— hithwen

@nerdoc gdzie selfget sugeruje z łańcucha Foo.bar.fset?
— Tadhg McDonald-Jensen

Tylko myśl - jaźń AFAIK jest zawsze pośrednio przekazywana. Jeśli zrobisz foo = Foo()\, foo.bar(c)nie zostanie przekazane self , ale bar () odbiera je z Pythona. Nie jestem ekspertem od Pythona, mniej więcej też początkującym. To tylko myśl.
— nerdoc

selfjest przekazywana niejawnie tylko wtedy, gdy metoda jest wywoływana na wystąpieniu klasy. Na przykład, jeśli mam klasę Az metodą b(self, arg)i tworzę instancję c = A(), wywołanie c.b(arg)jest równoważneA.b(c, arg)
— TallChuck

53

Super powinien załatwić sprawę:

return super().bar

W Pythonie 2.x musisz użyć bardziej szczegółowej składni:

return super(FooBar, self).bar

1
TypeError: super () przyjmuje co najmniej 1 argument (podano 0)
— Aaron Maenpaa,

4
Myślę (cóż, sądząc po linku: P), ta odpowiedź jest związana z pythonem3. W python3 super () może przyjmować zero argumentów, tak.
— shylent

6
Wygląda na to, że super (). bar działa dobrze dla metody pobierającej, ale nie działa w przypadku przypisywania przez właściwość base w przesłoniętej metodzie ustawiającej. Jeśli zrobię super (). Bar = 3, otrzymam AttributeError: 'super' obiekt nie ma atrybutu 'bar'
— Rob Smallshire

1
Słuszna uwaga Rob, nie wiedziałem o tym. Tutaj jest więcej informacji: stackoverflow.com/questions/10810369/…
— Pankrat

2
Chociaż działa to w przypadku pobierania, nie powiedzie się w przypadku ustawienia z rozszerzeniem AttributeError.
— Ethan Furman

24

Istnieje alternatywne użycie, superktóre nie wymaga jawnego odwoływania się do nazwy klasy bazowej.

Klasa podstawowa 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

W B, uzyskując dostęp do metody pobierającej właściwość klasy nadrzędnej A:

Jak już odpowiedzieli inni, jest to:

super(B, self).prop

Lub w Pythonie 3:

super().prop

Zwraca to wartość zwracaną przez metodę pobierającą właściwości, a nie sam pobierający, ale wystarczy rozszerzyć funkcję pobierającą.

W B, uzyskując dostęp do setera właściwości klasy nadrzędnej A:

Najlepsza rekomendacja, jaką do tej pory widziałem, jest następująca:

A.prop.fset(self, value)

Uważam, że ten jest lepszy:

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

W tym przykładzie obie opcje są równoważne, ale użycie super ma tę zaletę, że jest niezależne od klas bazowych B. Gdyby Bdziedziczyć z Cklasy, która również rozszerza właściwość, nie musiałbyś aktualizować Bkodu.

Pełny kod B rozszerzający właściwość 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)

Jedno zastrzeżenie:

Jeśli twoja właściwość nie ma metody ustawiającej, musisz zdefiniować zarówno metodę ustawiającą, jak i pobierającą, Bnawet jeśli zmieniasz zachowanie tylko jednego z nich.


Witam, to jest bardzo pomocne. Mam dodatkowe pytanie. Ta konfiguracja działa dobrze; jednak przestaje działać, gdy ustawię init dla B, aby zdefiniować dodatkowe właściwości. Czy jest sposób, aby mieć oddzielny init dla B? Dziękuję
— zaśpiewano

Czy mógłbyś wyjaśnić, jak super(B, self.__class__)dokładnie działa z super(class, class)? Gdzie to jest udokumentowane?
— Art

Zasugerowałem kilka drobnych poprawek w tej odpowiedzi w mojej odpowiedzi
— Eric

4

próbować

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

Chociaż nie jestem pewien, czy Python obsługuje wywoływanie właściwości klasy bazowej. Właściwość jest w rzeczywistości wywoływalnym obiektem, który jest konfigurowany za pomocą określonej funkcji, a następnie zastępuje tę nazwę w klasie. Może to łatwo oznaczać, że nie ma dostępnej super funkcji.

Zawsze możesz jednak zmienić składnię, aby użyć funkcji 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

Działa to dobrze, jeśli napiszesz klasę bazową. Ale co, jeśli rozszerzysz klasę bazową innej firmy, która używa tej samej nazwy dla właściwości i metody pobierającej?
— akaihola

1

Kilka drobnych ulepszeń w odpowiedzi Maxime :

  • Używanie, __class__aby uniknąć pisania B. Zauważ, że self.__class__jest to typ środowiska wykonawczego self, ale __class__ bez self jest nazwą otaczającej definicji klasy. super()jest skrótem dla super(__class__, self).
  • Używanie __set__zamiast fset. Ta ostatnia jest specyficzna dla propertys, ale pierwsza dotyczy wszystkich obiektów podobnych do własności (deskryptorów).
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

Możesz użyć następującego szablonu:

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)

Uwaga! Wszystkie metody właściwości muszą zostać ponownie zdefiniowane razem. Jeśli nie chcesz przedefiniowywać wszystkich metod, użyj zamiast tego następującego szablonu:

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

(to znaczy chyba, że ​​czegoś mi brakuje w twoim wyjaśnieniu)


1
jesteś: on mówi o właściwościach, a nie o zwykłych metodach
— akaihola
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.