Subclassing: Apakah mungkin untuk menimpa properti dengan atribut konvensional?

14

Mari kita asumsikan kita ingin membuat keluarga kelas yang implementasi atau spesialisasi yang berbeda dari konsep menyeluruh. Mari kita asumsikan ada implementasi default yang masuk akal untuk beberapa properti turunan. Kami ingin memasukkan ini ke kelas dasar

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

Jadi subclass akan secara otomatis dapat menghitung elemen-elemennya dalam contoh yang agak konyol ini

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

Concrete_Math_Set(1,2,3).size
# 3

Tetapi bagaimana jika subkelas tidak ingin menggunakan default ini? Ini tidak bekerja:

import math

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

Square_Integers_Below(7)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute

Saya menyadari ada cara untuk mengganti properti dengan properti, tetapi saya ingin menghindari itu. Karena tujuan dari kelas dasar adalah untuk membuat hidup semudah mungkin bagi penggunanya, bukan untuk menambahkan mengasapi dengan memaksakan (dari sudut pandang sempit subclass) metode akses yang berbelit-belit dan berlebihan.

Bisakah itu dilakukan? Jika tidak, apa solusi terbaik berikutnya?

Paul Panzer
sumber

Jawaban:

6

Properti adalah deskriptor data yang diutamakan di atas atribut instance dengan nama yang sama. Anda bisa mendefinisikan deskriptor non-data dengan __get__()metode unik : atribut instance didahulukan dari deskriptor non-data dengan nama yang sama, lihat dokumen . Masalahnya di sini adalah bahwa yang non_data_propertydidefinisikan di bawah ini hanya untuk tujuan perhitungan (Anda tidak dapat mendefinisikan setter atau deleter) tetapi tampaknya menjadi kasus dalam contoh Anda.

import math

class non_data_property:
    def __init__(self, fget):
        self.__doc__ = fget.__doc__
        self.fget = fget

    def __get__(self, obj, cls):
        if obj is None:
            return self
        return self.fget(obj)

class Math_Set_Base:
    @non_data_property
    def size(self, *elements):
        return len(self.elements)

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1, 2, 3).size) # 3
print(Square_Integers_Below(1).size) # 1
print(Square_Integers_Below(4).size) # 2
print(Square_Integers_Below(9).size) # 3

Namun ini mengasumsikan bahwa Anda memiliki akses ke kelas dasar untuk melakukan perubahan ini.

Arkelis
sumber
Ini cukup dekat dengan sempurna. Jadi ini berarti poperty bahkan jika Anda hanya mendefinisikan pengambil tidak secara implisit menambahkan setter yang tidak melakukan apa pun selain memblokir tugas? Menarik. Saya mungkin akan menerima jawaban ini.
Paul Panzer
Ya, menurut dokumen, properti selalu mendefinisikan ketiga metode deskriptor ( __get__(), __set__()dan __delete__()) dan mereka meningkatkan AttributeErrorjika Anda tidak menyediakan fungsi apa pun untuk mereka. Lihat Python yang setara dengan implementasi properti .
Arkelis
1
Selama Anda tidak peduli dengan setteratau __set__properti, ini akan berhasil. Namun saya akan mengingatkan Anda bahwa ini juga berarti Anda dapat dengan mudah menimpa properti tidak hanya di level kelas ( self.size = ...) tetapi bahkan di tingkat instance (misalnya Concrete_Math_Set(1, 2, 3).size = 10akan sama-sama valid). Makanan untuk
dipikirkan
Dan Square_Integers_Below(9).size = 1juga valid karena merupakan atribut sederhana. Dalam kasus penggunaan "ukuran" khusus ini, ini mungkin tampak akward (gunakan properti sebagai gantinya), tetapi dalam kasus umum "menimpa prop yang dihitung dengan mudah dalam subkelas", mungkin dalam beberapa kasus baik. Anda juga dapat mengontrol akses atribut dengan __setattr__()tetapi itu bisa sangat besar.
Arkelis
1
Saya tidak setuju mungkin ada use case yang valid untuk ini, saya hanya ingin menyebutkan peringatan karena dapat mempersulit upaya debugging di masa depan. Selama Anda dan OP menyadari implikasinya maka semuanya baik-baik saja.
mulai
10

