W Pythonie, jakie są metaklasy i czego ich używamy?

6323
e-satis 19 wrzesień 2008, 10:10

16 odpowiedzi

Najlepsza odpowiedź

Metaklas jest klasą klasy. Klasa określa, jak instancja klasy (tj. Obiekt) zachowuje się, podczas gdy metaklas określa, jak zachowuje się klasa. Klasa jest instancją metaklasu.

W Pythonie możesz użyć arbitralnych kalcynków dla metaclasses (np. Jerbić Pokazy), lepsze podejście jest sprawienie, aby była to sama klasa. type jest zwykłą metaklą w Pythonie. type sama jest klasa i jest to jego własny typ. Nie będziesz w stanie odtworzyć czegoś takiego jak type czysto w Pythonie, ale Python nieco oszukuje. Aby utworzyć własną metaklas w Pythonie, naprawdę chcesz podklasę type.

Metaklas jest najczęściej używany jako fabryka klasy. Podczas tworzenia obiektu dzwoniąc do klasy, Python tworzy nową klasę (gdy wykonuje instrukcję "klasy"), dzwoniąc do metaklasu. W połączeniu z normalnymi metodami __init__ i __new__ Metaklasses umożliwiają wykonywanie "dodatkowych rzeczy" podczas tworzenia klasy, jak rejestrując nową klasę z niektórym rejestrem lub zastąpienie klasy z czymś całkowicie innym.

Po wykonaniu oświadczenia {{x0}, Python najpierw wykonuje korpus oświadczenia class jako normalny blok kodu. Powstała przestrzeń nazw (dict) posiada atrybuty klasy-to-be. Metaklas jest określany przez patrząc na BaseClasses klasy-To-Be (Metaclasses są dziedziczone), w atrybucie __metaclass__ klasy-to-be (jeśli istnieje) lub __metaclass__ zmienna globalna . Metaclass jest następnie wywołany nazwą, bazami i atrybutami klasy do instancji.

Jednak metaklasy faktycznie definiują typ klasy, a nie tylko fabryki, dzięki czemu możesz zrobić z nimi znacznie więcej. Możesz na przykład, zdefiniować normalne metody na metaklasie. Te metody metoklasy są jak klasyfikacje, że można je wywołać na klasie bez instancji, ale nie są one również jak klasyfikacje klasy, których nie można ich wezwać na instancji klasy. type.__subclasses__() jest przykładem metody na type Metaclas. Można również zdefiniować normalne metody "magiczne", jak __add__, __iter__ i __getattr__, aby wdrożyć lub zmienić sposób zachowania klasy.

Oto zagregowany przykład bitów i części:

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.__name__ + 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__
3117
Cameron Savage 4 marzec 2019, 21:34

Uwaga, ta odpowiedź jest dla Pythona 2.x, ponieważ została napisana w 2008 r., Metaklasy są nieco inne w 3.x.

Metaklasy to tajny sos, który tworzy "klasę" pracy. Domyślny metaclass dla obiektu nowego stylu jest nazywany "typem".

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

Metaklasy wziąć 3 args. " nazwa ", " bazy " i " DICT "

Tutaj jest tam sekret. Poszukaj tego, gdzie nazywają się, bazy i dict pochodzą z tej przykładowej definicji klasy.

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

Pozwala zdefiniować metaklas, który zademonstruje, jak " klasa: " nazywa go.

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 teraz przykład, który rzeczywiście coś oznacza, automatycznie wykonuje zmienne na liście "Atrybuty" ustawione na klasie i ustawić na Brak.

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

Należy zauważyć, że magiczne zachowanie, które Initialised zyskuje przez mający metaclasę init_attributes nie jest przekazywany na podklasie Initialised.

Oto jeszcze bardziej konkretny przykład, pokazując, jak możesz podklasowy "typ", aby wykonać metaklas, który wykonuje akcję, gdy tworzona jest klasa. To jest dość trudne:

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
425
ralh 6 listopad 2019, 07:57

Inni wyjaśnili, jak działają i jak pasują do systemu typu Pythona. Oto przykład tego, do czego można ich użyć. W ramach testowania napisałem, chciałem śledzić zamówienie, w którym zdefiniowano zajęcia, aby mogła później dodać je w tej kolejności. Znalazłem to najłatwiej to zrobić za pomocą metaklasu.

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

Wszystko, co jest podklasą MyType, a następnie dostaje atrybut klasy _order, który rejestruje kolejność, w której zdefiniowano zajęcia.

180
kindall 28 listopad 2016, 18:04

Jedno zastosowanie do metakaweń jest dodawanie nowych właściwości i metod automatycznie do instancji.

Na przykład, jeśli spojrzysz na Django Models, ich definicja wygląda trochę mylące. Wygląda na to, że jesteś tylko definiującym właściwościami klasy:

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

Jednak w czasie wykonywania Obiekty są wypełnione wszelkiego rodzaju przydatnych metod. Zobacz Źródło dla niektórych niesamowitej metaklasery.

167
Antti Rasinen 19 wrzesień 2008, 06:45

Myślę, że programowanie Metaclass Programowanie jest dobrze napisane i daje naprawdę dobre wprowadzenie do tematu, mimo że jest już kilku lat.

http://www.onlamp.com/pub/a /Python/2003/04/17/metaclasses.html (zarchiwizowany w https://web.archive.org/web/20080206005253/HTTP://www.onlamp.com/pub/a/python/ 2003/04/17 / METACLASSES.HTML)

Krótko mówiąc: Klasa jest planem stworzenia instancji, metaklas jest planem stworzenia klasy. Można go łatwo widać, że w klasach Python muszą być również obiektami pierwszej klasy, aby umożliwić to zachowanie.

Nigdy się nie napisałem, ale myślę, że jedno z najpiękniejszych zastosowań metakawszych można zobaczyć w Django Framework . Zajęcia modelu wykorzystują podejście MetaClass, aby umożliwić deklaratywny styl pisania nowych modeli lub zajęć form. Podczas gdy metaklas tworzy klasę, wszyscy członkowie otrzymują możliwość dostosowania samej klasy.

Rzecz, co pozostało, to: Jeśli nie wiesz, jakie są metaklasy, prawdopodobieństwo, że nie będzie ich potrzebować wynosi 99%.

128
Yet Another User 13 sierpień 2018, 04:53

Aktualizacja Pythona 3

Są (w tym momencie) dwóch kluczowych metod w metaklasie:

  • __prepare__ i
  • __new__

__prepare__ pozwala dostarczyć niestandardowe mapowanie (takie jak OrderedDict), aby być używanym jako przestrzeń nazw, podczas gdy klasa jest tworzona. Musisz zwrócić instancję tego, co wybierzesz przestrzeń nazw. Jeśli nie wdrażasz __prepare__ Normalna dict jest używana.

__new__ jest odpowiedzialny za rzeczywisty tworzenie / modyfikację klasy końcowej.

Bare-Bones, nie ma nic - dodatkowe metaklas chciałby:

class Meta(type):

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

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

Prosty przykład:

Powiedz, że chcesz uruchomić prosty kod walidacji na atrybutach - jak zawsze musi być int lub str. Bez metakuszki twoja klasa wyglądałaby na coś takiego:

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

Jak widać, musisz dwukrotnie powtórzyć nazwę atrybutu. To sprawia, że typo jest możliwe wraz z irytującymi błędami.

Prosty metaklas może rozwiązać ten problem:

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

Właśnie tak wyglądałby metaklas (nie używając __prepare__, ponieważ nie jest potrzebny):

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)

