it-swarm-eu.dev

Co jsou metaclasses v Pythonu?

Co jsou to metaclasses a pro co je používáme?

5115
e-satis

Metaclass je třída třídy. Třída definuje, jak se třída třídy (tj. Objekt) chová, zatímco metaklass definuje, jak se třída chová. Třída je instancí metaclass.

Zatímco v Pythonu můžete použít libovolné callable pro metaclasses (jako Jerub shows), lepší přístup je, aby se stala skutečnou třídou samotnou. type je obvyklá metaclass v Pythonu. type je sama třída a je to její vlastní typ. Nebudete moci znovu vytvořit něco jako type čistě v Pythonu, ale Python trochu podvádí. Chcete-li vytvořit vlastní metaclass v Pythonu, opravdu chcete pouze podtřídu type.

Metaclass je nejčastěji používán jako třída-továrna. Když vytvoříte objekt voláním třídy, Python vytvoří novou třídu (když provede příkaz 'class') voláním metaklasy. V kombinaci s normálními metodami __init__ a __new__ vám tedy metaclasses dovolují dělat „extra věci“ při vytváření třídy, jako je registrace nové třídy s nějakým registrem nebo nahrazení třídy něčím úplně jiným.

Když je příkaz class spuštěn, Python nejprve provede tělo příkazu class jako normální blok kódu. Výsledný jmenný prostor (dict) drží atributy třídy-k-být. Metaclass je určen pohledem na základní třídy třídy-to-be (metaclasses jsou zděděny), v atributu __metaclass__ atributu class-to-be (pokud existuje) nebo globální proměnné __metaclass__. Metaklasta je pak nazývána názvem, bázemi a atributy třídy, aby ji vytvořila instanci.

Metaclasses však definují type třídy, ne jen továrnu, takže s nimi můžete udělat mnohem více. Můžete například definovat běžné metody na metaclass. Tyto metody metaclass jsou jako metody třídy v tom, že mohou být volány na třídě bez instance, ale také nejsou jako metody třídy v tom, že nemohou být volány na instanci třídy. type.__subclasses__() je příklad metody metaclass type. Můžete také definovat normální metody „magie“, jako je __add__, __iter__ a __getattr__, k implementaci nebo změně chování třídy.

Zde je souhrnný příklad bitů a kusů:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__+ other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
2456
Thomas Wouters

Třídy jako objekty

Před porozuměním metaklasám musíte ovládat třídy v Pythonu. Python má velmi zvláštní představu o tom, jaké třídy jsou vypůjčeny z jazyka Smalltalk.

Ve většině jazyků jsou třídy pouze kousky kódu, které popisují, jak vytvořit objekt. V Pythonu to také platí:

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

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

Třídy jsou však více než v Pythonu. Třídy jsou také objekty.

Ano, objekty.

Jakmile použijete klíčové slovo class, Python ho provede a vytvoří OBJECT. Instrukce

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

vytvoří v paměti objekt s názvem "ObjectCreator".

Tento objekt (třída) je sám schopen vytvářet objekty (instance), a proto je to třída .

Ale stále je to objekt, a proto:

  • můžete jej přiřadit proměnné
  • můžete jej zkopírovat
  • můžete k němu přidat atributy
  • můžete ji předat jako parametr funkce

např.:

>>> 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>

Dynamické vytváření tříd

Protože třídy jsou objekty, můžete je vytvořit za běhu, jako jakýkoli objekt.

Nejprve můžete vytvořit třídu ve funkci pomocí class:

>>> 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>

Ale není to tak dynamické, protože stále musíte psát celou třídu sami.

Protože třídy jsou objekty, musí být generovány něčím.

Když použijete klíčové slovo class, Python vytvoří tento objekt automaticky. Ale stejně jako u většiny věcí v Pythonu vám to umožňuje ručně.

Zapamatujte si funkci type? Dobrá stará funkce, která vám umožní zjistit, jaký typ objektu je:

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

No, type má zcela jinou schopnost, může také vytvářet třídy za běhu. type může brát popis třídy jako parametry a vrátit třídu.

(Já vím, je to hloupé, že stejná funkce může mít dvě zcela odlišná použití podle parametrů, které jí předáte. Je to problém kvůli zpětné kompatibilitě v Pythonu)

type takto funguje:

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

např.:

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