Ini akan menjadi jawaban panjang lebar yang mungkin hanya berfungsi sebagai pelengkap ... tapi pertanyaan Anda membawa saya untuk turun ke lubang kelinci jadi saya ingin berbagi temuan saya (dan rasa sakit) juga.

Anda mungkin pada akhirnya menemukan jawaban ini tidak membantu masalah Anda yang sebenarnya. Bahkan, kesimpulan saya adalah - saya tidak akan melakukan ini sama sekali. Karena itu, latar belakang kesimpulan ini mungkin sedikit menghibur Anda, karena Anda mencari detail lebih lanjut.


Mengatasi beberapa kesalahpahaman

Jawaban pertama, meskipun benar dalam banyak kasus, tidak selalu demikian. Sebagai contoh, pertimbangkan kelas ini:

class Foo:
    def __init__(self):
        self.name = 'Foo!'
        @property
        def inst_prop():
            return f'Retrieving {self.name}'
        self.inst_prop = inst_prop

inst_prop, sementara menjadi property, adalah atribut instance yang tidak dapat dibatalkan:

>>> Foo.inst_prop
Traceback (most recent call last):
  File "<pyshell#60>", line 1, in <module>
    Foo.inst_prop
AttributeError: type object 'Foo' has no attribute 'inst_prop'
>>> Foo().inst_prop
<property object at 0x032B93F0>
>>> Foo().inst_prop.fget()
'Retrieving Foo!'

Itu semua tergantung di mana Anda propertydidefinisikan di tempat pertama. Jika Anda @propertydidefinisikan dalam "lingkup" kelas (atau benar-benar, namespace), itu menjadi atribut kelas. Dalam contoh saya, kelas itu sendiri tidak mengetahui adanya inst_propsampai instantiated. Tentu saja, ini sama sekali tidak berguna sebagai properti di sini.


Tapi pertama-tama, mari alamat komentar Anda pada resolusi warisan ...

Jadi bagaimana tepatnya faktor pewarisan dalam masalah ini? Artikel berikut ini sedikit menyimpang ke dalam topik, dan Metode Resolusi Urutan agak terkait, meskipun sebagian besar membahas Luasnya warisan daripada Kedalaman.

Dikombinasikan dengan temuan kami, dengan pengaturan di bawah ini:

@property
def some_prop(self):
    return "Family property"

class Grandparent:
    culture = some_prop
    world_view = some_prop

class Parent(Grandparent):
    world_view = "Parent's new world_view"

class Child(Parent):
    def __init__(self):
        try:
            self.world_view = "Child's new world_view"
            self.culture = "Child's new culture"
        except AttributeError as exc:
            print(exc)
            self.__dict__['culture'] = "Child's desired new culture"

Bayangkan apa yang terjadi ketika baris ini dieksekusi:

print("Instantiating Child class...")
c = Child()
print(f'c.__dict__ is: {c.__dict__}')
print(f'Child.__dict__ is: {Child.__dict__}')
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

Hasilnya adalah sebagai berikut:

Instantiating Child class...
can't set attribute
c.__dict__ is: {'world_view': "Child's new world_view", 'culture': "Child's desired new culture"}
Child.__dict__ is: {'__module__': '__main__', '__init__': <function Child.__init__ at 0x0068ECD8>, '__doc__': None}
c.world_view is: Child's new world_view
Child.world_view is: Parent's new world_view
c.culture is: Family property
Child.culture is: <property object at 0x00694C00>

Perhatikan caranya:

  1. self.world_viewdapat diterapkan, sementara self.culturegagal
  2. culturetidak ada di Child.__dict__( mappingproxykelas, jangan dikelirukan dengan instance __dict__)
  3. Meskipun cultureada di c.__dict__, itu tidak dirujuk.

Anda mungkin dapat menebak mengapa - world_viewditimpa oleh Parentkelas sebagai non-properti, sehingga Childdapat menimpanya juga. Sementara itu, sejak culturediwariskan, hanya ada dalam mappingproxydariGrandparent :

Grandparent.__dict__ is: {
    '__module__': '__main__', 
    'culture': <property object at 0x00694C00>, 
    'world_view': <property object at 0x00694C00>, 
    ...
}

Bahkan jika Anda mencoba untuk menghapus Parent.culture:

