RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1208241
Accepted
Danis
Danis
Asked:2021-11-22 22:56:23 +0000 UTC2021-11-22 22:56:23 +0000 UTC 2021-11-22 22:56:23 +0000 UTC

Python 中的元类是什么?

  • 772

Python 中的元类是什么,为什么要使用它们?

问题的翻译Python 中的元类是什么?成员@e-satis

python
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Danis
    2021-11-22T22:56:23Z2021-11-22T22:56:23Z

    作为对象的类

    在了解元类之前,您需要学习 Python 中的大师班。Python 对什么是类有一个非常奇特的想法,这是从 Smalltalk 语言中借来的。

    在大多数语言中,类只是描述如何创建对象的代码片段。这在 Python 中也部分正确:

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

    但在 Python 中,类远不止这些。类也是对象。

    是的,对象。

    一旦你使用关键字class,Python 就会执行它并创建一个对象。数据将由名为“ObjectCreator”的对象在内存中创建。

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

    这个对象(类)本身可以创建对象(实例),因此它是一个类。

    但它仍然是一个对象,这意味着

    • 您可以将其分配给变量
    • 你可以复制它
    • 您可以为其添加属性
    • 您可以将其作为函数参数传递

    例如:

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

    但这并不像您仍然必须自己编写整个课程那样动态。

    因为类是对象,所以它们必须由某些东西创建。

    当您使用关键字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: 班级名称
    • base:要继承的父类的元组(可能为空)
    • attrs:包含属性名称和值的字典

    例子:

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

    可以像这样手动创建:

    >>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
    >>> print(MyShinyClass)
    <class '__main__.MyShinyClass'>
    >>> print(MyShinyClass()) # создать экземпляр из класса
    <__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 унаследован от 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 中都是一个对象。这包括整数、字符串、函数和类。它们都是对象。它们都是从一个类创建的:

    >>> 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 使用的内置元类,当然您也可以创建自己的元类。

    元类属性

    Python 2 中的元类

    在 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 执行以下操作:

    有Foo属性__metaclass__吗?

    Foo如果是这样,请使用 in 中的内容创建一个名为 的内存中类对象__metaclass__。

    如果 Python 找不到__metaclass__它,它会查看__metaclass__MODULE 级别并尝试做同样的事情(但仅适用于不继承任何东西的类,主要是旧式类)。

    然后,如果它根本找不到任何__metaclass__东西,它将使用它自己的元类Bar(第一个父类,可能是默认的type)来创建该类的对象。

    注意:属性__metaclass__不会被继承,但父元素的元类 ( Bar.__class__) 会。如果使用(而不是)创建Bar的属性,子类将不会继承此行为__metaclass__Bartype()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__它实际上可以是任何可调用的,它不必是正式的类(我知道名称中带有“类”的东西不一定是类,明白......这很有用)。

    因此,我们将从一个使用函数的简单示例开始。

    # метаклассу автоматически передаются те же аргументы
    # которые вы обычно передаете в `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'
    

    让我们检查:

    >>> hasattr(Foo, 'bar')
    False
    >>> hasattr(Foo, 'BAR')
    True
    >>> Foo.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) 
    

    您可能已经注意到了额外的论点cls。这没有什么特别之处:__new__它总是将定义它的类作为第一个参数。就像您self对接收实例作为第一个参数的普通方法或类方法的定义类一样。

    但这是错误的 OOP。我们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)
    

    就这样。元类中没有其他内容。

    Причина сложности кода с использованием метаклассов заключается не в метаклассах, а в том, что вы обычно используете метаклассы для выполнения извращенных вещей, основанных на интроспекции, манипулировании наследованием, переменными, такими как __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, это делается путем небольшого мошенничества на уровне реализации.

    Во-вторых, метаклассы сложны. Возможно, вы не захотите использовать их для очень простых изменений класса. Вы можете менять классы, используя два разных методы:

    • monkey patching
    • Декораторы классов

    В 99% случаев, когда вам нужно изменить класс, вам лучше использовать их.

    Но в 98% случаев вам вообще не нужно менять класс.

    перевод ответа от участника @e-satis

    • 5

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5