lze takto vytvořit ručně:

>>> 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>

Všimnete si, že používáme "MyShinyClass" jako název třídy a jako proměnnou, která má obsahovat odkaz na třídu. Mohou být jiné, ale není důvod komplikovat věci.

type přijímá slovník pro definování atributů třídy. Tak:

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

Lze přeložit do:

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

A používá se jako normální třída:

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

A samozřejmě můžete po něm zdědit, takže:

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

bylo by:

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

Nakonec budete chtít přidat metody do své třídy. Stačí definovat funkci se správným podpisem a přiřadit ji jako atribut.

>>> 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

Po dynamickém vytvoření třídy můžete přidávat ještě více metod, stejně jako přidávání metod do objektu normálně vytvořené třídy.

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

Vidíte, kam jedeme: v Pythonu jsou třídy objekty a můžete vytvořit třídu za běhu, dynamicky.

To je to, co dělá Python, když používáte klíčové slovo class, a to pomocí metaklasy.

Co jsou metaclasses (konečně)

Metaclasses jsou „věci“, které vytvářejí třídy.

Definujete třídy, abyste mohli vytvářet objekty, ne?

Zjistili jsme však, že třídy Python jsou objekty.

Metaclasses jsou to, co tyto objekty vytvářejí. Jedná se o třídy tříd, můžete je takto zobrazit:

MyClass = MetaClass()
my_object = MyClass()

Viděli jste, že type vám umožňuje udělat něco takového:

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

Je to proto, že funkce type je ve skutečnosti metaclass. type je metaclass Python používá k vytvoření všech tříd v zákulisí.

Teď se divíte, proč je to sakra napsáno malými písmeny a ne Type?

No, myslím, že je to věc konzistence s str, třídou, která vytváří objekty string, a int třídou, která vytváří celočíselné objekty. type je pouze třída, která vytváří objekty třídy.

Vidíte to kontrolou atributu __class__.

Všechno, a tím myslím všechno, je objekt v Pythonu. To zahrnuje ints, řetězce, funkce a třídy. Všechny jsou objekty. A všechny byly vytvořeny ze třídy:

>>> 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'>

Co je __class__ jakéhokoliv __class__?

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

Metaclass je tedy jen věc, která vytváří objekty třídy.

Pokud si to přejete, můžete to nazvat „továrnou třídy“.

type je vestavěný metaclass Python používá, ale samozřejmě můžete vytvořit vlastní metaclass.

Atribut __metaclass__

V Pythonu 2 můžete při zápisu třídy přidat atribut __metaclass__ (viz další část syntaxe jazyka Python 3):

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

Pokud tak učiníte, Python použije metaclass k vytvoření třídy Foo.

Opatrně, je to složité.

Nejprve zapíšete class Foo(object), ale objekt třídy Foo ještě není vytvořen v paměti.

Python bude hledat __metaclass__ v definici třídy. Pokud ji nalezne, použije ji k vytvoření třídy objektu Foo. Pokud tomu tak není, použije type k vytvoření třídy.

Přečtěte si to několikrát.

Když tak učiníte:

class Foo(Bar):
    pass

Python provede následující:

Existuje __metaclass__ atribut v Foo?

Pokud ano, vytvořte v paměti objekt třídy (řekl jsem objekt třídy, zůstaň se mnou zde), s názvem Foo pomocí toho, co je v __metaclass__.

Pokud Python nenajde __metaclass__, bude hledat __metaclass__ na úrovni MODULU a bude se snažit udělat to samé (ale pouze pro třídy, které nic nedědí, v podstatě staré třídy).

Pokud pak vůbec nemůže najít __metaclass__, použije metaclass Bar's (první mateřský) (což může být výchozí type) k vytvoření objektu třídy.

Buďte opatrní, že atribut __metaclass__ nebude zděděn, metaclass rodiče (Bar.__class__) bude. Pokud Bar používá atribut __metaclass__, který vytvořil Bar s type() (a ne type.__new__()), podtřídy toto chování nezdědí.

Nyní je velká otázka, co můžete dát do __metaclass__?

Odpověď zní: něco, co může vytvořit třídu.

A co může vytvořit třídu? type, nebo cokoliv, co podtřídy nebo používá.

Metaclasses v Pythonu 3

Syntaxe pro nastavení metaclass byla změněna v Pythonu 3:

