Що таке метакласи в Python?


Відповіді:


2869

Метаклас - це клас класу. Клас визначає, як поводиться екземпляр класу (тобто об'єкта), а метаклас визначає, як поводиться клас. Клас - це примірник метакласу.

Хоча в Python ви можете використовувати довільні дзвінки для метакласів (як показує Jerub ), кращим підходом є перетворення його на власне клас. type- звичайний метаклас у Python. typeсам по собі клас, і це власний тип. Ви не зможете відтворити щось на зразок typeчисто в Python, але Python трохи обманює. Щоб створити свій власний метаклас у Python, ви просто хочете підкласи type.

Метаклас найчастіше використовується як фабрика класів. Коли ви створюєте об'єкт, викликаючи клас, Python створює новий клас (коли він виконує оператор 'class'), викликаючи метаклас. У поєднанні з нормальним __init__та __new__методами метакласи, таким чином, дозволяють робити "зайві речі" під час створення класу, як-от реєстрація нового класу в якомусь реєстрі або замінити клас на щось інше цілком.

Коли classоператор виконується, Python спочатку виконує тіло classоператора як звичайний блок коду. Отриманий простір імен (диктант) містить атрибути класу, який повинен бути. Метаклас визначається, переглядаючи базові класи класу, який буде бути (метакласи передаються у спадок), за __metaclass__атрибутом класу, який повинен бути (якщо він є) або __metaclass__глобальної змінної. Потім метаклас викликається ім'ям, базами та атрибутами класу для інстанції.

Однак метакласи насправді визначають тип класу, а не лише фабрику для нього, тому ви можете зробити набагато більше з ними. Наприклад, ви можете визначити звичайні методи метакласу. Ці метакласові методи схожі на classmethods тим, що їх можна викликати в класі без екземпляра, але вони також не схожі на classmethods, оскільки їх не можна викликати в екземплярі класу. type.__subclasses__()є прикладом методу на typeметакласі. Ви також можете визначити звичайні "магічні" методи, як-от __add__, __iter__і __getattr__реалізувати або змінити поведінку класу.

Ось узагальнений приклад бітів та фрагментів:

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__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry він, очевидно, означав, що ти не можеш відтворити тип, не використовуючи сам тип як метаклас. Що справедливо сказати.
Holle van

3
Чи не слід викликати відреєстратор () екземпляром класу Example?
Ciasto piekarz

5
Зверніть увагу, що __metaclass__це не підтримується в Python 3. Для використання Python 3 class MyObject(metaclass=MyType)див. Python.org/dev/peps/pep-3115 та відповідь нижче.
BlackShift

2
Документація описує спосіб вибору метакласу . Метаклас не успадковується настільки, наскільки він є похідним. Якщо ви вказали метаклас, він повинен бути підтипом кожного метакласу базового класу; в іншому випадку ви будете використовувати метаклас базового класу, який є підтипом метакласу базового класу. Зауважте, що можливо, не знайдеться дійсний метаклас, і визначення не вдасться.
чепнер

6814

Класи як об’єкти

Перш ніж розібратися в метакласах, потрібно освоїти класи в Python. І Python має дуже своєрідне уявлення про те, що таке класи, запозичені з мови Smalltalk.

У більшості мов класи - це просто фрагменти коду, які описують, як створити об'єкт. Це справді правда і в Python:

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

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

Але класи більше ніж у Python. Класи теж об’єкти.

Так, об’єкти.

Як тільки ви використовуєте ключове слово class, Python виконує його і створює OBJECT. Інструкція

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

створює в пам'яті об’єкт з назвою "ObjectCreator".

Цей об'єкт (клас) сам здатний створювати об'єкти (екземпляри), і саме тому це клас .

Але все-таки це об’єкт, а отже:

  • ви можете призначити його змінній
  • ви можете скопіювати його
  • ви можете додати до нього атрибути
  • Ви можете передати його як параметр функції

наприклад:

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

Створення класів динамічно

Оскільки класи - це об’єкти, ви можете створювати їх на льоту, як і будь-який об’єкт.

По-перше, ви можете створити клас у функції, використовуючи 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>

Але це не так динамічно, адже вам все одно доводиться писати весь клас самостійно.

Оскільки класи - це об’єкти, вони повинні бути породжені чимось.

Під час використання classключового слова Python автоматично створює цей об’єкт. Але як і у більшості речей у Python, це дає вам змогу це зробити вручну.

Пам'ятаєте функцію type? Стара хороша функція, яка дозволяє вам знати, який тип об’єкта:

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

Ну, typeмає зовсім інші здібності, він також може створювати заняття на льоту. typeможе приймати опис класу як параметри та повертати клас.

(Я знаю, нерозумно, що одна і та ж функція може мати два абсолютно різних використання відповідно до параметрів, які ви їй передаєте. Це проблема через зворотну сумісність у Python)

type працює таким чином:

type(name, bases, attrs)

Де:

  • name: назва класу
  • bases: кортеж батьківського класу (для спадкування може бути порожнім)
  • attrs: словник, що містить назви атрибутів та значення

наприклад:

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

Ви бачите, куди ми йдемо: в Python класи - це об'єкти, і ви можете створювати клас на льоту, динамічно.

Це те, що робить Python, коли ви використовуєте ключове слово class, і це робить за допомогою метакласу.

Що таке метакласи (нарешті)

Метакласи - це те, що створює класи.

Ви визначаєте класи для створення об’єктів, правда?

Але ми дізналися, що класи Python - це об’єкти.

Ну, метакласи - це те, що створює ці об’єкти. Це класи класів, ви можете зобразити їх так:

MyClass = MetaClass()
my_object = MyClass()

Ви бачили, що typeдозволяє робити щось подібне:

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

Це тому, що функція typeнасправді є метакласом. type- це метаклас, який Python використовує для створення всіх класів поза кадром.

Тепер вам цікаво, чому чорт пишеться з малих літер, а ні Type?

Ну, я думаю, це питання узгодженості з strкласом, який створює об'єкти рядків, і intкласом, який створює цілі об'єкти. typeце просто клас, який створює об’єкти класу.

Ви бачите це, перевіряючи __class__атрибут.

Все, і я маю на увазі все, є об’єктом у Python. Це включає в себе int, рядки, функції та класи. Усі вони є об’єктами. І всі вони створені з класу:

>>> 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 це вбудований метаклас, який використовує Python, але, звичайно, ви можете створити власний метаклас.

__metaclass__атрибут

У Python 2 ви можете додати __metaclass__атрибут під час запису класу (див. Наступний розділ для синтаксису Python 3):

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

Якщо ви це зробите, Python використовуватиме метаклас для створення класу Foo.

Обережно, це хитро.

Ви пишете class Foo(object)спочатку, але об’єкт класу Fooще не створений у пам'яті.

Python шукатиме __metaclass__у визначенні класу. Якщо він знайде його, він використовуватиме його для створення об’єктного класу Foo. Якщо цього немає, він буде використовуватись typeдля створення класу.

Прочитайте це кілька разів.

Коли ви робите:

class Foo(Bar):
    pass

Python робить наступне:

Чи є __metaclass__атрибут в Foo?

Якщо так, створіть у пам’яті об’єкт класу (я сказав об’єкт класу, залишайтеся зі мною тут) з назвою Foo, використовуючи те, що є __metaclass__.

Якщо Python не зможе знайти __metaclass__, він шукатиме __metaclass__на рівні МОДУЛЬ і намагатиметься зробити те саме (але тільки для класів, які нічого не успадковують, в основному класи старого стилю).

Тоді, якщо він взагалі не може знайти __metaclass__, він буде використовувати Barвласний метаклас (', який може бути за замовчуванням type) власного метакласу' s (який може бути типовим ) для створення об’єкта класу.

Тут будьте обережні, що __metaclass__атрибут не буде успадкований, метаклас батьківського ( Bar.__class__) буде. Якщо Barвикористовується __metaclass__атрибут, створений за Barдопомогою type()(а не type.__new__()), підкласи не успадкують таку поведінку.

Тепер велике питання, що ви можете поставити __metaclass__?

Відповідь: щось, що може створити клас.

А що може створити клас? typeабо будь-що, що підкласи або використовує.

Метакласи в Python 3

Синтаксис для встановлення метакласу в Python 3 змінено:

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

тобто __metaclass__атрибут більше не використовується на користь аргументу ключового слова у списку базових класів.

Однак поведінка метакласів залишається майже однаковою .

Одне, що додається до метакласів у python 3, це те, що ви також можете передавати атрибути як аргументи ключових слів у метаклас, наприклад:

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

Прочитайте розділ нижче, як python справляється з цим.

Спеціальні метакласи

Основна мета метакласу - це автоматично змінювати клас при його створенні.

Зазвичай це робиться для API, де потрібно створити класи, що відповідають поточному контексту.

Уявіть дурний приклад, коли ви вирішили, що всі класи у вашому модулі повинні мати свої атрибути, написані великими літерами. Існує кілька способів зробити це, але один із способів - встановити __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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

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

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

Давайте перевіримо:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Давайте перепишемо вище, але з більш короткими та реалістичнішими назвами змінних тепер, коли ми знаємо, що вони означають:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Можливо, ви помітили додатковий аргумент cls. У цьому немає нічого особливого: __new__завжди отримує клас, визначений у ньому, як перший параметр. Так само, як selfу звичайних методів, які отримують екземпляр як перший параметр, або визначальний клас для методів класу.

Але це не є належним ООП. Ми дзвонимо typeбезпосередньо, і ми не перекреслюємо або не дзвонимо батькам __new__. Зробимо це замість цього:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Ми можемо зробити його ще чистішим, використовуючи super, що полегшить успадкування (адже так, ви можете мати метакласи, успадковуючи метакласи, успадковуючи від типу):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

О, і в python 3, якщо ви робите цей виклик з аргументами ключових слів, як це:

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

Це перекладається на це в метакласі, щоб використовувати його:

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

Це воно. Дійсно нічого про метакласи немає.

Причина складності коду з використанням метакласів полягає не в метакласах, а тому, що ви зазвичай використовуєте метакласи, щоб робити кручені речі, спираючись на самоаналіз, маніпулювання спадщиною, такі зміни, як __dict__тощо.

Дійсно, метакласи особливо корисні для того, щоб робити чорну магію, а тому складні речі. Але самі по собі вони прості:

  • перехопити створення класу
  • модифікувати клас
  • повернути модифікований клас

Чому б ви використовували класи метакласів замість функцій?

Оскільки ви __metaclass__можете приймати будь-які дзвінки, чому ви використовуєте клас, оскільки це, очевидно, складніше?

Для цього є кілька причин:

  • Намір зрозумілий. Коли ви читаєте UpperAttrMetaclass(type), ви знаєте, що буде далі
  • Ви можете використовувати OOP. Метаклас може успадковувати метакласи, переосмислювати батьківські методи. Метакласи можуть використовувати навіть метакласи.
  • Підкласи класу будуть екземплярами його метакласу, якщо ви вказали клас метакласу, але не з функцією метакласу.
  • Ви можете краще структурувати код. Ви ніколи не використовуєте метакласи для чогось такого тривіального, як наведений вище приклад. Зазвичай це для чогось складного. Мати можливість скласти кілька методів та згрупувати їх в одному класі дуже корисно для полегшення читання коду.
  • Ви можете зачепити __new__, __init__і __call__. Що дозволить вам робити різні речі. Навіть якщо зазвичай ви можете це все зробити __new__, деякі люди просто зручніше користуватися __init__.
  • Це називаються метакласами, чорт забирай! Це має щось означати!

Навіщо використовувати метакласи?

Тепер велике питання. Чому б ви використовували якусь незрозумілу функцію схильності до помилок?

Ну, зазвичай ви цього не робите:

Метакласи - це глибша магія, про яку ніколи не повинні турбуватися 99% користувачів. Якщо вам цікаво, чи потрібні вони вам, ви цього не робите (люди, які насправді їх потребують, з упевненістю знають, що вони їм потрібні, і не потребують пояснень, чому це потрібно).

Пітон Гуру Тім Петерс

Основним випадком використання метакласу є створення API. Типовим прикладом цього є ORG Django. Це дозволяє визначити щось подібне:

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

Але якщо ви це зробите:

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

Він не поверне IntegerFieldоб'єкт. Він поверне intі навіть може взяти його безпосередньо з бази даних.

Це можливо, тому що models.Modelвизначає __metaclass__і використовує деяку магію, яка перетворить Personтобі щойно визначені прості висловлювання у складний гак у поле бази даних.

Django робить щось складне виглядати простим, відкриваючи простий API і використовуючи метакласи, відтворюючи код з цього API, щоб зробити справжню роботу за кадром.

Останнє слово

По-перше, ви знаєте, що класи - це об'єкти, які можуть створювати екземпляри.

Ну насправді, класи самі по собі. Метакласів.

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

У Python все є об'єктом, і всі вони є або екземплярами класів, або екземплярами метакласів.

За винятком type.

typeнасправді є власним метакласом. Це не те, що ви могли б відтворити в чистому Python, і це робиться шляхом обману трохи на рівні реалізації.

По-друге, метакласи складні. Можливо, ви не хочете використовувати їх для дуже простих змін класу. Ви можете змінити заняття за допомогою двох різних технік:

99% часу, коли вам потрібна зміна класу, вам краще використовувати їх.

Але в 98% часу вам зовсім не потрібна зміна класу.


30
Здається, що в Django models.Modelвін не використовує, __metaclass__а class Model(metaclass=ModelBase):посилається на ModelBaseклас, який потім виконує вищезгадану магію метакласу. Чудовий пост! Ось джерело Django: github.com/django/django/blob/master/django/db/models/…
Макс Гудрідж

15
<< Будьте обережні, що __metaclass__атрибут не буде успадкований, метаклас батьківського ( Bar.__class__) буде. Якщо Barвикористовується __metaclass__атрибут, створений за Barдопомогою type()(а не type.__new__()), підкласи не успадкують таку поведінку. >> - Чи могли б ви / хтось пояснити трохи глибше цей уривок?
petrux

15
@MaxGoodridge Це синтаксис Python 3 для метакласів. Дивіться Python 3.6 Модель даних VS Python 2.7 Модель даних
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?- добре, тому що він реалізований на C - це та сама причина, що за замовчуванням є нижній регістр, а OrdersDict (у python 2) є нормальним CamelCase
Mr_and_Mrs_D

15
Це відповідь у вікі спільноти (тому ті, хто коментує виправлення / вдосконалення, можуть спробувати змінити свої коментарі у відповідь, якщо вони впевнені, що вони правильні).
Brōtsyorfuzthrāx

403

Зауважте, ця відповідь стосується Python 2.x, як це було написано у 2008 році, метакласи трохи відрізняються в 3.x.

Метакласи - це таємний соус, який змушує працювати над класом. Метаклас за замовчуванням для нового об’єкта стилю називається "type".

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

Метакласи займають 3 аргументи. " ім'я ", " бази " та " диктант "

Ось звідки починається секрет. Шукайте, звідки в цьому прикладі визначення класу походять назва, бази та диктант.

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

Давайте визначимо метаклас, який буде демонструвати, як ' class: ' називає його.

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'

А тепер, наприклад, що насправді щось означає, це автоматично зробить змінні у списку "атрибути", встановлені на класі, і встановить значення "None".

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

Зауважте, що магічна поведінка, яку Initialisedотримує метаклас init_attributes, не передається на підклас Initialised.

Ось ще більш конкретний приклад, який показує, як можна підклас 'type' скласти метаклас, який виконує дію під час створення класу. Це досить складно:

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

169

Інші пояснили, як працюють метакласи та як вони вписуються в систему типу Python. Ось приклад того, для чого вони можуть бути використані. У тестовій рамці, яку я написав, я хотів відслідковувати порядок визначення класів, щоб потім міг їх інстанціювати в цьому порядку. Мені було найпростіше це зробити за допомогою метакласу.

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

Все, що є підкласом, MyTypeтоді отримує атрибут класу, _orderякий записує порядок, у якому були визначені класи.


Дякую за приклад. Чому ви це простіше , ніж спадкування від MyBase, якого __init__(self)говорить type(self)._order = MyBase.counter; MyBase.counter += 1?
Майкл

1
Я хотів, щоб самі класи, а не їхні екземпляри, були нумеровані.
kindall

Правильно, да. Дякую. Мій код буде скидати атрибут MyType за кожної інстанції і ніколи не встановлюватиме атрибут, якби екземпляр MyType ніколи не створювався. На жаль (І властивість класу також може працювати, але на відміну від метакласу, він не пропонує очевидного місця для зберігання лічильника.)
Майкл

1
Це прикольний цікавий приклад, не в останню чергу тому, що справді можна зрозуміти, чому метаклас може бути пов'язаний з цим, щоб забезпечити рішення певної складності. ОТО, я намагаюся переконатись, що комусь дійсно потрібно створити об'єкти в порядку, в якому визначено їхні класи: я думаю, ми просто повинні прийняти ваше слово для цього :).
мік гризун

159

Одне використання метаклас - це додавання нових властивостей і методів до екземпляра автоматично.

Наприклад, якщо подивитися на моделі Django , їх визначення виглядає дещо заплутаним. Схоже, ви визначаєте лише властивості класу:

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

Однак під час виконання об'єкти Особи заповнюються всілякими корисними методами. Дивіться джерело для деяких дивовижних метакласиків.


6
Чи не використання мета-класів додає нові властивості та методи до класу, а не екземпляр? Наскільки я зрозумів це, мета-клас змінює сам клас, і як результат, змінені класи можуть бути побудовані по-різному. Може бути трохи оманливим для людей, які намагаються отримати природу мета-класу. Наявність корисних методів на примірниках може бути досягнуто за нормальної невідповідності. Хоча посилання на код Джанго як приклад хороша.
trixn

119

Я думаю, що введення ONLamp у програмування метакласу добре написане і дає дуже гарне вступ до цієї теми, незважаючи на те, що мені вже кілька років.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (архівовано за адресою https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclasses.html )

Коротше кажучи: клас - це креслення для створення екземпляра, метаклас - це креслення для створення класу. Неважко помітити, що в Python-класах теж потрібно бути об'єктами першокласного рівня, щоб дозволити цю поведінку.

Я ніколи не писав жодного, але думаю, що одне з найприємніших способів використання метакласів можна побачити в рамках Джанго . Класи моделей використовують підхід метакласу, щоб включити декларативний стиль написання нових моделей або класів форм. У той час як метаклас створює клас, всі члени отримують можливість налаштувати сам клас.

Залишилося сказати: Якщо ви не знаєте, що таке метакласи, ймовірність того, що вони вам не знадобляться, становить 99%.


109

Що таке метакласи? Для чого ви їх використовуєте?

TLDR: Метаклас інстанціює та визначає поведінку для класу так само, як клас інстанціює та визначає поведінку для екземпляра.

Псевдокод:

>>> Class(...)
instance

Сказане повинно виглядати звично. Ну, звідки береться Class? Це екземпляр метакласу (також псевдокод):

>>> Metaclass(...)
Class

У реальному коді ми можемо передати метаклас за замовчуванням type, все, що потрібно для екземпляра класу, і ми отримаємо клас:

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

По-різному

  • Клас - це екземпляр, оскільки метаклас - це клас.

    Коли ми створюємо об'єкт, ми отримуємо екземпляр:

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

    Так само, коли ми чітко визначаємо клас із метакласом за замовчуванням type, ми інстанціюємо його:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • По-іншому, клас - це примірник метакласу:

    >>> isinstance(object, type)
    True
  • По-третє, метаклас - це клас класу.

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

Коли ви пишете визначення класу, а Python виконує його, він використовує метаклас для інстанціювання об'єкта класу (який, у свою чергу, буде використовуватися для інстанції екземплярів цього класу).

Так само, як ми можемо використовувати визначення класів, щоб змінити поведінку власних екземплярів об'єкта, ми можемо використовувати визначення класу метакласу, щоб змінити поведінку об'єкта класу.

Для чого їх можна використовувати? З документів :

Потенційне використання метакласів безмежне. Деякі досліджені ідеї включають реєстрацію, перевірку інтерфейсу, автоматичне делегування, автоматичне створення властивостей, проксі, рамки та автоматичне блокування / синхронізацію ресурсів.

Тим не менш, зазвичай рекомендується користувачам уникати використання метакласів, якщо це абсолютно не потрібно.

Ви використовуєте метаклас кожного разу, коли створюєте клас:

Наприклад, коли ви пишете визначення класу, наприклад, як це,

class Foo(object): 
    'demo'

Ви створюєте об'єкт класу.

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

Це те саме, що функціонально викликати typeвідповідними аргументами та присвоювати результат змінній цього імені:

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

Зауважте, деякі речі автоматично додаються до __dict__простору імен:

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

Метаклассом об'єкта , який ми створили, в обох випадках це type.

(Побічна примітка до вмісту класу __dict__: __module__чи є тому, що класи повинні знати, де вони визначені, і __dict__чи __weakref__є вони, тому що ми не визначаємо __slots__- якщо ми визначаємо,__slots__ ми заощадимо трохи місця в примірниках, як ми можемо заборонити __dict__і __weakref__виключити їх. Наприклад:

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

... але я відволікаюсь.)

Ми можемо розширити так typeсамо, як і будь-яке інше визначення класу:

Ось за замовчуванням __repr__класів:

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

Однією з найцінніших речей, яку ми можемо зробити за замовчуванням при написанні об’єкта Python - це забезпечити йому добро __repr__. Коли ми телефонуємо, help(repr)ми дізнаємось, що є хороший тест на __repr__те, що також вимагає тесту на рівність - obj == eval(repr(obj)). Наступна проста реалізація __repr__та __eq__для екземплярів нашого класу типу дає нам демонстрацію, яка може покращитися за умовчанням __repr__класів:

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.__name__ 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__)

