Ich bin mit den beiden vorherigen Antworten zum Erstellen schreibgeschützter Eigenschaften unzufrieden, da mit der ersten Lösung das schreibgeschützte Attribut gelöscht und dann festgelegt werden kann und das __dict__ nicht blockiert wird. Die zweite Lösung könnte mit Tests umgangen werden: Finden Sie den Wert, der dem entspricht, den Sie auf zwei festgelegt haben, und ändern Sie ihn schließlich.
Nun zum Code.
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()
Es macht keinen Sinn, schreibgeschützte Attribute zu erstellen, es sei denn, Sie schreiben Bibliothekscode, Code, der als Code zur Verbesserung ihrer Programme an andere verteilt wird, und keinen Code für andere Zwecke wie die App-Entwicklung. Das __dict__ -Problem ist gelöst, da das __dict__ jetzt vom unveränderlichen Typ ist. MappingProxyType ist. , sodass Attribute nicht über __dict__ geändert werden können. Das Festlegen oder Löschen von __dict__ ist ebenfalls blockiert. Die einzige Möglichkeit, schreibgeschützte Eigenschaften zu ändern, besteht darin, die Methoden der Klasse selbst zu ändern.
Obwohl ich glaube, dass meine Lösung besser ist als die beiden vorherigen, könnte sie verbessert werden. Dies sind die Schwächen dieses Codes:
a) Erlaubt nicht das Hinzufügen zu einer Methode in einer Unterklasse, die ein schreibgeschütztes Attribut setzt oder löscht. Eine in einer Unterklasse definierte Methode kann automatisch nicht auf ein schreibgeschütztes Attribut zugreifen, selbst wenn die Version der Methode der Oberklasse aufgerufen wird.
b) Die schreibgeschützten Methoden der Klasse können geändert werden, um die schreibgeschützten Einschränkungen aufzuheben.
Ohne die Bearbeitung der Klasse ist es jedoch nicht möglich, ein schreibgeschütztes Attribut festzulegen oder zu löschen. Dies hängt nicht von Namenskonventionen ab, was gut ist, da Python nicht so konsistent mit Namenskonventionen ist. Auf diese Weise können schreibgeschützte Attribute erstellt werden, die nicht mit ausgeblendeten Lücken geändert werden können, ohne die Klasse selbst zu bearbeiten. Listen Sie einfach die Attribute auf, die schreibgeschützt sein sollen, wenn Sie den Dekorateur als Argumente aufrufen, und sie werden schreibgeschützt.
Dank an Brices Antwort in Wie erhalte ich den Namen der Anruferklasse in einer Funktion einer anderen Klasse in Python? zum Abrufen der Aufruferklassen und -methoden.
self.x
und vertrauen Sie darauf, dass sich niemand ändern wirdx
. Wennx
es wichtig ist, sicherzustellen, dass dies nicht geändert werden kann, verwenden Sie eine Eigenschaft.