class Foo(object, metaclass=something):
    ...

atribut __metaclass__ se již nepoužívá ve prospěch argumentu klíčového slova v seznamu základních tříd.

Chování metaclasses nicméně zůstane velmi stejný .

Jedna věc přidaná do metaclasses v pythonu 3 je, že můžete také předat atributy jako argumenty klíčových slov do metaklasy, jako je tomu tak:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Přečtěte si níže uvedenou sekci, jak to python zpracovává.

Vlastní metaclasses

Hlavním účelem metaclass je automaticky změnit třídu, když je vytvořena.

Obvykle to děláte pro rozhraní API, kde chcete vytvořit třídy odpovídající aktuálnímu kontextu.

Představte si hloupý příklad, kdy se rozhodnete, že všechny třídy ve vašem modulu by měly mít své atributy napsané velkými písmeny. Existuje několik způsobů, jak toho dosáhnout, ale jedním způsobem je nastavit __metaclass__ na úrovni modulu.

Tímto způsobem budou všechny třídy tohoto modulu vytvořeny pomocí této metaklasy a my musíme pouze říct metaclass, aby změnil všechny atributy na velká písmena.

Naštěstí __metaclass__ může být ve skutečnosti libovolný, nemusí to být formální třída (já vím, něco s 'třídou' ve svém názvu nemusí být třída, jít obrázek ... ale je to užitečné).

Začneme tedy jednoduchým příkladem pomocí funkce.

# 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'

Udělejme přesně to samé, ale používáme skutečnou třídu pro metaclass:

# 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)

Ale to není OOP. Přímo voláme type a nepředpisujeme ani nevolat rodič __new__. Pojďme na to:

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)

Možná jste si všimli extra argumentu upperattr_metaclass. Na tom není nic zvláštního: __new__ vždy dostává první třídu, která je definována jako první parametr. Stejně jako máte self pro běžné metody, které dostávají instanci jako první parametr, nebo definující třídu pro metody třídy.

Jména, která jsem zde použila, jsou samozřejmě z důvodu přehlednosti dlouhá, ale stejně jako u self mají všechny argumenty obvyklé názvy. Skutečná metaclass výroby by tedy vypadal takto:

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)

Můžeme to udělat ještě čistší pomocí super, které usnadní dědičnost (protože ano, můžete mít metaclasses, zdědit z metaclasses, zdědit od typu):

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)

A v pythonu 3, pokud toto volání použijete s argumenty klíčových slov, jako je tento:

class Foo(object, metaclass=Thing, kwarg1=value1):
    ...

To znamená, že to v metaclassu používá:

