منع إنشاء سمات جديدة خارج __init__
-
25-09-2019 - |
سؤال
أريد أن أكون قادرًا على إنشاء فئة (في بيثون) التي تم تهيئتها مرة واحدة __init__
, ، لا يقبل سمات جديدة ، ولكنه يقبل تعديلات السمات الحالية. هناك العديد من طرق الاختراق التي يمكنني رؤيتها للقيام بذلك ، على سبيل المثال وجود ملف __setattr__
طريقة مثل
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
ثم التحرير __dict__
مباشرة في الداخل __init__
, ، لكنني كنت أتساءل عما إذا كانت هناك طريقة "مناسبة" للقيام بذلك؟
المحلول
لن أستخدم __dict__
مباشرة ، ولكن يمكنك إضافة وظيفة إلى "تجميد" بشكل صريح:
class FrozenClass(object):
__isfrozen = False
def __setattr__(self, key, value):
if self.__isfrozen and not hasattr(self, key):
raise TypeError( "%r is a frozen class" % self )
object.__setattr__(self, key, value)
def _freeze(self):
self.__isfrozen = True
class Test(FrozenClass):
def __init__(self):
self.x = 42#
self.y = 2**3
self._freeze() # no new attributes after this point.
a,b = Test(), Test()
a.x = 10
b.z = 10 # fails
نصائح أخرى
إذا كان شخص ما مهتمًا بالقيام بذلك مع ديكور ، فإليك حلًا عملًا:
from functools import wraps
def froze_it(cls):
cls.__frozen = False
def frozensetattr(self, key, value):
if self.__frozen and not hasattr(self, key):
print("Class {} is frozen. Cannot set {} = {}"
.format(cls.__name__, key, value))
else:
object.__setattr__(self, key, value)
def init_decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
func(self, *args, **kwargs)
self.__frozen = True
return wrapper
cls.__setattr__ = frozensetattr
cls.__init__ = init_decorator(cls.__init__)
return cls
واضح جدا للاستخدام:
@froze_it
class Foo(object):
def __init__(self):
self.bar = 10
foo = Foo()
foo.bar = 42
foo.foobar = "no way"
نتيجة:
>>> Class Foo is frozen. Cannot set foobar = no way
في الواقع ، أنت لا تريد __setattr__
, ، انت تريد __slots__
. يضيف __slots__ = ('foo', 'bar', 'baz')
إلى جسم الفصل ، وسيتأكد Python من وجود Foo و Bar و Baz فقط في أي حالة. ولكن اقرأ محاذي قوائم الوثائق!
الفتحات هي الطريق للذهاب:
الطريقة البيثونية هي استخدام فتحات بدلاً من اللعب مع __setter__
. في حين أنه قد يحل المشكلة ، فإنه لا يعطي أي تحسين للأداء. يتم تخزين سمات الكائنات في قاموس "__dict__
"، هذا هو السبب ، لماذا يمكنك إضافة سمات ديناميكيًا إلى كائنات من الفئات التي أنشأناها حتى الآن. استخدام قاموس لتخزين السمات مريح للغاية ، ولكنه قد يعني مضيعة للمساحة للكائنات ، التي تحتوي على صغيرة فقط مقدار متغيرات المثيل.
فتحات هي وسيلة لطيفة للعمل حول مشكلة استهلاك الفضاء هذه. بدلاً من أن يكون لديك مقال ديناميكي يسمح بإضافة سمات إلى الكائنات ديناميكيًا ، توفر الفتحات بنية ثابتة تحظر الإضافات بعد إنشاء مثيل.
عندما نقوم بتصميم فئة ، يمكننا استخدام فتحات لمنع إنشاء سمات ديناميكية. لتحديد فتحات ، عليك تحديد قائمة بالاسم __slots__
. يجب أن تحتوي القائمة على جميع السمات ، فأنت تريد استخدامها. نوضح هذا في الفئة التالية ، حيث تحتوي قائمة الفتحات فقط على اسم سمة "Val".
class S(object):
__slots__ = ['val']
def __init__(self, v):
self.val = v
x = S(42)
print(x.val)
x.new = "not possible"
=> فشل في إنشاء سمة "جديدة":
42
Traceback (most recent call last):
File "slots_ex.py", line 12, in <module>
x.new = "not possible"
AttributeError: 'S' object has no attribute 'new'
NB:
نظرًا لأن Python 3.3 ، فإن ميزة تحسين استهلاك المساحة لم يعد مثيرًا للإعجاب. مع بيثون 3.3 المشاركة الرئيسية يتم استخدام القواميس لتخزين الكائنات. سمات الحالات قادرة على مشاركة جزء من التخزين الداخلي بين بعضها البعض ، أي الجزء الذي يخزن المفاتيح وتجزئةها المقابلة. هذا يساعد على تقليل استهلاك الذاكرة للبرامج ، والتي تخلق العديد من مثيلات الأنواع غير المبنية. ولكن لا يزال الطريق للذهاب لتجنب السمات التي تم إنشاؤها ديناميكيا.
استخدام فتحات تأتي أيضا بتكلفة خاصة بها. سوف يكسر التسلسل (مثل المخلل). سوف يكسر أيضا الميراث المتعدد. لا يمكن للفئة أن ترث من أكثر من فئة واحدة تحدد فتحات أو قبعة تخطيط مثيل محدد في رمز C (مثل القائمة أو tuple أو int).
الطريقة الصحيحة هي تجاوز __setattr__
. هذا ما هو موجود من أجله.
أحب كثيرًا الحل الذي يستخدم ديكورًا ، لأنه من السهل استخدامه للعديد من الفصول عبر المشروع ، مع الحد الأدنى من الإضافات لكل فصل. لكنه لا يعمل بشكل جيد مع الميراث. لذا ، فهذا هو الإصدار الخاص بي: إنه يتجاوز وظيفة __SetAttr__ فقط - إذا لم تكن السمة موجودة ولم تكن وظيفة المتصل __init__ ، فهي تطبع رسالة خطأ.
import inspect
def froze_it(cls):
def frozensetattr(self, key, value):
if not hasattr(self, key) and inspect.stack()[1][3] != "__init__":
print("Class {} is frozen. Cannot set {} = {}"
.format(cls.__name__, key, value))
else:
self.__dict__[key] = value
cls.__setattr__ = frozensetattr
return cls
@froze_it
class A:
def __init__(self):
self._a = 0
a = A()
a._a = 1
a._b = 2 # error
فيما يلي النهج الذي توصلت إليه ، لا يحتاج إلى سمة أو طريقة _frozen لتجميد () في init.
أثناء فيه أنا فقط أضف جميع سمات الفصل إلى المثيل.
يعجبني هذا لأنه لا يوجد _frozen و freeze () و _frozen لا يظهر أيضًا في إخراج Vars (مثيل).
class MetaModel(type):
def __setattr__(self, name, value):
raise AttributeError("Model classes do not accept arbitrary attributes")
class Model(object):
__metaclass__ = MetaModel
# init will take all CLASS attributes, and add them as SELF/INSTANCE attributes
def __init__(self):
for k, v in self.__class__.__dict__.iteritems():
if not k.startswith("_"):
self.__setattr__(k, v)
# setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist
def __setattr__(self, name, value):
if not hasattr(self, name):
raise AttributeError("Model instances do not accept arbitrary attributes")
else:
object.__setattr__(self, name, value)
# Example using
class Dog(Model):
name = ''
kind = 'canine'
d, e = Dog(), Dog()
print vars(d)
print vars(e)
e.junk = 'stuff' # fails
ماذا عن هذا:
class A():
__allowed_attr=('_x', '_y')
def __init__(self,x=0,y=0):
self._x=x
self._y=y
def __setattr__(self,attribute,value):
if not attribute in self.__class__.__allowed_attr:
raise AttributeError
else:
super().__setattr__(attribute,value)
أنا أحب "المجمدة" من Jochen Ritzel. المريح هو أن متغير isfrozen ثم يظهر عند طباعة الفصل. __ dictلقد تجولت في هذه المشكلة بهذه الطريقة من خلال إنشاء قائمة بالسمات المعتمدة (على غرار فتحات):
class Frozen(object):
__List = []
def __setattr__(self, key, value):
setIsOK = False
for item in self.__List:
if key == item:
setIsOK = True
if setIsOK == True:
object.__setattr__(self, key, value)
else:
raise TypeError( "%r has no attributes %r" % (self, key) )
class Test(Frozen):
_Frozen__List = ["attr1","attr2"]
def __init__(self):
self.attr1 = 1
self.attr2 = 1
ال FrozenClass
بقلم Jochen Ritzel رائع ، لكن الاتصال _frozen()
عند تهيلية الفصل في كل مرة لا يكون الأمر رائعًا (وتحتاج إلى خطر نسيانه). أضفت أ __init_slots__
وظيفة:
class FrozenClass(object):
__isfrozen = False
def _freeze(self):
self.__isfrozen = True
def __init_slots__(self, slots):
for key in slots:
object.__setattr__(self, key, None)
self._freeze()
def __setattr__(self, key, value):
if self.__isfrozen and not hasattr(self, key):
raise TypeError( "%r is a frozen class" % self )
object.__setattr__(self, key, value)
class Test(FrozenClass):
def __init__(self):
self.__init_slots__(["x", "y"])
self.x = 42#
self.y = 2**3
a,b = Test(), Test()
a.x = 10
b.z = 10 # fails
pystrict
هو ديكور Pypi قابل للتثبيت مستوحاة من سؤال stackoverflow الذي يمكن استخدامه مع الفصول لتجميدها. هناك مثال على ReadMe يوضح سبب حاجة إلى ديكور مثل هذا حتى لو كان لديك mypy و pylint في مشروعك:
pip install pystrict
ثم فقط استخدم Decorator Strict:
from pystrict import strict
@strict
class Blah
def __init__(self):
self.attr = 1