2012-05-25 15 views
12

मैं निम्नलिखित अजगर कोड है:पायथन मेटाक्लास: वर्ग परिभाषा के दौरान निर्धारित गुणों के लिए __setattr__ क्यों नहीं कहा जाता है?

class FooMeta(type): 
    def __setattr__(self, name, value): 
     print name, value 
     return super(FooMeta, self).__setattr__(name, value) 

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

मैं मेटा वर्ग के __setattr__ की उम्मीद है | FOO और a देने की मांग की जा रही। हालांकि, यह बिल्कुल नहीं कहा जाता है। कक्षा को परिभाषित करने के बाद जब मैं पर कुछ निर्दिष्ट करता हूं तो विधि कहा जाता है।

इस व्यवहार का कारण क्या है और क्या कक्षा के निर्माण के दौरान होने वाले असाइनमेंट को रोकने का कोई तरीका है? __new__ में attrs का उपयोग करना काम नहीं करेगा क्योंकि मैं check if a method is being redefined करना चाहता हूं।

+0

यह सटीक मुद्दा यही कारण है कि मेटालास वाक्यविन्यास py3 में बदल गया है! – SingleNegationElimination

उत्तर

20

एक वर्ग ब्लॉक एक शब्दकोश बनाने के लिए मोटे तौर पर वाक्य रचनात्मक चीनी है, और उसके बाद क्लास ऑब्जेक्ट बनाने के लिए मेटाक्लास का आह्वान किया जाता है।

यह:

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