Отже, коли ми створюємо об'єкт з цим метакласом, __repr__відлуння в командному рядку забезпечує набагато менш потворне уявлення, ніж за замовчуванням:

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

З приємним __repr__визначенням для екземпляра класу, ми маємо більш сильну здатність налагоджувати наш код. Однак набагато подальша перевірка з цим eval(repr(Class))малоймовірною (оскільки функцій було б неможливо порівняти із замовчуванням __repr__).

Очікуване використання: __prepare__простір імен

Якщо, наприклад, ми хочемо знати, в якому порядку створені методи класу, ми могли б надати впорядкований дикт як простір імен класу. Ми зробимо це, за допомогою __prepare__чого повертає дикт простору імен для класу, якщо він буде реалізований в Python 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

І використання:

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

А тепер у нас є запис про порядок створення цих методів (та інших атрибутів класу):

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

Зауважте, цей приклад був адаптований з документації - новий перелік у стандартній бібліотеці робить це.

Тому ми створили метаклас, створивши клас. Ми також можемо ставитися до метакласу, як і до будь-якого іншого класу. Він має порядок роздільної здатності методу:

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

І він має приблизно правильне repr(що ми вже не можемо оцінити, якщо не зможемо знайти спосіб представити свої функції.):

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

78

Оновлення Python 3