class Thing(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

A je to. O metaclassech není nic víc.

Důvodem složitosti kódu používající metaclasses není metaclasses, je to proto, že obvykle používáte metaclasses k tomu, aby se točily věci, které se spoléhají na introspekci, manipulaci s dědictvím, vars jako __dict__ atd.

Metaklasy jsou totiž obzvláště užitečné k tomu, aby dělali černou magii, a proto i komplikované věci. Ale samy o sobě jsou jednoduché:

  • zachytit vytvoření třídy
  • upravit třídu
  • vrátit upravenou třídu

Proč byste místo funkcí používali třídy metaclasses?

Vzhledem k tomu, že __metaclass__ může akceptovat jakékoli vypovězení, proč byste použili třídu, protože je to zjevně složitější?

Existuje několik důvodů:

  • Záměr je jasný. Když čtete UpperAttrMetaclass(type), víte, co bude následovat
  • Můžete použít OOP. Metaclass může dědit z metaclass, přepsat rodičovské metody. Metaklasy mohou dokonce používat metaclasses.
  • Podtřídy třídy budou instancemi jeho metaklasy, pokud jste zadali třídu metaclass, ale nikoli funkci metaclass.
  • Kód můžete lépe strukturovat. Nikdy nepoužívejte metaclasses pro něco tak triviálního jako výše uvedený příklad. Obvykle je to něco složitého. Mít schopnost dělat několik metod a seskupit je do jedné třídy je velmi užitečná, aby byl kód snadněji čitelný.
  • Můžete zavěsit na __new__, __init__ a __call__. Což vám umožní dělat různé věci. I když to obvykle zvládnete v __new__, někteří lidé jsou pomocí __init__ pohodlnější.
  • Ty se nazývají metaclasses, zatraceně! Musí to něco znamenat!

Proč byste měli používat metaclasses?

Teď je to velká otázka. Proč byste měli použít nějakou obskurní chybovou funkci?

No, obvykle ne:

Metaklasy jsou hlubší magií, že 99% uživatelů by se nikdy nemělo bát. Pokud vás zajímá, zda je potřebujete, nemusíte (lidé, kteří je skutečně potřebují, s jistotou vědí, že je potřebují, a nepotřebují vysvětlení proč).

Python Guru Tim Peters

Hlavní případ použití metaclass je vytvoření API. Typickým příkladem je Django ORM.

Umožňuje definovat něco takového:

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

Ale pokud to uděláte:

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

Vrací objekt IntegerField. To vrátí int, a moci dokonce vzít to přímo z databáze.

To je možné, protože models.Model definuje __metaclass__ a používá nějaké kouzlo, které změní Person, které jste právě definovali pomocí jednoduchých příkazů do komplexního háku do databázového pole.

Django dělá něco složitého vypadá jednoduše tím, že vystaví jednoduché API a používat metaclasses, znovu vytvořit kód od tohoto API dělat skutečnou práci v zákulisí.

Poslední slovo

Nejprve víte, že třídy jsou objekty, které mohou vytvářet instance.

Ve skutečnosti jsou třídy samy o sobě instancemi. Metaclasses.

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

Všechno je objekt v Pythonu a jsou to buď instance tříd nebo instancí metaclasses.

S výjimkou type.

type je vlastně vlastní metaclass. To není něco, co byste mohli reprodukovat v čistém Pythonu, a je to tím, že trochu podvádíte úroveň implementace.

Za druhé, metaclasses jsou komplikované. Možná je nebudete chtít použít pro velmi jednoduché úpravy tříd. Třídy můžete změnit pomocí dvou různých technik:

99% času, který potřebujete změnit třídu, budete lépe používat tyto.

Ale 98% času, nepotřebujete změnu třídy vůbec.

6226
e-satis

Poznámka, tato odpověď je pro Python 2.x, jak byla napsána v roce 2008, metaclasses jsou mírně odlišné v 3.x.

Metaklasy jsou tajnou omáčkou, která dělá „třídní“ práci. Výchozí metaclass pro nový objekt stylu se nazývá 'type'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaklasy zabírají 3 args. ' jméno ', ' základy ' a ' dict '

Zde začíná tajemství. Podívejte se, kde název, základy a diktát pocházejí z definice příkladové třídy.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Definujeme metaclass, který bude ukazovat jak ' class: ' to volá.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

A teď, příklad, který ve skutečnosti znamená něco, to automaticky vytvoří proměnné v seznamu "atributy" nastaveny na třídu, a nastavte na hodnotu Žádný.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Všimněte si, že magické chování, které „initalizované“ zisky mají tím, že metaclass init_attributes není předáno podtřídě Initalised.

Zde je ještě konkrétnější příklad, který ukazuje, jak můžete podtřídu „typ“ vytvořit metaklasu, která provede akci při vytvoření třídy. To je dost složité:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b
347
Jerub

Jedno použití pro metaclasses je automatické přidávání nových vlastností a metod do instance.

Například, pokud se podíváte na modely Django , jejich definice vypadá trochu matoucí. Vypadá to, že definujete pouze vlastnosti třídy:

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

Při běhu jsou však objekty Osobnosti naplněny nejrůznějšími užitečnými metodami. Viz source pro některé úžasné metaclassery.

141
Antti Rasinen

Jiní vysvětlili, jak metaclasses fungují a jak zapadají do systému typu Python. Zde je příklad toho, na co mohou být použity. V testovacím rámci jsem napsal, chtěl jsem sledovat pořadí, ve kterém byly třídy definovány, abych je mohl později v tomto pořadí vytvořit. Zjistil jsem, že je nejjednodušší to udělat pomocí metaklasy.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Cokoliv, co je podtřída MyType, pak získá atribut třídy _order, který zaznamenává pořadí, ve kterém byly třídy definovány.

140
kindall

Myslím, že úvod do programování metaclassu je dobře napsaný a dává opravdu dobrý úvod k tématu, přestože je již několik let starý.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archivováno na adrese https://web.archive.org/web/20080206005253/http: //www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )

Stručně řečeno: Třída je plán pro vytvoření instance, metaklass je plán pro vytvoření třídy. To může být snadno viditelné, že v Python třídách musí být prvotřídní objekty příliš, aby toto chování.

Nikdy jsem sám sebe nenapsal, ale myslím si, že jedno z nejkrásnějších použití metaclasses lze vidět v Django framework . Třídy modelu používají metaklasový přístup, který umožňuje deklarativní styl psaní nových modelů nebo tříd formulářů. Zatímco metaclass vytváří třídu, všichni členové mají možnost přizpůsobit si samotnou třídu.

To, co zbývá říci, je: Pokud nevíte, co jsou metaklasy, pravděpodobnost, že nepotřebujete je je 99%.

104

Co jsou metaclasses? Na co je používáte?

TLDR: Metaklass vytvoří instanci a definuje chování pro třídu, stejně jako třída vytvoří instanci a definuje chování pro instanci.

Pseudo kód:

>>> Class(...)
instance

Výše uvedené by mělo vypadat dobře. Odkud pochází Class? Jedná se o instanci metaclass (také pseudokód):

>>> Metaclass(...)
Class

V reálném kódu můžeme předat výchozí metaclass, type, vše, co potřebujeme k vytvoření instance třídy a dostaneme třídu:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Jinak řečeno

  • Třída je instanci jako metaclass pro třídu.

    Když vytvoříme instanci objektu, dostaneme instanci:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    Podobně, když definujeme třídu explicitně s výchozí metaclass, type, vytvoříme ji:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Jinak řečeno, třída je instancí třídy metaclass:

    >>> isinstance(object, type)
    True
    
  • Třetím způsobem je metaclass třídou třídy.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

Když píšete definici třídy a Python jej provede, použije metaclass k instanci objektu třídy (který bude zase použit k instanci instancí této třídy).

Stejně jako můžeme použít definice tříd ke změně chování vlastních instancí objektů, můžeme pomocí definice třídy metaclass změnit způsob, jakým se objekt třídy chová.

Na co mohou být použity? Z docs :

Potenciální využití metaklas je neomezené. Některé nápady, které byly prozkoumány, zahrnují protokolování, kontrolu rozhraní, automatickou delegaci, automatické vytváření vlastností, servery proxy, rámce a automatické zamykání/synchronizaci zdrojů.

Obvykle se však doporučuje, aby se uživatelé vyhnuli používání metakarů, pokud to není nezbytně nutné.

Metaclass používáte při každém vytvoření třídy:

Když napíšete definici třídy, například takto,

class Foo(object): 
    'demo'

Vytvoříte instanci objektu třídy.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Je to stejné jako funkční volání type s příslušnými argumenty a přiřazení výsledku proměnné tohoto jména:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Některé věci se automaticky přidají do souboru __dict__, tj. Do jmenného prostoru:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

metaclass objektu, který jsme vytvořili, v obou případech je type.

(Boční poznámka k obsahu třídy __dict__: __module__ je tam, protože třídy musí vědět, kde jsou definovány, a __dict__ a __weakref__ jsou tam, protože nedefinujeme __slots__ - pokud my define __slots__ we'll ukládat trochu místa v instancích, protože můžeme zakázat __dict__ a __weakref__ jejich vyloučením.

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... ale já odbočím.)

type můžeme rozšířit stejně jako všechny ostatní definice třídy:

Zde je výchozí __repr__ tříd:

>>> Foo
<class '__main__.Foo'>

Jedna z nejcennějších věcí, které můžeme standardně udělat při psaní objektu Python, je poskytnout mu dobrý __repr__. Když zavoláme help(repr), zjistíme, že existuje dobrý test na __repr__, který také vyžaduje test na rovnost - obj == eval(repr(obj)). Následující jednoduchá implementace __repr__ a __eq__ pro instance třídy naší třídy nám poskytuje ukázku, která může zlepšit výchozí __repr__ tříd:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Když tedy nyní vytvoříme objekt s touto metaclass, __repr__ ozvěna na příkazovém řádku poskytuje mnohem méně ošklivý pohled než výchozí:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

S Nice __repr__ definovaným pro instanci třídy máme silnější schopnost ladit náš kód. Mnohem další kontrola s eval(repr(Class)) je však nepravděpodobná (protože funkce by bylo nemožné eval z jejich výchozího __repr__ 's).

Očekávané použití: __prepare__ jmenný prostor

Chceme-li například vědět, v jakém pořadí jsou metody třídy vytvořeny, mohli bychom poskytnout objednaný dikt jako jmenný prostor třídy. Udělali bychom to s __prepare__ který vrací obor názvů dict pro třídu, pokud je implementován v Pythonu 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = Tuple(namespace)
        return result

A použití:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

A nyní máme záznam o pořadí, ve kterém byly tyto metody (a další atributy třídy) vytvořeny:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Poznámka: tento příklad byl upraven z documentation - new enum ve standardní knihovně dělá to.

Takže to, co jsme udělali, bylo vytvořit instanci metaclass vytvořením třídy. Můžeme také zacházet s metaclassem, jako bychom se rozhodli pro jakoukoli jinou třídu. Má pořadí rozlišení metody:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

A má přibližně správné repr (které už nemůžeme eval, pokud nemůžeme najít způsob, jak reprezentovat naše funkce.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})
89
Aaron Hall

Aktualizace Pythonu 3

Existují (v tomto bodě) dvě klíčové metody v metaclass:

  • __prepare__ a
  • __new__

__prepare__ umožňuje zadat vlastní mapování (například OrderedDict), které bude použito jako jmenný prostor při vytváření třídy. Musíte vrátit instanci libovolného jmenného prostoru, který si zvolíte. Pokud nezavedete __prepare__, použije se normální dict.

__new__ je zodpovědný za vlastní vytvoření/úpravu konečné třídy.

Holé kosti, metaklasy do-nic-nic navíc:

class Meta(type):

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

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

Jednoduchý příklad:

Řekněme, že chcete, aby na vašich atributech běžel nějaký jednoduchý ověřovací kód - jako vždy musí být int nebo str. Bez metaclass by vaše třída vypadala jako:

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

Jak vidíte, musíte dvakrát zopakovat název atributu. To umožňuje překlepy spolu s dráždivými bugy.

Tento problém může řešit jednoduchá metaclass:

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

To je to, co by metaclass vypadal (nepoužívá __prepare__, protože to není potřeba):

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)