>>> del Parent.culture
Traceback (most recent call last):
  File "<pyshell#67>", line 1, in <module>
    del Parent.culture
AttributeError: culture

Anda akan melihat itu bahkan tidak ada Parent. Karena objek secara langsung merujuk kembali ke Grandparent.culture.


Jadi, bagaimana dengan Resolution Order?

Jadi kami tertarik untuk mengamati Resolution Order yang sebenarnya, mari kita coba hapus Parent.world_view:

del Parent.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Bertanya-tanya apa hasilnya?

c.world_view is: Family property
Child.world_view is: <property object at 0x00694C00>

Itu dikembalikan ke kakek-nenek world_view property, meskipun kami telah berhasil menugaskan self.world_viewsebelumnya! Tetapi bagaimana jika kita dengan paksa berubah world_viewdi tingkat kelas, seperti jawaban yang lain? Bagaimana jika kita menghapusnya? Bagaimana jika kita menetapkan atribut kelas saat ini menjadi properti?

Child.world_view = "Child's independent world_view"
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

del c.world_view
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Child.world_view = property(lambda self: "Child's own property")
print(f'c.world_view is: {c.world_view}')
print(f'Child.world_view is: {Child.world_view}')

Hasilnya adalah:

# Creating Child's own world view
c.world_view is: Child's new world_view
Child.world_view is: Child's independent world_view

# Deleting Child instance's world view
c.world_view is: Child's independent world_view
Child.world_view is: Child's independent world_view

# Changing Child's world view to the property
c.world_view is: Child's own property
Child.world_view is: <property object at 0x020071B0>

Ini menarik karena c.world_viewdikembalikan ke atribut instansinya, sedangkan atribut Child.world_viewyang kami tetapkan. Setelah menghapus atribut instance, itu akan kembali ke atribut class. Dan setelah menugaskan kembali Child.world_viewke properti, kami langsung kehilangan akses ke atribut instance.

Oleh karena itu, kami dapat menduga urutan resolusi berikut :

  1. Jika atribut kelas ada dan itu adalah property, ambil nilainya melalui getteratau fget(lebih lanjut tentang ini nanti). Kelas saat ini pertama ke kelas Base terakhir.
  2. Jika tidak, jika ada atribut instance, ambil nilai atribut instance.
  3. Lain, ambil propertyatribut non- kelas. Kelas saat ini pertama ke kelas Base terakhir.

Dalam hal ini, mari kita hapus root property:

del Grandparent.culture
print(f'c.culture is: {c.culture}')
print(f'Child.culture is: {Child.culture}')

Pemberian yang mana:

c.culture is: Child's desired new culture
Traceback (most recent call last):
  File "<pyshell#74>", line 1, in <module>
    print(f'Child.culture is: {Child.culture}')
AttributeError: type object 'Child' has no attribute 'culture'

Ta-dah! Childsekarang memiliki sendiri cultureberdasarkan penyisipan paksa ke c.__dict__. Child.culturetidak ada, tentu saja, karena tidak pernah didefinisikan dalam Parentatau Childatribut kelas, dan Grandparentsudah dihapus.


Apakah ini penyebab utama masalah saya?

Sebenarnya tidak . Kesalahan yang Anda dapatkan, yang masih kami amati saat menetapkan self.culture, sangat berbeda . Tetapi urutan warisan mengatur latar belakang untuk jawaban - yang merupakan propertydirinya sendiri.