काफी के रूप में यदि आप लिखा था बाहर आता है: (और वहाँ भी है वास्तविकता में

d = {} 
d['__metaclass__'] = FooMeta 
d['FOO'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = d.get('__metaclass__', type)('Foo', (object,), d) 

केवल नाम स्थान प्रदूषण के बिना निर्धारित करने के लिए सभी ठिकानों के माध्यम से एक खोज मेटाक्लास, या मेटाक्लास संघर्ष है या नहीं, लेकिन मैं इसे अनदेखा कर रहा हूं)।

metaclass '__setattr__ नियंत्रित कर सकते हैं क्या होता है जब आप अपने उदाहरणों में से एक (कक्षा वस्तु) पर एक विशेषता सेट करने का प्रयास है, लेकिन वर्ग ब्लॉक के अंदर आपको लगता है कि नहीं कर रहे हैं, तो आप एक में डालने रहे हैं शब्दकोश वस्तु, इसलिए dict कक्षा नियंत्रण क्या हो रहा है, आपका मेटाक्लास नहीं। तो आप भाग्य से बाहर हैं।


जब तक आप पाइथन 3.x का उपयोग नहीं कर रहे हैं! पायथन 3.x में आप मेटाक्लास पर __prepare__ क्लासमे विधि (या staticmethod) को परिभाषित कर सकते हैं, जो नियंत्रित करता है कि क्लास ब्लॉक में सेट किए गए गुणों को जमा करने से पहले ऑब्जेक्ट का उपयोग मेटाक्लास कन्स्ट्रक्टर को पास करने से पहले किया जाता है। डिफ़ॉल्ट __prepare__ बस एक सामान्य शब्दकोश दिखाए, लेकिन आप एक कस्टम dict की तरह वर्ग कि चाबी नए सिरे से परिभाषित करने की अनुमति नहीं है का निर्माण कर सकता है, और का उपयोग करें कि आपकी विशेषताएं जमा करने के लिए:

from collections import MutableMapping 


class SingleAssignDict(MutableMapping): 
    def __init__(self, *args, **kwargs): 
     self._d = dict(*args, **kwargs) 

    def __getitem__(self, key): 
     return self._d[key] 

    def __setitem__(self, key, value): 
     if key in self._d: 
      raise ValueError(
       'Key {!r} already exists in SingleAssignDict'.format(key) 
      ) 
     else: 
      self._d[key] = value 

    def __delitem__(self, key): 
     del self._d[key] 

    def __iter__(self): 
     return iter(self._d) 

    def __len__(self): 
     return len(self._d) 

    def __contains__(self, key): 
     return key in self._d 

    def __repr__(self): 
     return '{}({!r})'.format(type(self).__name__, self._d) 


class RedefBlocker(type): 
    @classmethod 
    def __prepare__(metacls, name, bases, **kwargs): 
     return SingleAssignDict() 

    def __new__(metacls, name, bases, sad): 
     return super().__new__(metacls, name, bases, dict(sad)) 


class Okay(metaclass=RedefBlocker): 
    a = 1 
    b = 2 


class Boom(metaclass=RedefBlocker): 
    a = 1 
    b = 2 
    a = 3 

इस रनिंग मुझे देता है:

Traceback (most recent call last): 
    File "/tmp/redef.py", line 50, in <module> 
    class Boom(metaclass=RedefBlocker): 
    File "/tmp/redef.py", line 53, in Boom 
    a = 3 
    File "/tmp/redef.py", line 15, in __setitem__ 
    'Key {!r} already exists in SingleAssignDict'.format(key) 
ValueError: Key 'a' already exists in SingleAssignDict 

कुछ नोट: क्योंकि यह वें से पहले बुलाया जा रहा है

  1. __prepare__, एक classmethod या staticmethod हो गया है ई मेटाक्लास 'उदाहरण (आपकी कक्षा) मौजूद है।
  2. type अभी भी, एक असली dict होने के लिए अपने तीसरे पैरामीटर की जरूरत है, ताकि आप एक __new__ विधि है कि करने के लिए एक सामान्य एक
  3. मैं dict subclassed किया जा सकता था SingleAssignDict बदल देता है, जो शायद (2) से परहेज किया जाएगा करने के लिए है, लेकिन __setitem__ जैसे बुनियादी तरीकों के आपके ओवरराइड का सम्मान नहीं करते हैं, इस तरह से मैं वास्तव में ऐसा करने से नापसंद करता हूं कि update जैसी गैर-मूलभूत विधियां कैसे हैं। तो मैं collections.MutableMapping उपclass करना पसंद करता हूं और एक शब्दकोश लपेटता हूं।
  4. वास्तविक Okay.__dict__ ऑब्जेक्ट एक सामान्य शब्दकोश है, क्योंकि इसे type और type द्वारा सेट किया गया था, जो इस प्रकार के शब्दकोश के बारे में परिष्कृत है। इसका मतलब है कि कक्षा निर्माण के बाद कक्षा विशेषताओं को ओवरराइट करना अपवाद नहीं बढ़ाता है। __new__ में सुपरक्लास कॉल के बाद आप __dict__ विशेषता को ओवरराइट कर सकते हैं यदि आप कक्षा ऑब्जेक्ट के शब्दकोश द्वारा मजबूर नो-ओवरराइटिंग को बनाए रखना चाहते हैं।

दुर्भाग्य से इस तकनीक का अजगर 2.x (मैं जाँच) में उपलब्ध नहीं है। __prepare__ विधि को लागू नहीं किया गया है, जो पाइथन 2.x के रूप में समझ में आता है मेटाक्लास क्लासब्लॉक में एक विशेष कीवर्ड की बजाय __metaclass__ जादू विशेषता द्वारा निर्धारित किया जाता है; जिसका मतलब है कि वर्ग ब्लॉक के लिए विशेषताओं को जमा करने के लिए उपयोग की जाने वाली dict वस्तु पहले से ही मेटाक्लास ज्ञात होने तक मौजूद है।

अजगर 2 की तुलना करें:

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

के बराबर होने के नाते:

d = {} 
d['__metaclass__'] = FooMeta 
d['FOO'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = d.get('__metaclass__', type)('Foo', (object,), d) 

कहाँ metaclass आह्वान करने के लिए शब्दकोश से निर्धारित किया जाता है, अजगर 3 बनाम:

class Foo(metaclass=FooMeta): 
    FOO = 123 
    def a(self): 
     pass 

होने के नाते लगभग बराबर:

d = FooMeta.__prepare__('Foo',()) 
d['Foo'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = FooMeta('Foo',(), d) 

जहां उपयोग करने के लिए शब्दकोश मेटाक्लास से निर्धारित किया जाता है।

2

क्लास गुण मेटाक्लास को एक एकल शब्दकोश के रूप में पास कर दिए जाते हैं और मेरी परिकल्पना यह है कि इसका उपयोग कक्षा के __dict__ विशेषता को एक बार में अपडेट करने के लिए किया जाता है, उदा। प्रत्येक आइटम पर setattr() करने के बजाय cls.__dict__.update(dct) की तरह कुछ। इस बिंदु पर, यह सब सी-लैंड में संभाला गया है और इसे कस्टम __setattr__() पर कॉल करने के लिए लिखा नहीं गया था।

अपने मेटाक्लास के __init__() विधि में कक्षा के गुणों को जो कुछ भी करना चाहते हैं, वह करना आसान है, क्योंकि आप कक्षा नामस्थान को dict के रूप में पारित कर चुके हैं, तो बस ऐसा करें।

7

कक्षा के निर्माण के दौरान कोई असाइनमेंट नहीं हो रहा है। या: वे हो रहे हैं, लेकिन संदर्भ में नहीं, आपको लगता है कि वे हैं। सभी वर्ग गुण वर्ग शरीर दायरे से एकत्र कर रहे हैं और metaclass '__new__ को पारित कर दिया, पिछले तर्क के रूप में:

class FooMeta(type): 
    def __new__(self, name, bases, attrs): 
     print attrs 
     return type.__new__(self, name, bases, attrs) 

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 

कारण: जब वर्ग शरीर में कोड निष्पादित करता है, वहाँ कोई वर्ग अभी तक है। जिसका अर्थ है कि मेटाक्लास को कुछ भी को को अवरुद्ध करने का कोई अवसर नहीं है।

0

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

क्योंकि नामस्थान एक नियम है, इसलिए आपके अन्य प्रश्न द्वारा सुझाए गए अनुसार डुप्लीकेट विधियों की जांच करने का कोई तरीका नहीं है। ऐसा करने का एकमात्र व्यावहारिक तरीका स्रोत कोड को पार्स करना है।