Przykładowy bieg:

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

Produkuje:

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

Uwaga : Ten przykład jest wystarczająco prosty, można go również osiągnąć w klasie dekoratora, ale prawdopodobnie rzeczywista metaklasa robiłaby znacznie więcej.

Klasa "ValidateType" w celach informacyjnych:

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
85
Ethan Furman 1 marzec 2016, 19:48

Metoda Metaclass '__call__() Podczas tworzenia instancji klasy

Jeśli zrobiłeś programowanie Pythona przez ponad kilka miesięcy, w końcu natkniesz się na kod, który wygląda tak:

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

Ten ostatni jest możliwy, gdy zaimplementujesz metodę magiczną __call__() na klasie.

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

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

Metoda __call__() jest wywoływana, gdy instancja klasy jest używana jako wywołany. Ale jak widzieliśmy z poprzednich odpowiedzi, sama klasa jest instancją metaclass, więc kiedy używamy klasy jako kalną (tj. Kiedy tworzymy instancję go) W rzeczywistości nazywamy jego metaclass '{x1} } metoda. W tym momencie większość programistów Pythona jest trochę zdezorientowani, ponieważ powiedziano im, że podczas tworzenia instancji taka instance = SomeClass() Wywołujesz metodę __init__(). Niektórzy, którzy wykopali trochę głębiej wiedzą, że przed __init__() jest __new__(). Cóż, dziś objawiona jest kolejna warstwa prawdy, przed __new__() Istnieje Metaclass '__call__().

Badajmy łańcuch wywołania metody, z konkretnie perspektywy tworzenia instancji klasy.