Наразі є два ключові методи в метакласі:

  • __prepare__, і
  • __new__

__prepare__дозволяє надати спеціальне відображення (наприклад, an OrderedDict), яке буде використовуватися як простір імен під час створення класу. Ви повинні повернути екземпляр будь-якого простору імен. Якщо ви не реалізуєте, __prepare__використовується звичайний dict.

__new__ відповідає за фактичне створення / модифікацію підсумкового класу.

Голі кістки, чи не зайвий метаклас, хотіли б:

class Meta(type):

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

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

Простий приклад:

Скажімо, ви хочете, щоб на ваших атрибутах запускався простий код перевірки - як завжди він повинен бути intабо a str. Без метакласу ваш клас виглядав би приблизно так:

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

Як бачите, ви повинні повторити ім’я атрибуту двічі. Це робить можливими помилки друку разом із дратівливими помилками.

Простий метаклас може вирішити цю проблему:

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

Ось як виглядатиме метаклас (не використовується, __prepare__оскільки він не потрібен):

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)

Вибірка зразка:

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

Примітка : Цей приклад досить простий, він також міг бути виконаний за допомогою декоратора класу, але, мабуть, власне метаклас робив би набагато більше.

Клас "ValidateType" для довідок:

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

Нічого собі, це дивовижна нова функція, про яку я не знав, що існує в Python 3. Дякую за приклад !!
Багатий доктор фізичних наук

