[python] पायथन में मेटाक्लास क्या हैं?



Answers

वस्तुओं के रूप में वर्ग

मेटाक्लास को समझने से पहले, आपको पायथन में कक्षाएं मास्टर करने की आवश्यकता है। और पाइथन के पास क्लासिकल भाषा से उधार लेने वाले वर्गों का एक बहुत ही असाधारण विचार है।

अधिकांश भाषाओं में, कक्षाएं केवल कोड के टुकड़े हैं जो वर्णन करती हैं कि किसी ऑब्जेक्ट को कैसे उत्पन्न किया जाए। पाइथन में भी यह सच है:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

लेकिन पाइथन में कक्षाएं उससे अधिक हैं। कक्षाएं वस्तुएं भी हैं।

हाँ, वस्तुओं।

जैसे ही आप कीवर्ड class उपयोग करते हैं, पायथन इसे निष्पादित करता है और एक ऑब्जेक्ट बनाता है। निर्देष

>>> class ObjectCreator(object):
...       pass
...

मेमोरी में "ऑब्जेक्ट क्रिएटर" नाम के साथ एक ऑब्जेक्ट बनाता है।

यह वस्तु (कक्षा) स्वयं वस्तुओं (उदाहरण) बनाने में सक्षम है, और यही कारण है कि यह एक वर्ग है

लेकिन फिर भी, यह एक वस्तु है, और इसलिए:

  • आप इसे एक चर के लिए असाइन कर सकते हैं
  • आप इसे कॉपी कर सकते हैं
  • आप इसमें विशेषताओं को जोड़ सकते हैं
  • आप इसे फ़ंक्शन पैरामीटर के रूप में पास कर सकते हैं

उदाहरण के लिए:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

गतिशील रूप से कक्षाएं बनाना

चूंकि कक्षाएं वस्तुएं हैं, इसलिए आप उन्हें किसी भी वस्तु की तरह फ्लाई पर बना सकते हैं।

सबसे पहले, आप कक्षा का उपयोग कर फ़ंक्शन में कक्षा बना सकते हैं:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

लेकिन यह इतना गतिशील नहीं है, क्योंकि आपको अभी भी पूरी कक्षा खुद लिखनी है।

चूंकि कक्षाएं वस्तुएं हैं, इसलिए उन्हें कुछ द्वारा उत्पन्न किया जाना चाहिए।

जब आप class कीवर्ड का उपयोग करते हैं, तो पायथन स्वचालित रूप से इस ऑब्जेक्ट को बनाता है। लेकिन पाइथन में ज्यादातर चीजों के साथ, यह आपको मैन्युअल रूप से करने का एक तरीका देता है।

फ़ंक्शन type याद रखें? अच्छा पुराना फ़ंक्शन जो आपको यह बताता है कि ऑब्जेक्ट किस प्रकार है:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

खैर, type की पूरी तरह से अलग क्षमता है, यह फ्लाई पर कक्षाएं भी बना सकता है। type कक्षा के विवरण पैरामीटर के रूप में ले सकते हैं, और एक वर्ग वापस कर सकते हैं।

(मुझे पता है, यह मूर्खतापूर्ण है कि आपके द्वारा पारित पैरामीटर के अनुसार एक ही फ़ंक्शन में दो पूरी तरह से अलग-अलग उपयोग हो सकते हैं। यह पायथन में पिछली संगतता के कारण एक समस्या है)

इस type काम करता है:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

उदाहरण के लिए:

>>> class MyShinyClass(object):
...       pass

मैन्युअल रूप से इस तरह बनाया जा सकता है:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

आप देखेंगे कि हम कक्षा के नाम के रूप में "MyShinyClass" का उपयोग करते हैं और चर संदर्भ के रूप में कक्षा संदर्भ को पकड़ते हैं। वे अलग हो सकते हैं, लेकिन चीजों को जटिल करने का कोई कारण नहीं है।

type कक्षा के गुणों को परिभाषित करने के लिए एक शब्दकोश स्वीकार करता है। इसलिए:

>>> class Foo(object):
...       bar = True

इसका अनुवाद किया जा सकता है:

>>> Foo = type('Foo', (), {'bar':True})

और एक सामान्य वर्ग के रूप में प्रयोग किया जाता है:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

और निश्चित रूप से, आप इससे प्राप्त कर सकते हैं, इसलिए:

