Je ne suis pas satisfait des deux réponses précédentes pour créer des propriétés en lecture seule car la première solution permet à l'attribut readonly d'être supprimé puis défini et ne bloque pas le __dict__. La deuxième solution pourrait être contournée avec des tests - trouver la valeur qui équivaut à ce que vous avez défini deux et la modifier éventuellement.
Maintenant, pour le 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()
Il ne sert à rien de créer des attributs en lecture seule, sauf lorsque vous écrivez du code de bibliothèque, du code qui est distribué à d'autres en tant que code à utiliser afin d'améliorer leurs programmes, et non du code à d'autres fins, comme le développement d'applications. Le problème __dict__ est résolu, car le __dict__ est maintenant des types immuables.MappingProxyType , les attributs ne peuvent donc pas être modifiés via __dict__. La définition ou la suppression de __dict__ est également bloquée. La seule façon de modifier les propriétés en lecture seule consiste à modifier les méthodes de la classe elle-même.
Bien que je pense que ma solution est meilleure que les deux précédentes, elle pourrait être améliorée. Voici les faiblesses de ce code:
a) Ne permet pas l'ajout à une méthode dans une sous-classe qui définit ou supprime un attribut en lecture seule. Une méthode définie dans une sous-classe est automatiquement interdite d'accéder à un attribut en lecture seule, même en appelant la version de la superclasse de la méthode.
b) Les méthodes en lecture seule de la classe peuvent être modifiées pour contourner les restrictions en lecture seule.
Cependant, il n'y a pas moyen sans modifier la classe pour définir ou supprimer un attribut en lecture seule. Cela ne dépend pas des conventions de dénomination, ce qui est bien car Python n'est pas si cohérent avec les conventions de dénomination. Cela fournit un moyen de créer des attributs en lecture seule qui ne peuvent pas être modifiés avec des failles cachées sans modifier la classe elle-même. Répertoriez simplement les attributs à lire uniquement lors de l'appel du décorateur en tant qu'arguments et ils deviendront en lecture seule.
Crédit à la réponse de Brice dans Comment obtenir le nom de la classe de l'appelant dans une fonction d'une autre classe en python? pour obtenir les classes et méthodes de l'appelant.
self.x
et ayez confiance que personne ne changerax
. S'ilx
est important de s'assurer que cela ne peut pas être changé, utilisez une propriété.