Vzorový chod:

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

vyrábí:

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')

Poznámka : Tento příklad je dostatečně jednoduchý, aby mohl být také proveden s třídou dekoratér, ale pravděpodobně by metaclass skutečně dělal mnohem víc.

Třída 'ValidateType' pro 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
66
Ethan Furman

Role metody metaclass '__call__() při vytváření instance třídy

Pokud jste programování Pythonu provedli déle než několik měsíců, narazíte na kód, který vypadá takto:

# 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')

Druhá možnost je možná při implementaci magické metody __call__() ve třídě.

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

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

Metoda __call__() se vyvolá, když je instancí třídy použito jako volání. Jak jsme však viděli z předchozích odpovědí, třída sama o sobě je instancí metaklasy, takže když používáme třídu jako vypověditelnou (tj. Když ji vytvoříme), ve skutečnosti voláme metodu metaklass '__call__(). V tomto bodě je většina programátorů Pythonu trochu zmatená, protože jim bylo řečeno, že když vytváříte instanci, jako je tato instance = SomeClass(), voláte její metodu __init__(). Někteří, kteří vykopali trochu hlouběji, vědí, že před __init__() je __new__(). No, dnes je odhalena další vrstva pravdy, před __new__() je metaclass '__call__().

Podívejme se na řetězec volání metody konkrétně z pohledu vytvoření instance třídy.