Selain gettermetode yang disebutkan sebelumnya , propertyjuga memiliki beberapa trik rapi di lengan bajunya. Yang paling relevan dalam kasus ini adalah setter, atau fsetmetode, yang dipicu oleh self.culture = ...garis. Karena Anda propertytidak mengimplementasikan setteratau fgetfungsi apa pun , python tidak tahu apa yang harus dilakukan, dan AttributeErrorsebaliknya melemparkan (yaitu can't set attribute).

Namun jika Anda menerapkan settermetode:

@property
def some_prop(self):
    return "Family property"

@some_prop.setter
def some_prop(self, val):
    print(f"property setter is called!")
    # do something else...

Saat membuat instance Childkelas Anda akan mendapatkan:

Instantiating Child class...
property setter is called!

Alih-alih menerima AttributeError, Anda sekarang benar-benar memanggil some_prop.settermetode. Yang memberi Anda lebih banyak kontrol atas objek Anda ... dengan temuan kami sebelumnya, kami tahu bahwa kami perlu memiliki atribut kelas yang ditimpa sebelum mencapai properti. Ini bisa diimplementasikan dalam kelas dasar sebagai pemicu. Ini contoh baru:

class Grandparent:
    @property
    def culture(self):
        return "Family property"

    # add a setter method
    @culture.setter
    def culture(self, val):
        print('Fine, have your own culture')
        # overwrite the child class attribute
        type(self).culture = None
        self.culture = val

class Parent(Grandparent):
    pass

class Child(Parent):
    def __init__(self):
        self.culture = "I'm a millennial!"

c = Child()
print(c.culture)

Yang mengakibatkan:

Fine, have your own culture
I'm a millennial!

TA-DAH! Sekarang Anda dapat menimpa atribut instance Anda sendiri di atas properti yang diwarisi!


Jadi, masalah terpecahkan?

... Tidak juga. Masalah dengan pendekatan ini adalah, sekarang Anda tidak dapat memiliki settermetode yang tepat . Ada kasus di mana Anda ingin menetapkan nilai pada property. Tapi sekarang setiap kali Anda mengaturnya self.culture = ...akan selalu menimpa fungsi apa pun yang Anda tetapkan dalam getter(yang dalam hal ini, benar-benar hanya bagian @propertyterbungkus. Anda dapat menambahkan dalam langkah-langkah yang lebih bernuansa, tetapi dengan satu atau lain cara itu akan selalu melibatkan lebih dari sekadar self.culture = .... misalnya:

class Grandparent:
    # ...
    @culture.setter
    def culture(self, val):
        if isinstance(val, tuple):
            if val[1]:
                print('Fine, have your own culture')
                type(self).culture = None
                self.culture = val[0]
        else:
            raise AttributeError("Oh no you don't")

# ...

class Child(Parent):
    def __init__(self):
        try:
            # Usual setter
            self.culture = "I'm a Gen X!"
        except AttributeError:
            # Trigger the overwrite condition
            self.culture = "I'm a Boomer!", True

Itu waaaaay lebih rumit dari jawaban yang lain, size = Nonedi tingkat kelas.

Anda juga dapat mempertimbangkan untuk menulis deskriptor Anda sendiri untuk menangani metode __get__dan __set__, atau tambahan. Tetapi pada akhirnya, ketika self.culturedireferensikan, yang __get__akan selalu dipicu pertama, dan ketika self.culture = ...direferensikan, __set__akan selalu dipicu terlebih dahulu. Tidak ada jalan keluar sejauh yang saya coba.


Inti masalahnya, IMO

Masalah yang saya lihat di sini adalah - Anda tidak dapat memiliki kue dan memakannya juga. propertydimaksudkan seperti deskriptor dengan akses mudah dari metode seperti getattratau setattr. Jika Anda juga ingin metode ini mencapai tujuan yang berbeda, Anda hanya meminta masalah. Saya mungkin akan memikirkan kembali pendekatan:

  1. Apakah saya benar-benar membutuhkan propertyini?
  2. Dapatkah metode melayani saya berbeda?
  3. Jika saya membutuhkan property, apakah ada alasan saya perlu menimpanya?
  4. Apakah subclass benar-benar termasuk dalam keluarga yang sama jika ini propertytidak berlaku?
  5. Jika saya perlu menimpa semua / semua property, akankah metode terpisah bermanfaat bagi saya lebih baik daripada hanya menugaskan kembali, karena penugasan kembali secara tidak sengaja dapat membatalkan propertys?

Untuk poin 5, pendekatan saya akan memiliki overwrite_prop()metode di kelas dasar yang menimpa atribut kelas saat ini sehingga propertytidak akan lagi dipicu:

class Grandparent:
    # ...
    def overwrite_props(self):
        # reassign class attributes
        type(self).size = None
        type(self).len = None
        # other properties, if necessary

# ...

# Usage
class Child(Parent):
    def __init__(self):
        self.overwrite_props()
        self.size = 5
        self.len = 10

Seperti yang Anda lihat, sementara masih sedikit dibuat-buat, itu setidaknya lebih eksplisit daripada yang samar size = None. Yang mengatakan, pada akhirnya, saya tidak akan menimpa properti sama sekali, dan akan mempertimbangkan kembali desain saya dari root.

Jika Anda telah sampai sejauh ini - terima kasih telah berjalan dalam perjalanan ini bersama saya. Itu adalah latihan kecil yang menyenangkan.

benteng
sumber
2
Wow, terima kasih banyak! Saya perlu sedikit waktu untuk mencerna ini, tetapi saya rasa saya memintanya.
Paul Panzer
6

A @propertydidefinisikan di tingkat kelas. Dokumentasi membahas secara mendalam tentang cara kerjanya, tetapi cukup untuk mengatakan bahwa pengaturan atau membuat properti memutuskan memanggil metode tertentu. Namun, propertyobjek yang mengelola proses ini didefinisikan dengan definisi kelas sendiri. Yaitu, itu didefinisikan sebagai variabel kelas tetapi berperilaku seperti variabel instan.

Salah satu konsekuensi dari ini adalah Anda dapat menugaskan kembali secara bebas di tingkat kelas :

print(Math_Set_Base.size)
# <property object at 0x10776d6d0>

Math_Set_Base.size = 4
print(Math_Set_Base.size)
# 4

Dan sama seperti nama level kelas lainnya (misalnya metode), Anda dapat menimpanya dalam subkelas dengan hanya mendefinisikannya secara eksplisit:

class Square_Integers_Below(Math_Set_Base):
    # explicitly define size at the class level to be literally anything other than a @property
    size = None

    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Square_Integers_Below(4).size)  # 2
print(Square_Integers_Below.size)     # None

Ketika kita membuat instance aktual, variabel instance hanya membayangi variabel kelas dengan nama yang sama. The propertyobjek biasanya menggunakan beberapa shenanigans untuk memanipulasi proses ini (yaitu menerapkan getter dan setter) tetapi ketika nama kelas tingkat tidak didefinisikan sebagai properti, tidak ada yang istimewa yang terjadi, dan sehingga bertindak seperti yang Anda harapkan dari variabel lain.

Guy Jubah Hijau
sumber
Terima kasih, itu sangat sederhana. Apakah itu berarti bahwa bahkan jika tidak pernah ada properti di mana pun di kelas saya dan leluhurnya masih akan terlebih dahulu mencari melalui seluruh pohon warisan untuk nama tingkat kelas sebelum bahkan mengganggu melihat __dict__? Juga, apakah ada cara untuk mengotomatisasi ini? Ya, itu hanya satu baris tapi itu jenis hal yang sangat samar untuk dibaca jika Anda tidak terbiasa dengan detail mengerikan dari proprties dll.
Paul Panzer
@ PaulPanzer Saya belum melihat prosedur di balik ini, jadi saya sendiri tidak bisa memberikan jawaban yang memuaskan. Anda dapat mencoba memecahkannya dari kode sumber cpython jika mau. Sedangkan untuk mengotomatisasi proses, saya tidak berpikir ada cara yang baik untuk melakukan itu selain tidak menjadikannya properti, atau hanya menambahkan komentar / docstring untuk membuat mereka yang membaca kode Anda tahu apa yang Anda lakukan . Sesuatu seperti# declare size to not be a @property
Green Cloak Guy
4

Anda tidak perlu penugasan sizesama sekali. sizeadalah properti di kelas dasar, sehingga Anda bisa menimpa properti itu di kelas anak:

class Math_Set_Base:
    @property
    def size(self):
        return len(self.elements)

    # size = property(lambda self: self.elements)


class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._cap = cap

    @property
    def size(self):
        return int(math.sqrt(self._cap))

    # size = property(lambda self: int(math.sqrt(self._cap)))

Anda dapat (mikro) mengoptimalkan ini dengan mengkompilasi akar kuadrat:

class Square_Integers_Below(Math_Set_Base):

    def __init__(self, cap):
        self._size = int(math.sqrt(self._cap))

    @property
    def size(self):
        return self._size
chepner
sumber
Itu poin yang bagus, timpa saja properti induk dengan properti anak! +1
r.ook
Ini adalah cara yang lurus untuk dilakukan, tetapi saya tertarik dan memang meminta cara-cara khusus untuk memungkinkan penggantian non-properti agar tidak memaksakan ketidaknyamanan karena harus menulis properti memoizing di mana tugas sederhana akan melakukan pekerjaan.
Paul Panzer
Saya tidak yakin mengapa Anda ingin menyulitkan kode Anda seperti itu. Untuk harga kecil yang mengakui bahwa itu sizeadalah properti dan bukan atribut instance, Anda tidak perlu melakukan sesuatu yang mewah.
chepner
Kasus penggunaan saya adalah banyak kelas anak dengan banyak atribut; tidak harus menulis 5 baris alih-alih satu untuk semua yang membenarkan sejumlah fancisitas, IMO.
Paul Panzer
2

Sepertinya Anda ingin mendefinisikan sizedi kelas Anda:

class Square_Integers_Below(Math_Set_Base):
    size = None

    def __init__(self, cap):
        self.size = int(math.sqrt(cap))

Pilihan lain adalah menyimpan capdi kelas Anda dan menghitungnya dengan sizedidefinisikan sebagai properti (yang menimpa properti kelas dasar size).

Uri
sumber
2

Saya sarankan menambahkan setter seperti:

class Math_Set_Base:
    @property
    def size(self):
        try:
            return self._size
        except:
            return len(self.elements)

    @size.setter
    def size(self, value):
        self._size = value

Dengan cara ini Anda dapat mengganti .sizeproperti default seperti:

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self,*elements):
        self.elements = elements