Jest to metaclass, który loguje się dokładnie w momencie, przed utworzeniem instancji i w momencie, aby go zwrócić.

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

Jest to klasa, która wykorzystuje ten metaklas

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

A teraz utwórzmy instancję 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.

Obserwuj, że powyższy kod nie robi nic więcej niż rejestrowanie zadań. Każda metoda przekazuje rzeczywistą pracę do wdrożenia jego rodzica, utrzymując tym samym domyślne zachowanie. Ponieważ type klasa macierzysta Meta_1 (type będąc domyślnym metaclass macierzystym) i biorąc pod uwagę kolejność kolejkowania wyjścia powyżej, teraz mamy wskazówkę, co byłoby pseudo Wdrożenie 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

Metoda widzimy, że Metaclass '__call__() jest tym, który został wywołany jako pierwszy. Następnie deleguje tworzeniu instancji do metody klasy {X1} klasy} i inicjalizacji do instancji __init__(). To także ten, który ostatecznie zwraca instancję.

Z powyższego wynika, że metaclass '__call__() jest również dana możliwość decydowania, czy wezwanie do Class_1.__new__() lub Class_1.__init__() zostanie ostatecznie wykonane. W trakcie jego wykonania może rzeczywiście zwrócić obiekt, który nie został dotknięty żadną z tych metod. Przyjmij na przykład to podejście do wzoru 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__()."

Obserwujmy, co się dzieje, gdy wielokrotnie próbuje utworzyć obiekt typu {x0}}

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
72
Michael Ekoka 27 sierpień 2018, 17:21

Metaclass to klasa, która mówi, jak (niektóre) należy utworzyć inną klasę.

Jest to przypadek, w którym widziałem Metaklas jako rozwiązanie mojego problemu: miałem naprawdę skomplikowany problem, który prawdopodobnie mógł rozwiązać inaczej, ale zdecydowałem się rozwiązać za pomocą metaklasu. Ze względu na złożoność jest to jeden z niewielu modułów, które napisałem, gdzie komentarze w module przewyższają ilość kodu, który został napisany. Oto ...

#!/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()
60
3 revs, 2 users 99% 25 styczeń 2016, 20:08

TL; DR wersja

Funkcja type(obj) otrzymuje typ obiektu.

type() klasy jest jego metaklass .

Aby użyć metaclash:

class Foo(object):
    __metaclass__ = MyMetaClass

type to własny metaclas. Klasa klasy jest metaclass-- Ciało klasy jest argumentami przekazywanymi do metaklasu, który służy do budowy klasy.

Oto Możesz przeczytać o tym, jak korzystać z metaków do dostosowywania konstrukcji klasy.

46
noɥʇʎԀʎzɐɹƆ 5 grudzień 2019, 16:27

type jest właściwie metaclass - klasa tworzy kolejne zajęcia. Najbardziej metaclass są podklasami type. metaclass odbiera klasę new jako pierwszy argument i zapewnia dostęp do obiektu klasy ze szczegółami, jak wspomniano poniżej:

>>> 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'
__qualname__ :'NewClass'

Note:

Zauważ, że klasa nie była tworzona w dowolnym momencie; Prosty akt tworzenia klasy wywołanej wykonanie metaclass.

45
Chankey Pathak 29 sierpień 2017, 05:23

Klasy Pythona są sami obiektami - jak w instancji - ich meta-klasy.

Domyślny metaclass, który jest stosowany, gdy określasz klasy jako:

class foo:
    ...

Klasa Meta służy do stosowania reguły do całego zestawu zajęć. Na przykład, przypuśćmy, że budujesz ORM, aby uzyskać dostęp do bazy danych, i chcesz rekordy z każdej tabeli, aby mieć klasę mapowanej do tej tabeli (na podstawie pola, reguł biznesowych itp.), Możliwe wykorzystanie metaklasu jest na przykład logikę przyłączeniową puli, która jest udostępniana przez wszystkie klasy rekordu ze wszystkich tabel. Innym użyciem jest logika, aby wspierać klucze obce, co wiąże się z wieloma klasami rekordów.

Po zdefiniowaniu metaklasu, typ podklasy, i może przejąć następujące magiczne metody, aby wstawić logikę.

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

W każdym razie te są najczęściej używanymi haczykami. Metaclashing jest potężny, a powyżej nie ma nigdzie w pobliżu i wyczerpującej listy zastosowań do metaclassing.

29
Xingzhou Liu 13 lipiec 2017, 08:18

Funkcja typu () może zwrócić typ obiektu lub utworzyć nowy typ,

