__In__ के बाहर नई विशेषताएँ बनाना रोकें


82

मैं एक वर्ग (पायथन में) बनाने में सक्षम होना चाहता हूं जो एक बार आरंभिक हो __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__, लेकिन मैं सोच रहा था कि क्या ऐसा करने का कोई 'उचित' तरीका है?


1
katrielalex अच्छे अंक लाता है। वहाँ इसके बारे में कुछ भी नहीं है। आप उपयोग करने से बच सकते हैं __setattr__लेकिन यह संभवत: हैक किया जाएगा।
एरोनस्टरलिंग

मैं नहीं देखता कि यह क्यों हैकी है? यह सबसे अच्छा समाधान है जो मैं प्रस्तावित कर सकता हूं और कुछ अन्य प्रस्तावितों की तुलना में बहुत अधिक रसीला।
क्रिस बी

जवाबों:


81

मैं __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

बहुत ही शांत! मुझे लगता है कि मैं उस बिट कोड को पकड़ लूंगा और उसका उपयोग शुरू कर दूंगा। (हम्म, मुझे आश्चर्य है कि अगर यह एक डेकोरेटर के रूप में किया जा सकता है, या अगर यह एक अच्छा विचार नहीं होगा ...)
वेरोनिका

5
देर से टिप्पणी: मैं कुछ समय के लिए इस नुस्खा का सफलतापूर्वक उपयोग कर रहा था, जब तक कि मैंने एक संपत्ति के लिए विशेषता नहीं बदल दी, जहां गेटटर एक NotImplementedError बढ़ा रहा था। मुझे यह पता लगाने में बहुत समय लगा कि यह इस तथ्य के कारण था कि एक्टुअल hasattrकॉल getattr, परिणाम को दूर कर देता है और त्रुटियों के मामले में गलत रिटर्न देता है, इस ब्लॉग को देखें । द्वारा प्रतिस्थापित not hasattr(self, key)करके वर्कअराउंड मिला key not in dir(self)। यह धीमा हो सकता है, लेकिन मेरे लिए समस्या हल हो गई है।
बास स्विंकल्स

31

अगर किसी को एक डेकोरेटर के साथ ऐसा करने में दिलचस्पी है, तो यहां एक काम करने वाला समाधान है:

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

डेकोरेटर संस्करण के लिए +1। यही कारण है कि मैं एक बड़ी परियोजना के लिए उपयोग करूंगा, एक बड़ी स्क्रिप्ट में यह ओवरकिल है (शायद अगर उनके पास यह मानक पुस्तकालय में था ...)। अभी के लिए केवल "आईडीई शैली चेतावनी" है।
टॉमस गैंडर

2
यह समाधान विरासत के साथ कैसे काम करता है? उदाहरण के लिए अगर मेरे पास फू का बच्चा वर्ग है, तो यह बच्चा डिफ़ॉल्ट रूप से एक जमे हुए वर्ग है?
mrgiesel

क्या इस डेकोरेटर के लिए एक पेपी पैकेज है?
winni2k

डेकोरेटर को कैसे बढ़ाया जा सकता है ताकि यह विरासत में मिली कक्षाओं के लिए काम करे?
इवान नेचिपायको

30

स्लॉट जाने का रास्ता है:

पाइथोनिक तरीका स्लॉट्स का उपयोग करने के बजाय आसपास के साथ खेलने के लिए है __setter__। हालांकि यह समस्या को हल कर सकता है, यह किसी भी प्रदर्शन में सुधार नहीं देता है। वस्तुओं की विशेषताओं को एक शब्दकोश " __dict__" में संग्रहीत किया जाता है, यही कारण है कि, आप गतिशील रूप से उन कक्षाओं की वस्तुओं में विशेषताओं को जोड़ सकते हैं जो हमने अब तक बनाए हैं। विशेषता भंडारण के लिए एक शब्दकोश का उपयोग करना बहुत सुविधाजनक है, लेकिन इसका मतलब वस्तुओं के लिए जगह की बर्बादी हो सकता है, जिसमें केवल उदाहरण चर की एक छोटी राशि है।

