Bagaimana memanggil properti kelas dasar jika properti ini ditimpa di kelas turunan?

88

Saya mengubah beberapa kelas saya dari penggunaan ekstensif getter dan setter ke penggunaan properti yang lebih pythonic.

Tapi sekarang saya macet karena beberapa getter atau setter saya sebelumnya akan memanggil metode yang sesuai dari kelas dasar, dan kemudian melakukan sesuatu yang lain. Tapi bagaimana ini bisa dilakukan dengan properti? Bagaimana memanggil pengambil atau penyetel properti di kelas induk?

Tentu saja memanggil atribut itu sendiri memberikan rekursi tak terbatas.

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
sumber

Jawaban:

103

Anda mungkin berpikir Anda bisa memanggil fungsi kelas dasar yang dipanggil oleh properti:

class FooBar(Foo):

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

Meskipun ini adalah hal yang paling jelas untuk dicoba, saya pikir - ini tidak berhasil karena bar adalah properti, bukan yang dapat dipanggil.

Tetapi properti hanyalah sebuah objek, dengan metode pengambil untuk menemukan atribut yang sesuai:

class FooBar(Foo):

    @property
    def bar(self):
        # return the same value
        # as in the base class
        return Foo.bar.fget(self)
David Cournapeau
sumber
Mengapa saya harus memanggil Foo.bar.fset(self, c)penyetel yang diwariskan? Mengapa tidak Foo.bar.fset(c)- tanpa "diri" - Saya pikir ini secara implisit telah berlalu?
nerdoc
2
Saya mendapatkan TypeError: objek 'properti' tidak dapat dipanggil
hithwen
@nerdoc di mana selfget menyiratkan dari rantai Foo.bar.fset?
Tadhg McDonald-Jensen
Sekadar pikiran - AFAIK diri selalu secara implisit dilewati. Jika Anda melakukan foo = Foo()\ foo.bar(c)tidak ada yang lulus sendiri , tetapi bar () menerimanya dari Python. Saya bukan ahli Python, kurang lebih pemula juga. Itu hanya sebuah pikiran.
nerdoc
selfhanya diteruskan secara implisit saat metode dipanggil pada instance kelas. Misalnya, jika saya memiliki kelas Adengan metode b(self, arg), dan saya membuat instance c = A(), maka panggilannya c.b(arg)sama denganA.b(c, arg)
TallChuck
55

Super harus melakukan trik:

return super().bar

Di Python 2.x Anda perlu menggunakan sintaks yang lebih verbose:

return super(FooBar, self).bar
Pankrat
sumber
1
TypeError: super () membutuhkan setidaknya 1 argumen (0 diberikan)
Aaron Maenpaa
4
Saya kira (yah, dilihat dari tautannya: P), jawaban ini terkait dengan python3. Dalam python3 super () dapat mengambil nol argumen, ya.
shylent
6
super (). bar tampaknya berfungsi dengan baik untuk pengambil, tetapi tidak berfungsi untuk tugas melalui properti dasar dalam penyetel yang diganti. Jika saya melakukan super (). Bar = 3 Saya mendapatkan AttributeError: 'super' object tidak memiliki atribut 'bar'
Rob Smallshire
1
Poin bagus Rob, tidak tahu itu. Berikut informasi lebih lanjut: stackoverflow.com/questions/10810369/…
Pankrat
2
Meskipun ini berfungsi untuk mendapatkan, itu gagal untuk pengaturan dengan AttributeError.
Ethan Furman
27

Ada penggunaan alternatif superyang tidak perlu secara eksplisit mereferensikan nama kelas dasar.

Kelas dasar 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

Di B, mengakses pengambil properti dari kelas induk A:

Seperti yang telah dijawab orang lain, ini:

super(B, self).prop

Atau dengan Python 3:

super().prop

Ini mengembalikan nilai yang dikembalikan oleh pengambil properti, bukan pengambil itu sendiri, tetapi itu cukup untuk memperluas pengambil.

Di B, mengakses penyetel properti dari kelas induk A:

Rekomendasi terbaik yang pernah saya lihat sejauh ini adalah sebagai berikut:

A.prop.fset(self, value)

Saya yakin yang ini lebih baik:

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

Dalam contoh ini, kedua opsi setara tetapi menggunakan super memiliki keuntungan karena tidak bergantung pada kelas dasar B. Jika Bmewarisi dari Ckelas juga memperluas properti, Anda tidak perlu memperbarui Bkode.

Kode lengkap B yang memperluas properti 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)

Satu peringatan:

Kecuali jika properti Anda tidak memiliki penyetel, Anda harus menentukan penyetel dan pengambil Bmeskipun Anda hanya mengubah perilaku salah satunya.

Maxime R.
sumber
Halo, ini sangat membantu. Saya punya pertanyaan lanjutan untuk ini. Setup ini bekerja dengan baik; bagaimanapun, ini berhenti bekerja ketika saya menyetel init untuk B untuk mendefinisikan properti tambahan. Apakah ada cara untuk memiliki init terpisah untuk B? Terima kasih
bernyanyi
Bisakah Anda menjelaskan bagaimana super(B, self.__class__)tepatnya bekerja dengan super(class, class)? Dimana didokumentasikan?
Seni
Saya menyarankan beberapa perubahan kecil untuk jawaban ini dalam jawaban saya
Eric
4

mencoba

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

Meskipun saya tidak yakin apakah python mendukung pemanggilan properti kelas dasar. Properti sebenarnya adalah objek yang dapat dipanggil yang disiapkan dengan fungsi yang ditentukan dan kemudian menggantikan nama tersebut di kelas. Ini bisa dengan mudah berarti bahwa tidak ada fungsi super yang tersedia.

Anda selalu dapat mengganti sintaks Anda untuk menggunakan fungsi 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
sumber
Ini berfungsi dengan baik jika Anda menulis kelas dasar. Tetapi bagaimana jika Anda memperluas kelas dasar pihak ketiga yang menggunakan nama yang sama untuk properti dan pengambil?
akaihola
2

Beberapa perbaikan kecil pada jawaban Maxime :

  • Menggunakan __class__untuk menghindari menulis B. Perhatikan bahwa self.__class__ini adalah jenis runtime self, tetapi __class__ tanpa self adalah nama definisi kelas yang melampirkan. super()adalah singkatan dari super(__class__, self).
  • Menggunakan, __set__bukan fset. Yang terakhir khusus untuk propertys, tetapi yang pertama berlaku untuk semua objek mirip properti (deskriptor).
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
sumber
1

Anda dapat menggunakan template berikut:

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)

Catatan! Semua metode properti harus didefinisikan ulang bersama. Jika tidak ingin mendefinisikan ulang semua metode, gunakan template berikut sebagai gantinya:

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

(kecuali saya melewatkan sesuatu dari penjelasan Anda)

shylent
sumber
1
Anda adalah: dia berbicara tentang properti, bukan metode biasa
akaihola