Na przykład możemy utworzyć klasę HI z funkcją typu () i nie trzeba używać w ten sposób z klasą Cześć (obiekt):

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

Oprócz używania typu (), aby dynamicznie tworzyć klasy, możesz kontrolować zachowanie tworzenia klasy i używać metaklasu.

Według modelu obiektu Pythona klasa jest obiektem, więc klasa musi być instancją innej pewnej klasy. Domyślnie klasa Python jest instancją klasy typu. Oznacza to, że typ jest metaklasem większości wbudowanych klas i metaklasu klas zdefiniowanych przez użytkownika.

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

Magia będzie działać, gdy minęliśmy argumenty słów kluczowych w Metaclass, wskazuje tłumacz Pythona, aby utworzyć niestandardową listę przez Listmetaklas. Nowy (), w tym momencie możemy zmodyfikować definicję klasy, na przykład i dodać nową metodę, a następnie zwrócić poprawioną definicję.

23
binbjz 12 styczeń 2018, 09:30

Oprócz opublikowanych odpowiedzi, które mogę powiedzieć, że metaclass definiuje zachowanie klasy. Więc możesz wyraźnie ustawić swój metaclas. Kiedy tylko Python otrzymuje słowo kluczowe class, zaczyna wyszukiwać metaclass. Jeśli nie zostanie znaleziony - domyślny typ metaclass jest używany do tworzenia obiektu klasy. Korzystanie z atrybutu __metaclass__ można ustawić metaclass swojej klasy:

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

print(MyClass.__metaclass__)

Wyposażeniu tak samo wyjście:

class 'type'

I oczywiście możesz stworzyć własne metaclass, aby zdefiniować zachowanie dowolnej klasy utworzonej za pomocą swojej klasy.

Aby to zrobić, klasa typ domyślna metaclass musi być dziedziczona, ponieważ jest to główny 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__)

Wynik będzie:

class '__main__.MyMetaClass'
class 'type'
13
Andy Fedoroff 15 wrzesień 2018, 13:17

W programowaniu obiektowym, metaklas jest klasą, której instancje są klasami. Podobnie jak zwykła klasa określa zachowanie niektórych obiektów, metaklas określa zachowanie niektórych klas i ich przypadek terminy metaklasu oznacza po prostu coś wykorzystanego do tworzenia klas. Innymi słowy, jest to klasa klasy. Metaklas służy do tworzenia klas, tak jak obiekt będący instancją klasy, klasa jest instancją metaklasy. W klasach Pythona są również uważane za obiekty.

5
Venu Gopal Tewari 9 lipiec 2019, 05:45

Oto kolejny przykład tego, co można wykorzystać do:

  • Możesz użyć metaclass, aby zmienić funkcję jego instancji (klasa).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

metaclass jest potężny, istnieje wiele rzeczy (takich jak Małpa Magia), możesz z nim zrobić, ale uważaj, że może to być znane tylko.

4
Carson 20 grudzień 2019, 11:03

Klasa, w Pythonie, jest obiektem, a także jak każdy inny obiekt, jest to instancja "czegoś". To "coś" jest tym, co jest nazywane jako metaklas. Ta metaklasa jest specjalnym rodzajem klasy, która tworzy obiekty innych klasy. W związku z tym Metaclash jest odpowiedzialny za nowe zajęcia. Pozwala to na dostosowanie programisty do dostosowywania klas.

Aby utworzyć metaklasę, nadrzędną obserwowanie NEW () i init () są zazwyczaj wykonane. Nowy () można przesłonić do zmiany sposobu tworzenia obiektów, podczas gdy init () może być zastąpiony, aby zmienić sposób inicjowania obiektu. Metaklas może być tworzony przez wiele sposobów. Jednym z sposobów jest użycie funkcji typu (). Funkcja typu (), gdy wywołana 3 parametry tworzy metaklas. Parametry to: -

  1. Nazwa klasy
  2. Krotka ma podstawowe klasy odziedziczone klasą
  3. Słownik mający wszystkie metody klasy i zmienne klasy

Innym sposobem tworzenia metaklasu składa się z słowa kluczowego "Metaclass". Zdefiniuj metaklas jako prostą klasę. W parametrach odziedziczonej klasy, przepustka Metaclass = MetaClass_Name

Metaclass można specjalnie wykorzystać w następujących sytuacjach: -

  1. Gdy należy zastosować konkretny efekt do wszystkich podklas
  2. Wymagana jest automatyczna zmiana klasy (na tworzeniu)
  3. Przez programistów API
3
Swati Srivastava 20 styczeń 2020, 06:59