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
UncleZeiv
źródło

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)
David Cournapeau
źródło
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
Pankrat
źródło
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.

Maxime R.
źródło
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
workmad3
źródło
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)
Eric
źródło
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)
Nader Aryabarzan
źródło
-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)

shylent
źródło
1
jesteś: on mówi o właściwościach, a nie o zwykłych metodach
akaihola