>>>   class FooChild(Foo):
...         pass

होने वाला:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

आखिरकार आप अपनी कक्षा में विधियां जोड़ना चाहेंगे। उचित हस्ताक्षर के साथ बस एक फ़ंक्शन को परिभाषित करें और इसे एक विशेषता के रूप में असाइन करें।

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

और सामान्य रूप से बनाए गए क्लास ऑब्जेक्ट में विधियों को जोड़ने की तरह, आप कक्षा को गतिशील रूप से बनाने के बाद और भी अधिक विधियां जोड़ सकते हैं।

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

आप देखते हैं कि हम कहां जा रहे हैं: पायथन में, कक्षाएं वस्तुएं हैं, और आप गतिशील रूप से फ्लाई पर एक कक्षा बना सकते हैं।

जब आप कीवर्ड class उपयोग करते हैं तो यह पाइथन करता है, और यह मेटाक्लास का उपयोग करके ऐसा करता है।

मेटाक्लास (अंत में) क्या हैं

Metaclasses 'सामान' हैं जो वर्ग बनाता है।

वस्तुओं को बनाने के लिए आप कक्षाओं को परिभाषित करते हैं, है ना?

लेकिन हमने सीखा कि पायथन कक्षाएं वस्तुएं हैं।

खैर, मेटाक्लास वे वस्तुएं बनाते हैं। वे कक्षाओं के वर्ग हैं, आप उन्हें इस तरह से चित्रित कर सकते हैं:

MyClass = MetaClass()
my_object = MyClass()

आपने देखा है कि इस type से आप ऐसा कुछ करने देते हैं:

MyClass = type('MyClass', (), {})

ऐसा इसलिए है क्योंकि फ़ंक्शन type वास्तव में एक मेटाक्लास है। type मेटाक्लास पाइथन दृश्यों के पीछे सभी वर्गों को बनाने के लिए उपयोग करता है।

अब आप सोचते हैं कि यह बिल्ली लोअरकेस में क्यों लिखा गया है, और Type नहीं?

खैर, मुझे लगता है कि यह str साथ स्थिरता का विषय है, वह वर्ग जो स्ट्रिंग ऑब्जेक्ट बनाता है, और कक्षा में int जो पूर्णांक ऑब्जेक्ट बनाता है। type केवल वर्ग है जो कक्षा वस्तुओं को बनाता है।

आप देखते हैं कि __class__ विशेषता की जांच करके।

सबकुछ, और मेरा मतलब सबकुछ है, पाइथन में एक वस्तु है। इसमें इन्स, तार, कार्य और कक्षाएं शामिल हैं। वे सभी वस्तुएं हैं। और उनमें से सभी एक वर्ग से बनाए गए हैं:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

अब, किसी भी __class__ के __class__ क्या है?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

तो, एक मेटाक्लास केवल सामान है जो कक्षा वस्तुओं को बनाता है।

यदि आप चाहें तो आप इसे 'क्लास फैक्ट्री' कह सकते हैं।

type अंतर्निहित मेटाक्लास पाइथन उपयोग करता है, लेकिन निश्चित रूप से, आप अपना खुद का मेटाक्लास बना सकते हैं।

__metaclass__ विशेषता

जब आप कक्षा लिखते हैं तो आप __metaclass__ विशेषता जोड़ सकते हैं:

class Foo(object):
    __metaclass__ = something...
    [...]

यदि आप ऐसा करते हैं, तो पायथन क्लास Foo बनाने के लिए मेटाक्लास का उपयोग करेगा।

सावधान, यह मुश्किल है।

आप class Foo(object) पहले लिखते हैं, लेकिन क्लास ऑब्जेक्ट Foo अभी तक स्मृति में नहीं बनाया गया है।

पायथन कक्षा परिभाषा में __metaclass__ तलाश करेगा। यदि इसे पाता है, तो यह ऑब्जेक्ट क्लास Foo बनाने के लिए इसका उपयोग करेगा। यदि ऐसा नहीं होता है, तो यह वर्ग बनाने के लिए type का उपयोग करेगा।

कई बार पढ़ें।

जब तुम करोगे:

class Foo(Bar):
    pass

पायथन निम्नलिखित करता है:

क्या Foo में __metaclass__ विशेषता है?

