سؤال

أريد أن أكون قادرًا على إنشاء فئة (في بيثون) التي تم تهيئتها مرة واحدة __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:

  1. نظرًا لأن Python 3.3 ، فإن ميزة تحسين استهلاك المساحة لم يعد مثيرًا للإعجاب. مع بيثون 3.3 المشاركة الرئيسية يتم استخدام القواميس لتخزين الكائنات. سمات الحالات قادرة على مشاركة جزء من التخزين الداخلي بين بعضها البعض ، أي الجزء الذي يخزن المفاتيح وتجزئةها المقابلة. هذا يساعد على تقليل استهلاك الذاكرة للبرامج ، والتي تخلق العديد من مثيلات الأنواع غير المبنية. ولكن لا يزال الطريق للذهاب لتجنب السمات التي تم إنشاؤها ديناميكيا.

  2. استخدام فتحات تأتي أيضا بتكلفة خاصة بها. سوف يكسر التسلسل (مثل المخلل). سوف يكسر أيضا الميراث المتعدد. لا يمكن للفئة أن ترث من أكثر من فئة واحدة تحدد فتحات أو قبعة تخطيط مثيل محدد في رمز 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top