इस अंतरिक्ष खपत समस्या के आसपास काम करने के लिए स्लॉट एक अच्छा तरीका है। गतिशील रूप से एक तानाशाही रखने के बजाय जो गतिशील रूप से वस्तुओं को जोड़ने की अनुमति देता है, स्लॉट एक स्थिर संरचना प्रदान करते हैं जो एक उदाहरण के निर्माण के बाद परिवर्धन को रोकते हैं।

जब हम एक वर्ग डिजाइन करते हैं, तो हम विशेषताओं के गतिशील निर्माण को रोकने के लिए स्लॉट का उपयोग कर सकते हैं। स्लॉट्स को परिभाषित करने के लिए, आपको नाम के साथ एक सूची को परिभाषित करना होगा __slots__। सूची में सभी विशेषताएँ हैं, जिनका आप उपयोग करना चाहते हैं। हम इसे निम्न वर्ग में प्रदर्शित करते हैं, जिसमें स्लॉट सूची में केवल एक विशेषता "वैल" का नाम होता है।

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. पायथन 3.3 के बाद से अंतरिक्ष की खपत का अनुकूलन करने वाला लाभ उतना प्रभावशाली नहीं है। पायथन 3.3 के साथ की-शेयरिंग डिक्शनरों का उपयोग वस्तुओं के भंडारण के लिए किया जाता है। उदाहरणों की विशेषताएं एक दूसरे के बीच उनके आंतरिक भंडारण के हिस्से को साझा करने में सक्षम हैं, अर्थात वह भाग जो कुंजी और उनके संबंधित हैश को संग्रहीत करता है। यह कार्यक्रमों की मेमोरी खपत को कम करने में मदद करता है, जो गैर-बिल्टिन प्रकारों के कई उदाहरण बनाते हैं। लेकिन फिर भी गतिशील रूप से निर्मित विशेषताओं से बचने का तरीका है।
  1. स्लॉट का उपयोग करना भी अपनी लागत के साथ आता है। यह क्रमबद्धता (जैसे अचार) को तोड़ देगा। यह कई विरासत को भी तोड़ देगा। एक वर्ग एक से अधिक वर्ग से वारिस नहीं हो सकता है जो या तो स्लॉट्स को परिभाषित करता है या उसके पास C कोड (जैसे सूची, टपल या इंट) में एक उदाहरण लेआउट परिभाषित है।

20

असल में, तुम नहीं चाहते __setattr__, तुम चाहते हो __slots____slots__ = ('foo', 'bar', 'baz')वर्ग निकाय में जोड़ें , और पायथन सुनिश्चित करेगा कि किसी भी उदाहरण पर केवल फू, बार और बाज है। लेकिन प्रलेखन सूचियों को पढ़ता है!


12
__slots__कार्यों का उपयोग करना , लेकिन यह अन्य चीजों के अलावा क्रमिकता (जैसे अचार) को तोड़ देगा ... आमतौर पर यह एक बुरा विचार है कि विशेषता निर्माण को नियंत्रित करने के लिए स्लॉट्स का उपयोग करें, बल्कि मेरी राय में, स्मृति ओवरहेड को कम करने के बजाय, वैसे भी ...
जो किंगटन

मुझे पता है, और मैं इसे स्वयं उपयोग करने के लिए प्रेरित करता हूं - लेकिन नई विशेषताओं को समाप्त करने के लिए अतिरिक्त काम करना भी आमतौर पर एक बुरा विचार है;)

2
उपयोग करने से __slots__कई वंशानुक्रम भी टूट जाते हैं। एक वर्ग एक से अधिक वर्ग से वारिस नहीं हो सकता है जो या तो स्लॉट्स को परिभाषित करता है या C कोड में परिभाषित इंस्टेंस लेआउट (जैसे list, tupleया int) को टोपी करता है ।
फ्युएर्मुरमेल