Зауважте, що, оскільки python 3.6, ви можете використовувати __set_name__(cls, name)в дескрипторі ( ValidateType) для встановлення імені в дескрипторі ( self.nameі в цьому випадку також self.attr). До цього додано, що не потрібно занурюватися в метакласи для цього конкретного випадку загального використання (див. PEP 487).
Ларс

68

Роль методу метакласу __call__()при створенні екземпляра класу

Якщо ви займалися програмуванням Python більше декількох місяців, ви зрештою натрапите на код, який виглядає приблизно так:

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

Останнє можливо, коли ви реалізуєте __call__()на уроці магічний метод.

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

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

__call__()Метод викликається , коли екземпляр класу використовується в якості викликається. Але, як ми бачили з попередніх відповідей, сам клас є екземпляром метакласу, тому коли ми використовуємо клас як позивний (тобто коли створюємо його екземпляр), ми фактично називаємо його __call__()метод метакласу . На даний момент більшість програмістів Python трохи розгублені, оскільки їм сказали, що, створюючи такий екземпляр, instance = SomeClass()ви викликаєте його __init__()метод. Деякі, хто копав трохи глибше, знають, що ще до __init__()цього __new__(). Ну, сьогодні відкривається ще один шар істини, перш ніж __new__()з'явиться метаклас ' __call__().