यदि हां, तो स्मृति वर्ग में एक क्लास ऑब्जेक्ट बनाएं (मैंने क्लास ऑब्जेक्ट कहा है, मेरे साथ यहां रहें), __metaclass__ में क्या है इसका उपयोग करके Foo नाम के साथ।

यदि पायथन को __metaclass__ नहीं मिल __metaclass__ , तो यह मॉड्यूल स्तर पर __metaclass__ तलाश करेगा, और ऐसा करने का प्रयास करेगा (लेकिन केवल उन वर्गों के लिए जो कुछ भी प्राप्त नहीं करते हैं, मूल रूप से पुरानी शैली के वर्ग)।

फिर यदि यह किसी भी __metaclass__ को बिल्कुल नहीं ढूंढ पा रहा है, तो यह क्लास ऑब्जेक्ट बनाने के लिए Bar (पहले माता-पिता) का स्वयं का मेटाक्लास (जो डिफ़ॉल्ट type हो सकता है) का उपयोग करेगा।

यहां सावधान रहें कि __metaclass__ विशेषता विरासत में नहीं मिलेगी, माता-पिता का Bar.__class__ ( Bar.__class__ ) होगा। यदि Bar ने __metaclass__ विशेषता का उपयोग किया जो Bar type() type.__new__() नहीं करता है (और type.__new__() नहीं करता है type.__new__() ), उप-वर्ग उस व्यवहार का उत्तराधिकारी नहीं होंगे।

अब बड़ा सवाल यह है कि आप __metaclass__ में क्या डाल सकते हैं?

जवाब यह है: कुछ ऐसा जो कक्षा बना सकता है।

और कक्षा क्या बना सकती है? type , या कुछ भी जो subclasses या इसका उपयोग करता है।

कस्टम metaclasses

मेटाक्लास का मुख्य उद्देश्य क्लास को स्वचालित रूप से बदलना है, जब इसे बनाया गया हो।

आप आमतौर पर एपीआई के लिए ऐसा करते हैं, जहां आप मौजूदा संदर्भ से मेल खाने वाली कक्षाएं बनाना चाहते हैं।

एक बेवकूफ उदाहरण की कल्पना करो, जहां आप तय करते हैं कि आपके मॉड्यूल के सभी वर्गों में अपरकेस में लिखे गए गुण होना चाहिए। ऐसा करने के कई तरीके हैं, लेकिन एक तरीका मॉड्यूल स्तर पर __metaclass__ सेट __metaclass__ है।

इस तरह, इस मॉड्यूल के सभी वर्ग इस मेटाक्लास का उपयोग करके बनाए जाएंगे, और हमें सभी विशेषताओं को अपरकेस में बदलने के लिए मेटाक्लास को बताना होगा।

सौभाग्य से, __metaclass__ वास्तव में किसी भी __metaclass__ हो सकता है, इसे औपचारिक वर्ग होने की आवश्यकता नहीं है (मुझे पता है, इसके नाम पर 'कक्षा' के साथ कुछ कक्षा होने की आवश्यकता नहीं है, आंकड़े जाओ ... लेकिन यह सहायक है)।

तो हम एक समारोह का उपयोग करके, एक साधारण उदाहरण के साथ शुरू करेंगे।

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

अब, चलो बिल्कुल वही करते हैं, लेकिन एक मेटाक्लास के लिए वास्तविक कक्षा का उपयोग करना:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

लेकिन यह वास्तव में ओओपी नहीं है। हम सीधे कॉल type करते हैं और हम माता-पिता __new__ को ओवरराइड या कॉल नहीं करते हैं। हो जाए:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

आपने अतिरिक्त तर्क upperattr_metaclass देखा होगा। इसके बारे में कुछ खास नहीं है: __new__ हमेशा पहले पैरामीटर के रूप में परिभाषित कक्षा को प्राप्त करता है। जैसे कि आपके पास सामान्य तरीकों के लिए self जो उदाहरण को पहले पैरामीटर के रूप में प्राप्त करता है, या वर्ग विधियों के लिए परिभाषित वर्ग।

निस्संदेह, यहां इस्तेमाल किए गए नाम स्पष्टता के लिए लंबे समय तक हैं, लेकिन self तरह, सभी तर्कों के पारंपरिक नाम हैं। तो एक वास्तविक उत्पादन मेटाक्लास इस तरह दिखेगा:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