यदि आप __slots__अपने अचार को तोड़ते हैं, तो आप एक प्राचीन अचार प्रोटोकॉल का उपयोग कर रहे हैं। protocol=-1सबसे हाल ही में उपलब्ध प्रोटोकॉल के लिए अचार के तरीकों को पास करें, जो कि पायथन 2 में 2 है ( 2003 में पेश किया गया )। पायथन 3 (3 और 4 क्रमशः) में डिफ़ॉल्ट और नवीनतम प्रोटोकॉल दोनों को संभालते हैं __slots__
निक मैट्टो

ठीक है, ज्यादातर समय मैं अचार का उपयोग करके पछतावा करता हूं
Erik Aronesty

7

उचित तरीका ओवरराइड करना है __setattr__। यह वही है जिसके लिए यह है।


तब चर सेट करने का उचित तरीका क्या है __init__? क्या उन्हें __dict__सीधे सेट करना है ?
एस्ट्रोफ्रा

1
मैं __setattr__अंदर से __init__, ओवरराइड करूंगा self.__setattr__ = <new-function-that-you-just-defined>
कट्रील

6
@katrielalex: नई शैली की कक्षाओं के लिए काम नहीं करेगा क्योंकि __xxx__तरीकों को केवल कक्षा पर देखा जाता है, उदाहरण के लिए नहीं।
एथन फुरमैन

6

मुझे एक समाधान पसंद है जो एक डेकोरेटर का उपयोग करता है, क्योंकि यह एक परियोजना के लिए कई वर्गों के लिए उपयोग करना आसान है, प्रत्येक वर्ग के लिए न्यूनतम परिवर्धन के साथ। लेकिन यह विरासत के साथ अच्छी तरह से काम नहीं करता है। इसलिए यहां मेरा संस्करण है: यह केवल __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

4

इस बारे में क्या:

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)

2

यहाँ दृष्टिकोण है कि मैं एक _frozen विशेषता या init को स्थिर करने के लिए विधि की आवश्यकता नहीं है के साथ आया था।

Init के दौरान मैं सिर्फ उदाहरण के लिए सभी वर्ग विशेषताओं को जोड़ता हूं।

मुझे यह पसंद है क्योंकि कोई _frozen नहीं है, फ्रीज (), और _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

यदि खेतों में से एक सूची है, तो यह काम नहीं करता है। चलिए बताते हैं names=[]। फिर और दोनों में d.names.append['Fido']सम्मिलित करेंगे । मुझे समझने के लिए पायथन के बारे में पर्याप्त जानकारी नहीं है। 'Fido'd.namese.names
रेनियर टॉरेनबेक

2

pystrictहै एक pypi स्थापना योग्य डेकोरेटर इस stackoverflow सवाल है कि उन्हें फ्रीज करने के वर्गों के साथ इस्तेमाल किया जा सकता से प्रेरित है। README के ​​लिए एक उदाहरण है जो दिखाता है कि इस तरह के एक डेकोरेटर की आवश्यकता क्यों है भले ही आपके पास अपने प्रोजेक्ट पर चलने वाले मैपी और पाइलिंट हों:

pip install pystrict

तो बस @ सजावट डेकोरेटर का उपयोग करें:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1

1

मुझे जोहान रिट्जेल का "फ्रोजन" पसंद है। असुविधाजनक यह है कि isfrozen वैरिएबल तब प्रकट होता है जब एक क्लास प्रिंट कर रहा होता है। _ तानाशाह मैं इस समस्या के चारों ओर इस तरह से अधिकृत विशेषताओं ( स्लॉट्स के समान ) की एक सूची बनाकर गया :

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

1

FrozenClassजोचेन Ritzel से शांत है, लेकिन कॉल _frozen()जब एक वर्ग हर बार इतना शांत नहीं है initialing (और आप इसे भूल का जोखिम लेने की जरूरत)। मैंने एक __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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.