class Square_Integers_Below(Math_Set_Base):
    def __init__(self,cap):
        self.size = int(math.sqrt(cap))

print(Concrete_Math_Set(1,2,3).size) # 3
print(Square_Integers_Below(7).size) # 2
Amir
sumber
1

Anda juga dapat melakukan hal berikutnya

class Math_Set_Base:
    _size = None

    def _size_call(self):
       return len(self.elements)

    @property
    def size(self):
        return  self._size if self._size is not None else self._size_call()

class Concrete_Math_Set(Math_Set_Base):
    def __init__(self, *elements):
        self.elements = elements


class Square_Integers_Below(Math_Set_Base):
    def __init__(self, cap):
        self._size = int(math.sqrt(cap))
Ryabchenko Alexander
sumber
Itu rapi, tetapi Anda tidak benar-benar membutuhkan _sizeatau di _size_callsana. Anda bisa saja terpanggang di pemanggilan fungsi di dalam sizekondisi, dan gunakan try... except... untuk menguji _sizealih - alih mengambil referensi kelas tambahan yang tidak akan digunakan. Bagaimanapun, saya merasa ini bahkan lebih samar daripada size = Nonemenimpa sederhana di tempat pertama.
mulai
0

Saya pikir adalah mungkin untuk mengatur properti dari kelas dasar ke properti lain dari kelas turunan, di dalam kelas turunan dan kemudian menggunakan properti dari kelas dasar dengan nilai baru.

