Nie wiem, kiedy atrybut powinien być prywatny i czy powinienem używać właściwości.
Czytałem ostatnio, że setery i gettery nie są pythonowe i powinienem użyć dekoratora właściwości. W porządku.
Ale co, jeśli mam atrybut, którego nie można ustawić spoza klasy, ale można go odczytać (atrybut tylko do odczytu). Czy ten atrybut powinien być prywatny, a przez prywatny mam na myśli podkreślenie, w ten sposób self._x
? Jeśli tak, to jak mogę to przeczytać bez użycia gettera? Jedyną metodą, jaką znam w tej chwili, jest pisanie
@property
def x(self):
return self._x
W ten sposób mogę odczytać atrybut według, obj.x
ale nie mogę go ustawić, obj.x = 1
więc jest w porządku.
Ale czy naprawdę powinno mnie obchodzić ustawienie obiektu, którego nie wolno ustawiać? Może powinienem to po prostu zostawić. Ale z drugiej strony nie mogę użyć podkreślenia, ponieważ czytanie obj._x
jest dziwne dla użytkownika, więc powinienem użyć, obj.x
a potem znowu użytkownik nie wie, że nie może ustawić tego atrybutu.
Jaka jest Twoja opinia i praktyka?
źródło
self.x
i ufaj, że nikt się nie zmienix
. Jeślix
ważne jest, aby upewnić się, że nie można tego zmienić, użyj właściwości._x
wcale nie jest dziwne: zgodnie z konwencją oznacza coś „prywatnego”.object
, aby faktycznie uniemożliwiło to ustawienieobj.x
. W klasach w starym stylu możesz nadal ustawićobj.x
, z całkiem nieoczekiwanymi wynikami.Odpowiedzi:
Ogólnie rzecz biorąc, programy w Pythonie powinny być pisane z założeniem, że wszyscy użytkownicy są pełnoletni, a zatem sami są odpowiedzialni za prawidłowe używanie rzeczy. Jednak w rzadkich przypadkach, w których ustawienie atrybutu po prostu nie ma sensu (na przykład wartość pochodna lub wartość odczytana z jakiegoś statycznego źródła danych), właściwość tylko pobierająca jest na ogół preferowanym wzorcem.
źródło
Tylko moje dwa centy, Silas Ray jest na dobrej drodze, ale chciałem dodać przykład. ;-)
Python jest językiem niebezpiecznym dla typów, dlatego zawsze będziesz musiał ufać użytkownikom twojego kodu, że będą używać kodu jak rozsądna (rozsądna) osoba.
Za PEP 8 :
Aby mieć właściwość „tylko do odczytu” w klasie, której możesz użyć
@property
dekoracji, musisz ją odziedziczyćobject
, aby skorzystać z klas w nowym stylu.Przykład:
>>> 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
źródło
self.__a = []
nadal możesz to zrobića.a.append('anything')
i zadziała.Oto sposób na uniknięcie takiego założenia
zobacz moją aktualizację poniżej
Używanie
@property
jest bardzo rozwlekłe, np .: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
Za pomocą
Poza ostatnim to konwencja. Nadal możesz, jeśli naprawdę się postarasz, uzyskać dostęp do zmiennych z podwójnym podkreśleniem.
Więc co robimy? Czy rezygnujemy z posiadania właściwości tylko do odczytu w Pythonie?
Ujrzeć!
read_only_properties
dekorator na ratunek!@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
Ty pytasz:
Cieszę się, że zapytałeś, oto źródło 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
aktualizacja
Nie spodziewałem się, że ta odpowiedź przyciągnie tyle uwagi. Zaskakujące jest, że tak. To zachęciło mnie do stworzenia pakietu, którego możesz użyć.
w twojej powłoce Pythona:
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
źródło
if..elif..else
blok może być tylkoif name in attrs and name in self.__dict__: raise Attr...
ze niepass
wymagane. Problem 1: wszystkie klasy tak udekorowane kończą się identycznym__name__
, a ciąg znaków reprezentujących ich typ jest również homogenizowany. Problem 2: ta dekoracja zastępuje dowolny zwyczaj__setattr__
. Problem 3: użytkownicy mogą to pokonać za pomocądel MyClass.__setattr__
.object.__setattr__(f, 'forbidden', 42)
. Nie rozumiem, coread_only_properties
dodaje, że nie jest to obsługiwane przez podwójne podkreślenie zniekształcenia nazwy.Oto nieco inne podejście do właściwości tylko do odczytu, które być może powinny nazywać się właściwościami jednorazowego zapisu, ponieważ muszą zostać zainicjalizowane, prawda? Dla paranoików, którzy martwią się możliwością modyfikowania właściwości poprzez bezpośredni dostęp do słownika obiektu, wprowadziłem „ekstremalne” zniekształcanie nazw:
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)
źródło
dict_name
zamiast tego zmienisz kod , npdict_name = "_spam_" + name
. Usunie to zależność oduuid4
i znacznie ułatwi debugowanie.p.__dict__['_spam_x'] = 5
żeby zmienić wartośćp.x
, więc to nie wystarczy do zmiany nazwy.Nie jestem zadowolony z poprzednich dwóch odpowiedzi dotyczących tworzenia właściwości tylko do odczytu, ponieważ pierwsze rozwiązanie umożliwia usunięcie atrybutu tylko do odczytu, a następnie ustawienie i nie blokuje __dict__. Drugie rozwiązanie można obejść, testując - znajdując wartość, która jest równa temu, co ustawisz, i ostatecznie ją zmieniając.
A teraz kod.
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()
Nie ma sensu tworzyć atrybutów tylko do odczytu, z wyjątkiem sytuacji, gdy piszesz kod biblioteki, kod, który jest dystrybuowany do innych jako kod do wykorzystania w celu ulepszenia ich programów, a nie kod do innych celów, takich jak tworzenie aplikacji. Problem __dict__ został rozwiązany, ponieważ __dict__ ma teraz niezmienne typy.MappingProxyType , więc atrybutów nie można zmienić za pomocą __dict__. Ustawianie lub usuwanie __dict__ jest również zablokowane. Jedynym sposobem zmiany właściwości tylko do odczytu jest zmiana metod samej klasy.
Chociaż uważam, że moje rozwiązanie jest lepsze niż poprzednie dwa, można je ulepszyć. Oto słabości tego kodu:
a) Nie pozwala na dodawanie do metody w podklasie, która ustawia lub usuwa atrybut tylko do odczytu. Metoda zdefiniowana w podklasie jest automatycznie blokowana przed dostępem do atrybutu tylko do odczytu, nawet przez wywołanie wersji metody nadklasy.
b) Metody klasy „tylko do odczytu” można zmienić, aby obejść ograniczenia tylko do odczytu.
Jednak nie ma możliwości ustawienia lub usunięcia atrybutu tylko do odczytu bez edycji klasy. Nie jest to zależne od konwencji nazewnictwa, co jest dobre, ponieważ Python nie jest tak spójny z konwencjami nazewnictwa. Zapewnia to sposób tworzenia atrybutów tylko do odczytu, których nie można zmienić za pomocą ukrytych luk bez edytowania samej klasy. Po prostu wymień atrybuty, które mają być odczytywane tylko podczas wywoływania dekoratora jako argumenty, a staną się one tylko do odczytu.
Podziękowania dla odpowiedzi Brice'a w Jak uzyskać nazwę klasy wywołującego wewnątrz funkcji innej klasy w Pythonie? do pobierania klas i metod wywołujących.
źródło
object.__setattr__(pfi, 'readonly', 'foobar')
psuje to rozwiązanie, bez edytowania samej klasy.Zauważ, że metody instancji są również atrybutami (klasy) i że możesz ustawić je na poziomie klasy lub instancji, jeśli naprawdę chcesz być złym człowiekiem. Albo, że możesz ustawić zmienną klasy (która jest również atrybutem klasy), gdzie przydatne właściwości tylko do odczytu nie będą działać porządnie po wyjęciu z pudełka. Chcę przez to powiedzieć, że problem „atrybutu tylko do odczytu” jest w rzeczywistości bardziej ogólny, niż zwykle się wydaje. Na szczęście w pracy istnieją konwencjonalne oczekiwania, które są tak silne, że oślepiają nas na te inne przypadki (w końcu prawie wszystko jest jakąś cechą w Pythonie).
Opierając się na tych oczekiwaniach, myślę, że najbardziej ogólnym i lekkim podejściem jest przyjęcie konwencji, że atrybuty „public” (bez wiodącego podkreślenia) są tylko do odczytu, chyba że są wyraźnie udokumentowane jako zapisywalne. To obejmuje zwykłe oczekiwanie, że metody nie zostaną poprawione, a zmienne klas wskazujące wartości domyślne instancji są lepsze, nie mówiąc już o tym. Jeśli czujesz się naprawdę paranoicznie z powodu jakiejś specjalnej cechy, użyj deskryptora tylko do odczytu jako ostatniej miary zasobów.
źródło
Chociaż podoba mi się dekorator klas z Oz123, możesz również wykonać następujące czynności, które używają jawnego opakowania klasy i __new__ z metodą klasy Factory zwracającą klasę w zamknięciu:
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()
źródło
To jest moje obejście.
@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
źródło
ktoś wspomniał o używaniu obiektu proxy, nie widziałem tego przykładu, więc ostatecznie go wypróbowałem, [słabo].
/! \ Jeśli to możliwe, preferuj definicje klas i konstruktory klas
ten kod jest efektywnie przepisywany
class.__new__
(konstruktor klas) z wyjątkiem gorszych pod każdym względem. Oszczędź sobie bólu i nie używaj tego schematu, jeśli możesz.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))
źródło
Wiem, że przywracam z martwych ten wątek, ale zastanawiałem się, jak sprawić, by nieruchomość była tylko do odczytu i po znalezieniu tego tematu nie byłem zadowolony z udostępnionych już rozwiązań.
Wracając do pierwszego pytania, jeśli zaczniesz od tego kodu:
@property def x(self): return self._x
A jeśli chcesz, aby X był tylko do odczytu, możesz po prostu dodać:
@x.setter def x(self, value): raise Exception("Member readonly")
Następnie, jeśli uruchomisz następujące:
print (x) # Will print whatever X value is x = 3 # Will raise exception "Member readonly"
źródło
AttributeError('can't set attribute')
)