Давайте вивчимо ланцюжок викликів методів конкретно з точки зору створення екземпляра класу.

Це метаклас, який записує точно в той момент, як створений екземпляр, і момент, коли він збирається повернути його.

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

Це клас, який використовує цей метаклас

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

А тепер давайте створимо екземпляр 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.

Зауважте, що наведений вище код насправді не робить нічого більшого, ніж реєстрацію завдань. Кожен метод делегує фактичну роботу реалізованому батьком, таким чином зберігаючи поведінку за замовчуванням. Оскільки typeце Meta_1батьківський клас ( typeякий є батьківським метакласом за замовчуванням) та враховуючи послідовність упорядкування виводу, наведеного вище, тепер ми маємо підказку щодо того, якою була б псевдореалізація 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

Ми можемо бачити, що __call__()метод метакласу - це той, хто називається першим. Потім він делегує створення екземпляра __new__()методу класу та ініціалізацію до примірника __init__(). Це в кінцевому підсумку повертає екземпляр.

З вищевикладеного випливає, що метакласу " __call__()також надається можливість вирішити, чи буде зроблено дзвінок Class_1.__new__()чи ні Class_1.__init__(), зрештою, буде здійснено. В ході його виконання він міг фактично повернути об'єкт, який не торкнувся жоден із цих методів. Візьмемо для прикладу такий підхід до одинарного шаблону:

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