Jedná se o metaclass, který zaznamenává přesně okamžik před vytvořením instance a okamžik, kdy se má vrátit.

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

Toto je třída, která používá metaklasu

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__()."

Nyní vytvořme instanci 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.

Všimněte si, že výše uvedený kód ve skutečnosti nedělá nic víc než protokolování úkolů. Každá metoda deleguje skutečnou práci na implementaci svého nadřízeného, ​​čímž se zachová výchozí chování. Vzhledem k tomu, že type je Meta_1 rodičovská třída (type je výchozí mateřská metaclass) a vzhledem k pořadí řazení výstupu výše, máme nyní ponětí, co by bylo pseudo implementací 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

Vidíme, že metoda metaclass '__call__() je ta, která je volána jako první. Poté deleguje vytvoření instance na metodu __new__() třídy a inicializaci na instanci __init__(). Je to také ten, který nakonec vrátí instanci.

Z výše uvedeného vyplývá, že metalass '__call__() je také dána možnost rozhodnout, zda bude volání Class_1.__new__() nebo Class_1.__init__() nakonec provedeno. V průběhu svého provádění by skutečně mohl vrátit objekt, který nebyl dotčen žádnou z těchto metod. Vezměme si například tento přístup ke vzorci singleton:

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__()."

Uvědomme si, co se stane, když se opakovaně pokoušíte vytvořit objekt typu 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
57
Michael Ekoka