Dalam kode awal ada semacam konflik antara nama sizedari kelas dasar dan atribut self.sizedari kelas turunan. Ini bisa terlihat jika kita mengganti nama self.sizedari kelas turunan dengan self.length. Ini akan menampilkan:

3
<__main__.Square_Integers_Below object at 0x000001BCD56B6080>

Kemudian, jika kita mengganti nama metode sizedengan lengthdalam semua kejadian di seluruh program, ini akan menghasilkan pengecualian yang sama:

Traceback (most recent call last):
  File "C:/Users/Maria/Downloads/so_1.2.py", line 24, in <module>
    Square_Integers_Below(7)
  File "C:/Users/Maria/Downloads/so_1.2.py", line 21, in __init__
    self.length = int(math.sqrt(cap))
AttributeError: can't set attribute

Kode tetap, atau bagaimanapun juga, versi yang entah bagaimana berfungsi, adalah untuk menjaga kode tetap sama, dengan pengecualian kelas Square_Integers_Below, yang akan mengatur metode sizedari kelas dasar, ke nilai lain.

class Square_Integers_Below(Math_Set_Base):

    def __init__(self,cap):
        #Math_Set_Base.__init__(self)
        self.length = int(math.sqrt(cap))
        Math_Set_Base.size = self.length

    def __repr__(self):
        return str(self.size)

Dan kemudian, ketika kita menjalankan semua program, hasilnya adalah:

3
2

Saya berharap ini membantu dalam satu atau lain cara.

Sofia190
sumber