Давайте спостерігатимемо, що відбувається, коли неодноразово намагаються створити об’єкт типу 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

Це хороше доповнення до раніше затвердженої "прийнятої відповіді". Він надає приклади проміжних кодерів для жування.
Багатий доктор наук

56

Метаклас - це клас, який розповідає про те, як слід створити (якийсь) інший клас.

Це випадок, коли я розглядав метаклас як рішення своєї проблеми: у мене була справді складна проблема, яка, ймовірно, могла бути вирішена інакше, але я вирішив її вирішити за допомогою метакласу. Через складність це один з небагатьох написаних мною модулів, де коментарі в модулі перевищують кількість написаного коду. Ось...

#!/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()

43

Версія tl; dr

type(obj)Функція отримує вас тип об'єкта.

type()Класу є його метаклассом .

Щоб використовувати метаклас:

class Foo(object):
    __metaclass__ = MyMetaClass

typeце власний метаклас. Клас класу - це метаклас - тіло класу - це аргументи, передані метакласу, який використовується для побудови класу.

Тут ви можете прочитати про те, як використовувати метакласи для налаштування структури класів.


42

typeнасправді а metaclass- клас, який створює інші класи. Більшість metaclass- це підкласи type. metaclassПриймає newклас в якості першого аргументу і забезпечити доступ до об'єкта класу з деталями , як зазначено нижче:

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