Metaclass je třída, která říká, jak by měla být vytvořena nějaká jiná třída.

Toto je případ, kdy jsem viděl metaklass jako řešení mého problému: měl jsem opravdu komplikovaný problém, který by pravděpodobně mohl být vyřešen jinak, ale rozhodl jsem se ho vyřešit pomocí metaklasy. Kvůli složitosti je to jeden z mála modulů, které jsem napsal, kde komentáře v modulu překračují množství kódu, který byl napsán. Tady to je...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()
49
Craig

type je vlastně metaclass - třída, která vytváří další třídy. Většina metaclass jsou podtříd type. metaclass dostane jako svůj první argument třídu new a poskytne přístup k objektu třídy s podrobnostmi uvedenými níže:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qual:'NewClass'

Note:

Všimněte si, že třída nebyla kdykoli instalována; jednoduchý úkon vytvoření třídy spouštěného spuštění metaclass.

36
Mushahid Khan

Verze tl

Funkce type(obj) získá typ objektu.

type() třídy je její metaclass .

Chcete-li použít metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass
32

Třídy Pythonu jsou samy o sobě objekty - jako v příkladu - jejich meta-třídy.

Výchozí metaclass, který se použije, když určujete třídy jako:

class foo:
    ...

třída meta se používá k použití určitého pravidla na celou sadu tříd. Předpokládejme například, že vytváříte ORM pro přístup k databázi a chcete, aby záznamy z každé tabulky byly třídy namapované na tuto tabulku (na základě polí, obchodních pravidel atd.), Možného použití metaklasy je to například logika připojení fondu, kterou sdílí všechny třídy záznamu ze všech tabulek. Dalším použitím je logika na podporu cizích klíčů, což zahrnuje více tříd záznamů.

když definujete metaclass, typ podtřídy a můžete přepsat následující magické metody pro vložení vaší logiky.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: Tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

v každém případě jsou tyto dva nejčastěji používané háčky. metaclassing je mocný a výše není nikde blízko a vyčerpávající seznam použití pro metaclassing.

21
Xingzhou Liu

Funkce type () může vrátit typ objektu nebo vytvořit nový typ,

například můžeme vytvořit třídu Hi s funkcí typu () a nemusíme tento způsob používat s třídou Hi (objekt):

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

Kromě použití typu () k dynamickému vytváření tříd můžete řídit chování při vytváření třídy a používat metaclass.

Podle objektového modelu Python je třída objektem, takže třída musí být instancí jiné určité třídy. Standardně je třída Python instancí třídy typu. To znamená, že typ je metaclass většiny vestavěných tříd a metaklass uživatelsky definovaných tříd.

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']

Kouzlo se projeví, když předáme argumenty klíčových slov v metaclassu, což znamená, že interpret Python vytvoří CustomList prostřednictvím ListMetaclass. new (), v tomto bodě můžeme změnit definici třídy, například přidat novou metodu a vrátit revidovanou definici.

17
binbjz

Kromě publikovaných odpovědí mohu říci, že metaclass definuje chování třídy. Můžete tak explicitně nastavit metaclass. Kdykoli Python dostane klíčové slovo class, začne hledat metaclass. Pokud není nalezen, použije se k vytvoření objektu třídy výchozí typ metaclass. Pomocí atributu __metaclass__ můžete nastavit metaclass vaší třídy:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Výsledkem bude takto:

class 'type'

A samozřejmě můžete vytvořit vlastní metaclass, abyste definovali chování jakékoli třídy, která je vytvořena pomocí vaší třídy.

Za tímto účelem musí být výchozí třída metaclass zděděna, protože je to hlavní metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

Výstupem bude:

class '__main__.MyMetaClass'
class 'type'
7
ARGeo