हम इसे super का उपयोग करके भी क्लीनर बना सकते हैं, जो विरासत को कम करेगा (क्योंकि हां, आप मेटाक्लास, मेटाक्लास से उत्तराधिकारी, प्रकार से विरासत प्राप्त कर सकते हैं):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

बस। Metaclasses के बारे में वास्तव में और कुछ भी नहीं है।

मेटाक्लास का उपयोग करके कोड की जटिलता के पीछे कारण मेटाक्लास के कारण नहीं है, ऐसा इसलिए होता है क्योंकि आप आम तौर पर आत्मनिरीक्षण पर निर्भर मोड़ वाली चीजें करने के लिए मेटाक्लास का उपयोग करते हैं, विरासत में हेरफेर करते हैं, जैसे __dict__ आदि।

दरअसल, मेटाक्लास विशेष रूप से काले जादू करने के लिए उपयोगी होते हैं, और इसलिए जटिल चीजें। लेकिन खुद से, वे सरल हैं:

  • कक्षा निर्माण को रोकें
  • कक्षा को संशोधित करें
  • संशोधित कक्षा वापस करें

आप कार्यों के बजाय मेटाक्लास वर्गों का उपयोग क्यों करेंगे?

चूंकि __metaclass__ किसी भी __metaclass__ करने __metaclass__ को स्वीकार कर सकता है, इसलिए आप कक्षा का उपयोग क्यों करेंगे क्योंकि यह स्पष्ट रूप से अधिक जटिल है?

ऐसा करने के कई कारण हैं:

  • इरादा स्पष्ट है। जब आप UpperAttrMetaclass(type) पढ़ते हैं, तो आप जानते हैं कि क्या चल रहा है
  • आप ओओपी का उपयोग कर सकते हैं। Metaclass मेटाक्लास से उत्तराधिकारी हो सकता है, पैरेंट विधियों को ओवरराइड कर सकता है। मेटाक्लास भी मेटाक्लास का उपयोग कर सकते हैं।
  • यदि आप मेटाक्लास-क्लास निर्दिष्ट करते हैं, लेकिन मेटाक्लास-फ़ंक्शन के साथ नहीं, तो कक्षा के उप-वर्ग इसके मेटाक्लास के उदाहरण होंगे।
  • आप अपने कोड को बेहतर बना सकते हैं। उपर्युक्त उदाहरण के रूप में आप कुछ के लिए मेटाक्लास का उपयोग कभी नहीं करते हैं। यह आमतौर पर कुछ जटिल के लिए है। कई विधियों को बनाने और उन्हें एक वर्ग में समूह बनाने की क्षमता रखने के लिए कोड को पढ़ने में आसान बनाने के लिए बहुत उपयोगी है।
  • आप __new__ , __init__ और __call__ पर हुक कर सकते हैं। जो आपको विभिन्न सामान करने की अनुमति देगा। यहां तक ​​कि यदि आप आमतौर पर __new__ में यह सब कर सकते हैं, तो कुछ लोग __init__ का उपयोग करके अधिक आरामदायक हैं।
  • इन्हें मेटाक्लास कहा जाता है, अरे! इसका मतलब कुछ होना चाहिए!

आप मेटाक्लास का उपयोग क्यों करेंगे?

अब बड़ा सवाल है। आप कुछ अस्पष्ट त्रुटि प्रवण सुविधा का उपयोग क्यों करेंगे?

खैर, आमतौर पर आप नहीं करते हैं:

Metaclasses गहरे जादू हैं कि 99% उपयोगकर्ताओं को कभी चिंता नहीं करना चाहिए। यदि आपको आश्चर्य है कि आपको उनकी आवश्यकता है, तो आप नहीं (वे लोग जो वास्तव में उन्हें निश्चित रूप से जानते हैं कि उन्हें उनकी आवश्यकता है, और उन्हें क्यों स्पष्टीकरण की आवश्यकता नहीं है)।

पायथन गुरु टिम पीटर्स

मेटाक्लास के लिए मुख्य उपयोग केस एक एपीआई बना रहा है। इसका एक सामान्य उदाहरण Django ORM है।

यह आपको इस तरह कुछ परिभाषित करने की अनुमति देता है:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

लेकिन अगर आप ऐसा करते हैं:

guy = Person(name='bob', age='35')
print(guy.age)