Зауважте, що клас не був ініційованим в будь-який час; простий акт створення класу викликав виконання metaclass.


27

Класи Python самі по собі є об'єктами, як, наприклад, мета-класу.

Метаклас за замовчуванням, який застосовується, коли ви визначаєте класи як:

class foo:
    ...

Мета-клас використовується для застосування якогось правила до всього набору класів. Наприклад, припустимо, що ви створюєте ORM для доступу до бази даних, і ви хочете, щоб записи з кожної таблиці були класом, відображеним у цій таблиці (на основі полів, бізнес-правил тощо), можливе використання метакласу Наприклад, логіка пулу з'єднань, яка поділяється на всі класи записів з усіх таблиць. Іншим використанням є логіка для підтримки зовнішніх ключів, яка включає в себе кілька класів записів.

коли ви визначаєте метаклас, ви підклас типу і може замінити наступні магічні методи, щоб вставити вашу логіку.

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

так чи інакше, ці два - це найчастіше використовувані гачки. метакласифікація є потужною, і перелік застосувань для метакласифікації вище ніде не є і є вичерпним.


21

Функція type () може повернути тип об'єкта або створити новий тип,

наприклад, ми можемо створити клас Hi з функцією type () і нам не потрібно використовувати цей спосіб з класом Hi (об’єкт):

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

