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__
cls = final(cls)
return cls
return classrebuilder
def setreadonlyattributes(cls, *readonlyattrs):
return readonlyattributes(*readonlyattrs)(cls)
if __name__ == '__main__':
@readonlyattributes('readonlyfield')
class ReadonlyFieldClass(object):
def __init__(self, a, b):
self.readonlyfield = a
self.publicfield = b
attr = None
def main():
global attr
pfi = ReadonlyFieldClass('forbidden', 'changable')
try:
print(pfi.publicfield)
print('__getattribute__ works')
pfi.publicfield = 'mutable'
print('__setattr__ seems to work')
print(pfi.publicfield)
print('__setattr__ definitely works')
del pfi.publicfield
print('__delattr__ seems to work')
print(pfi.publlicfield)
raise RuntimeError('__delattr__ doesn\'t work')
except(AttributeError):
print('__delattr__ works')
try:
print(pfi.readonlyfield)
print('__getattribute__ works')
pfi.readonlyfield = 'readonly'
raise RuntimeError('__setattr__ doesn\'t work')
except(AttributeError):
print('__setattr__ seems to work')
try:
print(pfi.readonlyfield)
print('__setattr__ works')
del pfi.readonlyfield
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.
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.