>>> print(ObjectCreator) # Вы можете распечатать класс, потому что это объект
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # Вы можете передать класс в качестве параметра
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # Вы можете добавлять атрибуты в класс
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # Вы можете назначить класс переменной
>>> print(ObjectCreatorMirror.new_attribute)
foo
动态类创建
因为类是对象,所以您可以像创建任何对象一样动态创建它们。
首先,您可以使用在函数中创建一个类class
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # вернуть класс, а не экземпляр
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # функция возвращает класс, а не экземпляр
<class '__main__.Foo'>
>>> print(MyClass()) # вы можете из этого создать объект
<__main__.Foo object at 0x89c6d4c>
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # создать экземпляр из класса
<__main__.MyShinyClass object at 0x8997cec>
# метаклассу автоматически передаются те же аргументы
# которые вы обычно передаете в `type`
def upper_attr(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()
}
# пусть `type` создаст класс
return type(future_class_name, future_class_parents, uppercase_attrs)
__metaclass__ = upper_attr # это повлияет на все классы в модуле
class Foo(): # __metaclass__ не будет работать с «объектом»
# но мы можем определить здесь __metaclass__, чтобы воздействовать только на этот класс
# и это будет работать с дочерними элементами «объекта»
bar = 'bip'
# помните, что `type` на самом деле является классом вроде `str` и `int`
# так что вы можете унаследовать от него
class UpperAttrMetaclass(type):
# __new__ - это метод, вызываемый до __init__
# это метод, который создает объект и возвращает его
# в то время как __init__ просто инициализирует объект, переданный как параметр
# вы редко используете __new__, за исключением случаев
# когда вы хотите контролировать, как объект создаётся
# здесь созданный объект - это класс, и мы хотим его настроить
# поэтому мы переопределяем __new__
# вы можете делать кое-что и в __init__, если хотите
# при продвинутом использовании нужно также переопределить __call__, но мы не будем делать это
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)
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)
就这样。元类中没有其他内容。
Причина сложности кода с использованием метаклассов заключается не в метаклассах, а в том, что вы обычно используете метаклассы для выполнения извращенных вещей, основанных на интроспекции, манипулировании наследованием, переменными, такими как __dict__ и т.д.
В самом деле, метаклассы особенно полезны для черной магии и, следовательно, для сложных вещей. Но сами по себе они просты:
Перехватить создание класса
Изменить класс
Вернуть измененный класс
Зачем для метаклассов использовать классы, а не функций?
Поскольку metaclass может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?
Для этого есть несколько причин:
Ясные намерение. Когда вы читаете UpperAttrMetaclass(type), вы знаете, что будет дальше
Вы можете использовать ООП. Метакласс может наследоватся от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
Подклассы класса будут экземплярами его метакласса, если вы укажите метакласс с помощью класса, а не с помощью функции.
Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно это для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезно для облегчения чтения кода.
Вы можете подключиться к __new__, __init__ и __call__. Это позволит вам делать разные вещи. Даже если обычно вы можете делать все это в __new__, некоторым людям просто удобнее использовать __init__.
Они называются метаклассами, черт возьми! Это должно что-то значить!
Зачем использовать метаклассы?
А теперь большой вопрос. Зачем использовать какую-то непонятную функцию, подверженную ошибкам?
Ну, обычно вы этого не делаете:
Метаклассы - это более глубокая магия, и 99% пользователей не должны беспокоиться об этом. Если вы задаетесь вопросом, нужны ли они вам, то нет (люди, которым они действительно нужны, чтобы точно знать, что они им нужны, и не нуждаются в объяснении почему).
Python гуру Tim Peters.
Основной вариант использования метакласса - создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде этого:
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, это делается путем небольшого мошенничества на уровне реализации.
Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете менять классы, используя два разных методы:
作为对象的类
在了解元类之前,您需要学习 Python 中的大师班。Python 对什么是类有一个非常奇特的想法,这是从 Smalltalk 语言中借来的。
在大多数语言中,类只是描述如何创建对象的代码片段。这在 Python 中也部分正确:
但在 Python 中,类远不止这些。类也是对象。
是的,对象。
一旦你使用关键字
class
,Python 就会执行它并创建一个对象。数据将由名为“ObjectCreator”的对象在内存中创建。这个对象(类)本身可以创建对象(实例),因此它是一个类。
但它仍然是一个对象,这意味着
例如:
动态类创建
因为类是对象,所以您可以像创建任何对象一样动态创建它们。
首先,您可以使用在函数中创建一个类
class
但这并不像您仍然必须自己编写整个课程那样动态。
因为类是对象,所以它们必须由某些东西创建。
当您使用关键字
class
时,Python 会自动创建此对象。但就像 Python 中的大多数事情一样,它允许您手动完成。还记得函数
type
吗?一个很好的旧函数,可让您找出对象的类型:好吧,它
type
有完全不同的可能性,它还可以动态创建类。type
可以将类描述作为参数并返回一个类(我知道,根据您传递给它的参数,同一个函数可以有两种完全不同的用途,这很愚蠢。这是 Python 中的向后兼容性问题)
type
像这样工作:在哪里:
name
: 班级名称base
:要继承的父类的元组(可能为空)attrs
:包含属性名称和值的字典例子:
可以像这样手动创建:
您会注意到我们使用“MyShinyClass”作为类的名称并作为一个变量来保存对类的引用。它们可能不同,但没有理由使事情复杂化。
type
需要一个字典来定义类属性可以变成:
并像普通课程一样使用:
当然你可以继承它:
或者
最终,您将希望向您的类添加方法。只需使用适当的签名定义一个函数并将其分配为属性。
在动态创建一个类之后,你可以添加更多的方法,就像给一个正常创建的类对象添加方法一样。
你知道我们要去哪里:在 Python 中,类是对象,你可以动态地动态创建类。
这就是 Python 在使用关键字
class
时所做的事情,它对元类也是如此什么是元类
元类是创建类的“东西”。
您正在定义类来创建对象,对吧?
但是我们已经了解到 Python 类是对象。
好吧,元类创建了这些对象。这些是类的类,你可以这样描述它们:
您看到它
type
允许您执行以下操作:这是因为该函数
type
实际上是一个元类。type
是 Python 用来在幕后创建所有类的元类。现在你想知道为什么这是小写而不是小写
Type
?好吧,我认为这是与
str
创建字符串对象int
的类以及创建整数对象的类的一致性问题。type
只是一个创建类对象的类。您将通过检查属性看到这一点
__class__
。一切,绝对一切,在 Python 中都是一个对象。这包括整数、字符串、函数和类。它们都是对象。它们都是从一个类创建的:
现在,什么是
__class__
任何人的__class__
?因此,元类只是创建类对象的东西。
如果你愿意,你可以称它为“类工厂”。
type
是 Python 使用的内置元类,当然您也可以创建自己的元类。元类属性
Python 2 中的元类
在 Python 2 中,您可以在编写类时添加属性
__metaclass__
(请参阅下一节了解 Python 3 语法):如果你这样做,Python 将使用元类来创建
Foo
.小心,这很难。
首先你写
class Foo(object)
,但类对象Foo
还没有在内存中创建。Python 将查看
__metaclass__
类定义。如果找到它,它将使用它来创建一个对象类Foo
。如果不是,它将type
用于创建类。多读几遍
当你这样做时:
Python 执行以下操作:
有
Foo
属性__metaclass__
吗?Foo
如果是这样,请使用 in 中的内容创建一个名为 的内存中类对象__metaclass__
。如果 Python 找不到
__metaclass__
它,它会查看__metaclass__
MODULE 级别并尝试做同样的事情(但仅适用于不继承任何东西的类,主要是旧式类)。然后,如果它根本找不到任何
__metaclass__
东西,它将使用它自己的元类Bar
(第一个父类,可能是默认的type
)来创建该类的对象。注意:属性
__metaclass__
不会被继承,但父元素的元类 (Bar.__class__
) 会。如果使用(而不是)创建Bar
的属性,子类将不会继承此行为__metaclass__
Bar
type()
type.__new__()
现在的主要问题是:可以添加
__metaclass__
什么?答案是一个类可以创造什么。
什么可以创建一个类?
type
或其子类使用的任何东西Python 3 中的元类
Python 3 中设置元类的语法发生了变化:
那些。该属性
__metaclass__
不再使用,但已变成父类列表中的参数然而,元类的行为基本保持不变。
在 Python 3 中添加到元类的一件事是,您还可以将属性作为关键字参数传递给元类,如下所示:
阅读以下部分以了解 Python 如何处理此问题。
自定义元类
元类的主要目的是在创建类时自动更改类。
您通常为要创建适合当前上下文的类的 API 执行此操作。
想象一个愚蠢的例子,你决定模块中所有类的属性都必须用大写。有几种方法可以做到这一点,但其中之一是
__metaclass__
在模块级别安装。因此,这个模块的所有类都将使用这个元类创建,我们只需要告诉元类将所有属性转换为大写即可。
幸运的是,
__metaclass__
它实际上可以是任何可调用的,它不必是正式的类(我知道名称中带有“类”的东西不一定是类,明白......这很有用)。因此,我们将从一个使用函数的简单示例开始。
让我们检查:
现在让我们做同样的事情,但是对元类使用一个真实的类:
让我们重写上面的内容,但使用更短更真实的变量名,现在我们知道它们的含义:
您可能已经注意到了额外的论点
cls
。这没有什么特别之处:__new__
它总是将定义它的类作为第一个参数。就像您self
对接收实例作为第一个参数的普通方法或类方法的定义类一样。但这是错误的 OOP。我们
type
直接调用,不覆盖或调用父级__new__
。我们可以使用更简洁
super
,这使得继承更容易(因为你可以拥有从元类继承的元类):就这样。元类中没有其他内容。
Причина сложности кода с использованием метаклассов заключается не в метаклассах, а в том, что вы обычно используете метаклассы для выполнения извращенных вещей, основанных на интроспекции, манипулировании наследованием, переменными, такими как
__dict__
и т.д.В самом деле, метаклассы особенно полезны для черной магии и, следовательно, для сложных вещей. Но сами по себе они просты:
Зачем для метаклассов использовать классы, а не функций?
Поскольку metaclass может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?
Для этого есть несколько причин:
Ясные намерение. Когда вы читаете
UpperAttrMetaclass(type)
, вы знаете, что будет дальшеВы можете использовать ООП. Метакласс может наследоватся от метакласса, переопределять родительские методы. Метаклассы могут даже использовать метаклассы.
Подклассы класса будут экземплярами его метакласса, если вы укажите метакласс с помощью класса, а не с помощью функции.
Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно это для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезно для облегчения чтения кода.
Вы можете подключиться к
__new__
,__init__
и__call__
. Это позволит вам делать разные вещи. Даже если обычно вы можете делать все это в__new__
, некоторым людям просто удобнее использовать__init__
.Они называются метаклассами, черт возьми! Это должно что-то значить!
Зачем использовать метаклассы?
А теперь большой вопрос. Зачем использовать какую-то непонятную функцию, подверженную ошибкам?
Ну, обычно вы этого не делаете:
Python гуру Tim Peters.
Основной вариант использования метакласса - создание API. Типичным примером этого является Django ORM. Это позволяет вам определить что-то вроде этого:
Но если вы сделаете это:
Он не вернет объект
IntegerField
. Он вернетint
и даже может взять его прямо из базы данных.Это возможно, потому что
models.Model
определяет__metaclass__
и использует некоторую магию, которая превратитPerson
, которую вы только что определили с помощью простых операторов, в сложный крючок для поля базы данных.Django делает что-то сложное простым, предоставляя простой API и используя метаклассы, воссоздавая код из этого API, чтобы делать реальную работу за кулисами.
Последнее слово
Во-первых, вы знаете, что классы - это объекты, которые могут создавать экземпляры.
На самом деле классы сами по себе являются экземплярами метаклассов.
В Python все является объектом, и все они являются экземплярами классов или экземплярами метаклассов.
Кроме
type
type
на самом деле является отдельным метаклассом. Это не то, что вы могли бы воспроизвести на чистом Python, это делается путем небольшого мошенничества на уровне реализации.Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете менять классы, используя два разных методы:
В 99% случаев, когда вам нужно изменить класс, вам лучше использовать их.
Но в 98% случаев вам вообще не нужно менять класс.
перевод ответа от участника @e-satis