Крім використання типу () для динамічного створення класів, ви можете керувати поведінкою створення класу та використовувати метаклас.

Відповідно до об'єктної моделі Python, клас є об'єктом, тому клас повинен бути екземпляром іншого певного класу. За замовчуванням клас Python є примірником класу типу. Тобто type - це метаклас більшості вбудованих класів та метаклас визначених користувачем класів.

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

Магія набере чинності, коли ми передамо аргументи ключових слів у метаклас, це вказує на інтерпретатора Python для створення користувальницького списку через ListMetaclass. new (), в цей момент ми можемо змінити визначення класу, наприклад, і додати новий метод, а потім повернути переглянутий визначення.


11

На додаток до опублікованих відповідей можу сказати, що metaclassвизначає поведінку для класу. Отже, ви можете явно встановити свій метаклас. Щоразу, коли Python отримує ключове слово, classвін починає пошук metaclass. Якщо його не знайти - тип метакласу за замовчуванням використовується для створення об’єкта класу. Використовуючи __metaclass__атрибут, ви можете встановити metaclassсвій клас:

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

print(MyClass.__metaclass__)

Це дасть вихід таким чином:

class 'type'

І, звичайно, ви можете створити свій власний, metaclassщоб визначити поведінку будь-якого класу, створеного за допомогою вашого класу.

Для цього metaclassклас типового типу повинен бути успадкований, оскільки це головне 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__)

Вихід буде:

class '__main__.MyMetaClass'
class 'type'

4

У об'єктно-орієнтованому програмуванні метаклас - це клас, екземпляри якого є класами. Як звичайний клас визначає поведінку певних об'єктів, так і метаклас визначає поведінку певного класу та їх екземплярів. Термін метаклас означає просто те, що використовується для створення класів. Іншими словами, це клас класу. Метаклас використовується для створення класу так, як об'єкт, який є екземпляром класу, клас - це екземпляр метакласу. У класах пітона також розглядаються об'єкти.


Замість того, щоб дати книжкові визначення, було б краще, якби ви додали кілька прикладів. Перший рядок вашої відповіді, здається, був скопійований із статті Вікіпедії "Метакласи".
правдивість

@verisimilitude Я також вчусь, чи можете ви допомогти мені покращити цю відповідь, надавши кілька практичних прикладів зі свого досвіду ??
Venu Gopal Tewari

2

Ось ще один приклад того, для чого це можна використовувати:

  • Ви можете використовувати metaclassдля зміни функції його примірника (класу).
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Є потужним, є багато речей (наприклад, мавпи магії) , ви можете зробити з ним, але будьте обережні , це може бути відомо тільки вам.


2

Клас у Python - це об'єкт, і, як і будь-який інший об'єкт, він є екземпляром "чогось". Це "щось" - це те, що називається метакласом. Цей метаклас - це особливий тип класу, який створює об'єкти інших класів. Отже, метаклас відповідає за створення нових класів. Це дозволяє програмісту налаштувати спосіб генерації класів.

Для створення метакласу зазвичай робиться переосмислення нових () та методів init (). new () може бути відмінено для зміни способу створення об’єктів, тоді як init () може бути замінено для зміни способу ініціалізації об'єкта. Метаклас можна створити різними способами. Один із способів - використовувати функцію type (). Функція type (), коли викликається з 3 параметрами, створює метаклас. Параметри:

  1. Назва класу
  2. Кортеж, що має базові класи, успадковані класом
  3. Словник, що містить усі методи класу та змінні класу

Інший спосіб створення метакласу включає ключове слово "метаклас". Визначте метаклас як простий клас. У параметрах спадкового класу передайте метаклас = metaclass_name

Метаклас може бути спеціально використаний у наступних ситуаціях: -

  1. коли певний ефект має бути застосований до всіх підкласів
  2. Потрібна автоматична зміна класу (при створенні)
  3. За розробниками API

2

Зауважте, що в python 3.6 __init_subclass__(cls, **kwargs)був введений новий метод дандера для заміни багатьох випадків загального використання для метакласів. Викликається, коли створюється підклас класу, що визначає. Див. Документи python .


-3

Метаклас - це такий клас, який визначає, як буде поводитися клас, або ми можемо сказати, що клас сам є екземпляром метакласу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.