Saya tidak tahu kapan atribut harus bersifat pribadi dan apakah saya harus menggunakan properti.
Saya baru-baru ini membaca bahwa setter dan getter bukan pythonic dan saya harus menggunakan dekorator properti. Tidak apa-apa.
Tetapi bagaimana jika saya memiliki atribut, yang tidak boleh disetel dari luar kelas tetapi dapat dibaca (atribut read-only). Haruskah atribut ini bersifat pribadi, dan yang saya maksud dengan garis bawah, seperti itu pribadi self._x
? Jika ya, bagaimana saya bisa membacanya tanpa menggunakan getter? Satu-satunya metode yang saya ketahui saat ini adalah menulis
@property
def x(self):
return self._x
Dengan cara itu saya bisa membaca atribut dengan obj.x
tetapi saya tidak bisa mengaturnya obj.x = 1
jadi tidak apa-apa.
Tetapi haruskah saya benar-benar peduli tentang pengaturan objek yang tidak boleh disetel? Mungkin sebaiknya aku tinggalkan saja. Tetapi sekali lagi saya tidak dapat menggunakan garis bawah karena membaca obj._x
itu aneh bagi pengguna, jadi saya harus menggunakan obj.x
dan sekali lagi pengguna tidak tahu bahwa dia tidak boleh menyetel atribut ini.
Apa pendapat dan praktik Anda?
sumber
self.x
dan percaya bahwa tidak ada yang akan berubahx
. Jika memastikan bahwax
tidak dapat diubah itu penting, gunakan properti._x
tidak aneh sama sekali: menurut konvensi , itu berarti sesuatu yang "pribadi".object
, agar ini benar-benar menghentikan pengaturan Andaobj.x
. Pada kelas gaya lama Anda sebenarnya masih dapat mengaturobj.x
, dengan hasil yang cukup tidak terduga.Jawaban:
Umumnya, program Python harus ditulis dengan asumsi bahwa semua pengguna menyetujui orang dewasa, dan karenanya bertanggung jawab untuk menggunakan berbagai hal dengan benar sendiri. Namun, dalam kasus yang jarang terjadi di mana tidak masuk akal jika atribut dapat diatur (seperti nilai turunan, atau nilai yang dibaca dari beberapa sumber data statis), properti khusus pengambil umumnya merupakan pola yang disukai.
sumber
Hanya dua sen saya, Silas Ray berada di jalur yang benar, namun saya ingin menambahkan contoh. ;-)
Python adalah jenis bahasa yang tidak aman dan oleh karena itu Anda harus selalu mempercayai pengguna kode Anda untuk menggunakan kode seperti orang yang masuk akal (masuk akal).
Per PEP 8 :
Untuk memiliki properti 'hanya-baca' di kelas Anda dapat menggunakan
@property
dekorasi, Anda harus mewarisi dariobject
saat Anda melakukannya untuk menggunakan kelas gaya baru.Contoh:
>>> class A(object): ... def __init__(self, a): ... self._a = a ... ... @property ... def a(self): ... return self._a ... >>> a = A('test') >>> a.a 'test' >>> a.a = 'pleh' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute
sumber
self.__a = []
, Anda masih bisa melakukan inia.a.append('anything')
dan itu akan berhasil.Berikut adalah cara untuk menghindari anggapan itu
silakan lihat pembaruan saya di bawah ini
Menggunakan
@property
, sangat bertele-tele misalnya:class AClassWithManyAttributes: '''refactored to properties''' def __init__(a, b, c, d, e ...) self._a = a self._b = b self._c = c self.d = d self.e = e @property def a(self): return self._a @property def b(self): return self._b @property def c(self): return self._c # you get this ... it's long
Menggunakan
Kecuali yang terakhir, ini konvensi. Anda masih dapat, jika benar-benar berusaha keras, mengakses variabel dengan garis bawah ganda.
Jadi apa yang kita lakukan? Apakah kita menyerah untuk memiliki properti hanya baca di Python?
Melihat!
read_only_properties
dekorator untuk menyelamatkan!@read_only_properties('readonly', 'forbidden') class MyClass(object): def __init__(self, a, b, c): self.readonly = a self.forbidden = b self.ok = c m = MyClass(1, 2, 3) m.ok = 4 # we can re-assign a value to m.ok # read only access to m.readonly is OK print(m.ok, m.readonly) print("This worked...") # this will explode, and raise AttributeError m.forbidden = 4
Anda bertanya:
Senang Anda bertanya, berikut adalah sumber read_only_properties :
def read_only_properties(*attrs): def class_rebuilder(cls): "The class decorator" class NewClass(cls): "This is the overwritten class" def __setattr__(self, name, value): if name not in attrs: pass elif name not in self.__dict__: pass else: raise AttributeError("Can't modify {}".format(name)) super().__setattr__(name, value) return NewClass return class_rebuilder
memperbarui
Saya tidak pernah menyangka jawaban ini akan mendapat banyak perhatian. Anehnya memang begitu. Ini mendorong saya untuk membuat paket yang dapat Anda gunakan.
di shell python Anda:
In [1]: from rop import read_only_properties In [2]: @read_only_properties('a') ...: class Foo: ...: def __init__(self, a, b): ...: self.a = a ...: self.b = b ...: In [3]: f=Foo('explodes', 'ok-to-overwrite') In [4]: f.b = 5 In [5]: f.a = 'boom' --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-5-a5226072b3b4> in <module>() ----> 1 f.a = 'boom' /home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value) 116 pass 117 else: --> 118 raise AttributeError("Can't touch {}".format(name)) 119 120 super().__setattr__(name, value) AttributeError: Can't touch a
sumber
if..elif..else
blok bisa sajaif name in attrs and name in self.__dict__: raise Attr...
tanpapass
diperlukan. Masalah 1: kelas-kelas yang didekorasi dengan demikian semuanya berakhir dengan sebuah identik__name__
, dan representasi string dari tipenya juga dihomogenisasi. Masalah 2: dekorasi ini menimpa kebiasaan apa pun__setattr__
. Masalah 3: Pengguna dapat mengalahkan ini dengandel MyClass.__setattr__
.object.__setattr__(f, 'forbidden', 42)
. Saya tidak melihatread_only_properties
penambahan apa yang tidak ditangani oleh nama garis bawah ganda mangling.Berikut adalah pendekatan yang sedikit berbeda untuk properti hanya-baca, yang mungkin harus disebut properti sekali tulis karena harus diinisialisasi, bukan? Untuk paranoid di antara kita yang khawatir bisa mengubah properti dengan mengakses kamus objek secara langsung, saya telah memperkenalkan mangling nama "ekstrim":
from uuid import uuid4 class Read_Only_Property: def __init__(self, name): self.name = name self.dict_name = uuid4().hex self.initialized = False def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.dict_name] def __set__(self, instance, value): if self.initialized: raise AttributeError("Attempt to modify read-only property '%s'." % self.name) instance.__dict__[self.dict_name] = value self.initialized = True class Point: x = Read_Only_Property('x') y = Read_Only_Property('y') def __init__(self, x, y): self.x = x self.y = y if __name__ == '__main__': try: p = Point(2, 3) print(p.x, p.y) p.x = 9 except Exception as e: print(e)
sumber
dict_name
Sebaliknya, jika Anda mengacaukan , misalnyadict_name = "_spam_" + name
menghapus ketergantungan padauuid4
dan membuat debugging menjadi jauh lebih mudah.p.__dict__['_spam_x'] = 5
untuk mengubah nilaip.x
, jadi ini tidak memberikan nama mangling yang cukup.Saya tidak puas dengan dua jawaban sebelumnya untuk membuat properti hanya baca karena solusi pertama memungkinkan atribut readonly dihapus dan kemudian disetel dan tidak memblokir __dict__. Solusi kedua dapat diselesaikan dengan pengujian - menemukan nilai yang sama dengan yang Anda tetapkan dua dan mengubahnya pada akhirnya.
Sekarang, untuk kodenya.
def final(cls): clss = cls @classmethod def __init_subclass__(cls, **kwargs): raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__)) cls.__init_subclass__ = __init_subclass__ return cls def methoddefiner(cls, method_name): for clss in cls.mro(): try: getattr(clss, method_name) return clss except(AttributeError): pass return None def readonlyattributes(*attrs): """Method to create readonly attributes in a class Use as a decorator for a class. This function takes in unlimited string arguments for names of readonly attributes and returns a function to make the readonly attributes readonly. The original class's __getattribute__, __setattr__, and __delattr__ methods are redefined so avoid defining those methods in the decorated class You may create setters and deleters for readonly attributes, however if they are overwritten by the subclass, they lose access to the readonly attributes. Any method which sets or deletes a readonly attribute within the class loses access if overwritten by the subclass besides the __new__ or __init__ constructors. This decorator doesn't support subclassing of these classes """ def classrebuilder(cls): def __getattribute__(self, name): if name == '__dict__': from types import MappingProxyType return MappingProxyType(super(cls, self).__getattribute__('__dict__')) return super(cls, self).__getattribute__(name) def __setattr__(self, name, value): if name == '__dict__' or name in attrs: import inspect stack = inspect.stack() try: the_class = stack[1][0].f_locals['self'].__class__ except(KeyError): the_class = None the_method = stack[1][0].f_code.co_name if the_class != cls: if methoddefiner(type(self), the_method) != cls: raise AttributeError("Cannot set readonly attribute '{}'".format(name)) return super(cls, self).__setattr__(name, value) def __delattr__(self, name): if name == '__dict__' or name in attrs: import inspect stack = inspect.stack() try: the_class = stack[1][0].f_locals['self'].__class__ except(KeyError): the_class = None the_method = stack[1][0].f_code.co_name if the_class != cls: if methoddefiner(type(self), the_method) != cls: raise AttributeError("Cannot delete readonly attribute '{}'".format(name)) return super(cls, self).__delattr__(name) clss = cls cls.__getattribute__ = __getattribute__ cls.__setattr__ = __setattr__ cls.__delattr__ = __delattr__ #This line will be moved when this algorithm will be compatible with inheritance cls = final(cls) return cls return classrebuilder def setreadonlyattributes(cls, *readonlyattrs): return readonlyattributes(*readonlyattrs)(cls) if __name__ == '__main__': #test readonlyattributes only as an indpendent module @readonlyattributes('readonlyfield') class ReadonlyFieldClass(object): def __init__(self, a, b): #Prevent initalization of the internal, unmodified PrivateFieldClass #External PrivateFieldClass can be initalized self.readonlyfield = a self.publicfield = b attr = None def main(): global attr pfi = ReadonlyFieldClass('forbidden', 'changable') ###---test publicfield, ensure its mutable---### try: #get publicfield print(pfi.publicfield) print('__getattribute__ works') #set publicfield pfi.publicfield = 'mutable' print('__setattr__ seems to work') #get previously set publicfield print(pfi.publicfield) print('__setattr__ definitely works') #delete publicfield del pfi.publicfield print('__delattr__ seems to work') #get publicfield which was supposed to be deleted therefore should raise AttributeError print(pfi.publlicfield) #publicfield wasn't deleted, raise RuntimeError raise RuntimeError('__delattr__ doesn\'t work') except(AttributeError): print('__delattr__ works') try: ###---test readonly, make sure its readonly---### #get readonlyfield print(pfi.readonlyfield) print('__getattribute__ works') #set readonlyfield, should raise AttributeError pfi.readonlyfield = 'readonly' #apparently readonlyfield was set, notify user raise RuntimeError('__setattr__ doesn\'t work') except(AttributeError): print('__setattr__ seems to work') try: #ensure readonlyfield wasn't set print(pfi.readonlyfield) print('__setattr__ works') #delete readonlyfield del pfi.readonlyfield #readonlyfield was deleted, raise RuntimeError raise RuntimeError('__delattr__ doesn\'t work') except(AttributeError): print('__delattr__ works') try: print("Dict testing") print(pfi.__dict__, type(pfi.__dict__)) attr = pfi.readonlyfield print(attr) print("__getattribute__ works") if pfi.readonlyfield != 'forbidden': print(pfi.readonlyfield) raise RuntimeError("__getattr__ doesn't work") try: pfi.__dict__ = {} raise RuntimeError("__setattr__ doesn't work") except(AttributeError): print("__setattr__ works") del pfi.__dict__ raise RuntimeError("__delattr__ doesn't work") except(AttributeError): print(pfi.__dict__) print("__delattr__ works") print("Basic things work") main()
Tidak ada gunanya membuat atribut hanya baca kecuali ketika Anda menulis kode perpustakaan, kode yang didistribusikan ke orang lain sebagai kode untuk digunakan untuk meningkatkan program mereka, bukan kode untuk tujuan lain, seperti pengembangan aplikasi. Masalah __dict__ terpecahkan, karena __dict__ sekarang dalam tipe yang tidak dapat diubah.MappingProxyType , jadi atribut tidak dapat diubah melalui __dict__. Menyetel atau menghapus __dict__ juga diblokir. Satu-satunya cara untuk mengubah properti hanya baca adalah dengan mengubah metode kelas itu sendiri.
Meskipun saya yakin solusi saya lebih baik daripada dua solusi sebelumnya, namun dapat ditingkatkan. Ini adalah kelemahan kode ini:
a) Tidak mengizinkan penambahan ke metode dalam subclass yang menyetel atau menghapus atribut hanya baca. Sebuah metode yang didefinisikan dalam subclass secara otomatis dilarang mengakses atribut readonly, bahkan dengan memanggil versi metode superclass '.
b) Metode hanya-baca kelas dapat diubah untuk mengalahkan batasan hanya-baca.
Namun, tidak ada cara tanpa mengedit kelas untuk menyetel atau menghapus atribut hanya baca. Ini tidak tergantung pada konvensi penamaan, yang bagus karena Python tidak begitu konsisten dengan konvensi penamaan. Ini menyediakan cara untuk membuat atribut hanya baca yang tidak dapat diubah dengan celah tersembunyi tanpa mengedit kelas itu sendiri. Cukup daftarkan atribut yang akan dibaca hanya saat memanggil dekorator sebagai argumen dan atribut tersebut akan menjadi hanya baca.
Penghargaan untuk jawaban Brice di Bagaimana cara mendapatkan nama kelas pemanggil di dalam fungsi kelas lain dengan python? untuk mendapatkan kelas dan metode pemanggil.
sumber
object.__setattr__(pfi, 'readonly', 'foobar')
memecahkan solusi ini, tanpa mengedit kelas itu sendiri.Perhatikan bahwa metode instance juga merupakan atribut (dari kelas) dan Anda dapat mengaturnya di kelas atau tingkat instance jika Anda benar-benar ingin menjadi seorang badass. Atau Anda dapat menyetel variabel kelas (yang juga merupakan atribut kelas), di mana properti hanya baca yang praktis tidak akan berfungsi dengan baik di luar kotak. Apa yang ingin saya katakan adalah bahwa masalah "atribut hanya baca" sebenarnya lebih umum daripada biasanya. Untungnya ada ekspektasi konvensional di tempat kerja yang begitu kuat untuk membutakan kita terhadap kasus-kasus lain ini (lagipula, hampir semuanya adalah atribut dalam python).
Berdasarkan ekspektasi ini, menurut saya pendekatan yang paling umum dan ringan adalah dengan mengadopsi konvensi bahwa atribut "publik" (tanpa garis bawah) hanya dapat dibaca kecuali jika didokumentasikan secara eksplisit sebagai dapat ditulisi. Ini mencakup ekspektasi biasa bahwa metode tidak akan ditambal dan variabel kelas yang menunjukkan default instance lebih baik, apalagi. Jika Anda merasa sangat paranoid tentang beberapa atribut khusus, gunakan deskriptor hanya-baca sebagai ukuran sumber daya terakhir.
sumber
Meskipun saya menyukai dekorator kelas dari Oz123, Anda juga dapat melakukan hal berikut, yang menggunakan pembungkus kelas eksplisit dan __new__ dengan metode Pabrik kelas yang mengembalikan kelas dalam closure:
class B(object): def __new__(cls, val): return cls.factory(val) @classmethod def factory(cls, val): private = {'var': 'test'} class InnerB(object): def __init__(self): self.variable = val pass @property def var(self): return private['var'] return InnerB()
sumber
Itu solusi saya.
@property def language(self): return self._language @language.setter def language(self, value): # WORKAROUND to get a "getter-only" behavior # set the value only if the attribute does not exist try: if self.language == value: pass print("WARNING: Cannot set attribute \'language\'.") except AttributeError: self._language = value
sumber
seseorang menyebutkan menggunakan objek proxy, saya tidak melihat contohnya jadi saya akhirnya mencobanya, [buruk].
/! \ Silakan pilih definisi kelas dan konstruktor kelas jika memungkinkan
kode ini secara efektif menulis ulang
class.__new__
(konstruktor kelas) kecuali lebih buruk dalam segala hal. Selamatkan diri Anda dari rasa sakit dan jangan gunakan pola ini jika Anda bisa.def attr_proxy(obj): """ Use dynamic class definition to bind obj and proxy_attrs. If you can extend the target class constructor that is cleaner, but its not always trivial to do so. """ proxy_attrs = dict() class MyObjAttrProxy(): def __getattr__(self, name): if name in proxy_attrs: return proxy_attrs[name] # overloaded return getattr(obj, name) # proxy def __setattr__(self, name, value): """ note, self is not bound when overloading methods """ proxy_attrs[name] = value return MyObjAttrProxy() myobj = attr_proxy(Object()) setattr(myobj, 'foo_str', 'foo') def func_bind_obj_as_self(func, self): def _method(*args, **kwargs): return func(self, *args, **kwargs) return _method def mymethod(self, foo_ct): """ self is not bound because we aren't using object __new__ you can write the __setattr__ method to bind a self argument, or declare your functions dynamically to bind in a static object reference. """ return self.foo_str + foo_ct setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))
sumber
Saya tahu saya membawa kembali utas ini dari kematian, tetapi saya sedang mencari cara membuat properti hanya baca dan setelah menemukan topik ini, saya tidak puas dengan solusi yang sudah dibagikan.
Jadi, kembali ke pertanyaan awal, jika Anda memulai dengan kode ini:
@property def x(self): return self._x
Dan Anda ingin membuat X hanya-baca, Anda cukup menambahkan:
@x.setter def x(self, value): raise Exception("Member readonly")
Kemudian, jika Anda menjalankan yang berikut ini:
print (x) # Will print whatever X value is x = 3 # Will raise exception "Member readonly"
sumber
AttributeError('can't set attribute')
)