यह एक IntegerField ऑब्जेक्ट नहीं लौटाएगा। यह एक int वापस करेगा, और इसे सीधे डेटाबेस से भी ले जा सकते हैं।

यह संभव है क्योंकि __metaclass__ को परिभाषित करता है और यह कुछ जादू का उपयोग करता है जो उस Person को बदल देगा जिसे आपने सरल कथन के साथ परिभाषित किया है जो डेटाबेस क्षेत्र में जटिल हुक में है।

Django एक साधारण एपीआई को उजागर करके और मेटाक्लास का उपयोग करके कुछ जटिल दिखता है, दृश्यों के पीछे असली नौकरी करने के लिए इस एपीआई से कोड को दोबारा बनाना।

आख़िरी शब्द

सबसे पहले, आप जानते हैं कि कक्षाएं ऐसी वस्तुएं हैं जो उदाहरण बना सकती हैं।

वास्तव में, कक्षाएं खुद के उदाहरण हैं। Metaclasses के।

>>> class Foo(object): pass
>>> id(Foo)
142630324

सब कुछ पाइथन में एक वस्तु है, और वे या तो वर्गों या मेटाक्लास के उदाहरणों के उदाहरण हैं।

type अलावा।

type वास्तव में अपने स्वयं के मेटाक्लास है। यह ऐसा कुछ नहीं है जिसे आप शुद्ध पायथन में पुन: उत्पन्न कर सकते हैं, और कार्यान्वयन स्तर पर थोड़ा सा धोखा देकर किया जाता है।

दूसरा, मेटाक्लास जटिल हैं। आप उन्हें बहुत ही सरल वर्ग परिवर्तनों के लिए उपयोग नहीं करना चाहेंगे। आप दो अलग-अलग तकनीकों का उपयोग करके कक्षाएं बदल सकते हैं:

99% बार आपको कक्षा में बदलाव की आवश्यकता है, आप इनका उपयोग करने से बेहतर हैं।

लेकिन 98% समय, आपको कक्षा में बदलाव की आवश्यकता नहीं है।

Question

मेटाक्लास क्या हैं और हम उनके लिए क्या उपयोग करते हैं?




Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

एक साधारण उदाहरण:

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

This is what the metaclass would look like (not using __prepare__ since it is not needed):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

A sample run of:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

पैदा करता है:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value



The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass



Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

The latter is possible when you implement the __call__() magic method on the class.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

This is a class that uses that metaclass

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

And now let's create an instance of Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of type.__call__() :

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__() 
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Let's observe what happens when repeatedly trying to create an object of type Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True



The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class Hi(object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

Magic will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




मेटाक्लास के लिए एक उपयोग स्वचालित रूप से एक उदाहरण के लिए नई गुण और विधियों को जोड़ रहा है।

उदाहरण के लिए, यदि आप Django मॉडल देखते हैं , तो उनकी परिभाषा थोड़ा उलझन में दिखती है। ऐसा लगता है कि आप केवल वर्ग गुणों को परिभाषित कर रहे हैं:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

हालांकि, रनटाइम पर व्यक्ति वस्तुएं उपयोगी विधियों के सभी प्रकार से भरे हुए हैं। कुछ अद्भुत मेटाक्लासरी के लिए source देखें।




मुझे लगता है कि मेटाक्लास प्रोग्रामिंग के लिए ऑनलैम्प परिचय अच्छी तरह से लिखा गया है और कई साल पहले होने के बावजूद विषय के लिए वास्तव में एक अच्छा परिचय देता है।

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

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

मैंने कभी खुद को नहीं लिखा है, लेकिन मुझे लगता है कि मेटाग्लास के सबसे अच्छे उपयोगों में से एक Django ढांचे में देखा जा सकता है। मॉडल क्लासेस नए मॉडल या फॉर्म क्लास लिखने की घोषणात्मक शैली को सक्षम करने के लिए मेटाक्लास दृष्टिकोण का उपयोग करते हैं। जबकि मेटाक्लास वर्ग बना रहा है, सभी सदस्यों को कक्षा को स्वयं अनुकूलित करने की संभावना मिलती है।

जो बात कहना बाकी है वह है: यदि आपको नहीं पता कि मेटाक्लास क्या हैं, तो संभावना है कि आपको उनकी आवश्यकता नहीं होगी 99%।






Links