• Python元类


    新式类

      新式类统一了类和类型,如果obj是新式类的实例,则type(obj)等同于obj.__class__

    >>> class Foo:
    ...     pass
    >>> obj = Foo()
    >>> obj.__class__
    <class '__main__.Foo'>
    >>> type(obj)
    <class '__main__.Foo'>
    >>> obj.__class__ is type(obj)
    True
    >>> n = 5
    >>> d = { 'x' : 1, 'y' : 2 }
    
    >>> class Foo:
    ...     pass
    ...
    >>> x = Foo()
    
    >>> for obj in (n, d, x):
    ...     print(type(obj) is obj.__class__)
    ...
    True
    True
    True

    Type and Class

    在Python 3中,所有类都是新式类。因此,在Python 3中,可以互换地引用对象的类型及其类是合理的。

    注意:在Python 2中,默认情况下类是旧式的。在Python 2.2之前,根本不支持新式类。从Python 2.2开始,它们可以创建,但必须显式声明为new-style。

    请记住,在Python中,一切都是对象。类也是对象。因此,类必须具有类型。class的类型是什么?

    考虑以下:

    >>> class Foo:
    ...     pass
    ...
    >>> x = Foo()
    
    >>> type(x)
    <class '__main__.Foo'>
    
    >>> type(Foo)
    <class 'type'

    该类型(type)x是class类Foo,如你所愿。但是Foo,类本身的类型type通常,任何新式类的类型都是type

    您熟悉的内置类的类型还包括type

    >>> for t in int, float, dict, list, tuple:
    ...     print(type(t))
    ...
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>
    <class 'type'>

    对于这个问题,类型typetype也(是对的):

    >>> type (type )
    <class'type'>

    type是一个元类,其中的类是实例。就像一个普通的对象是一个类的实例一样,Python中的任何新式类,以及Python 3中的任何类,都是type元类的一个实例

    在上述情况中:

    • x是一个类的实例Foo
    • Footype元类的一个实例
    • type也是type元类的一个实例,因此它本身就是一个实例。

                        

    动态定义类

    type()当传递一个参数时,内置函数返回一个对象的类型。对于新式类,通常与对象的__class__属性相同

    >>> type(3)
    <class 'int'>
    
    >>> type(['foo', 'bar', 'baz'])
    <class 'list'>
    
    >>> t = (1, 2, 3, 4, 5)
    >>> type(t)
    <class 'tuple'>
    
    >>> class Foo:
    ...     pass
    ...
    >>> type(Foo())
    <class '__main__.Foo'>

    你也可以type()用三个参数调用 - type(<name>, <bases>, <dct>)

    • <name>指定类名。这成为__name__属性。
    • <bases>指定类继承的基类的元组。这成为__bases__属性。
    • <dct>指定包含类主体定义的命名空间字典。这成为__dict__属性。

    type()以这种方式调用会创建type元类的新实例换句话说,它动态创建一个新类。

    在以下每个示例中,顶部代码段动态定义了一个类type(),而下面的代码段使用该class语句通常的方式定义了类在每种情况下,这两个片段在功能上是等效的。

    例1

    在第一个例子中,<bases><dct>通过参数type()都是空的。没有指定任何父类的继承,并且最初没有任何内容放在命名空间字典中。这是最简单的类定义:

      

    >>> Foo = type('Foo', (), {})
    
    >>> x = Foo()
    >>> x
    <__main__.Foo object at 0x04CFAD50>
    >>> class Foo:
    ...     pass
    ...
    >>> x = Foo()
    >>> x
    <__main__.Foo object at 0x0370AD50>

    例2

    <bases>是一个带有单个元素的元组Foo,指定Bar从中继承的父类属性,, attr最初放在命名空间字典中:

    >>> Bar = type('Bar', (Foo,), dict(attr=100))
    
    >>> x = Bar()
    >>> x.attr
    100
    >>> x.__class__
    <class '__main__.Bar'>
    >>> x.__class__.__bases__
    (<class '__main__.Foo'>,)
    >>> class Bar(Foo):
    ...     attr = 100
    ...
    
    >>> x = Bar()
    >>> x.attr
    100
    >>> x.__class__
    <class '__main__.Bar'>
    >>> x.__class__.__bases__
    (<class '__main__.Foo'>,)

    例3

    这一次,又<bases>是空的。通过<dct>参数将两个对象放入命名空间字典中第一个是名为的属性attr,第二个是名为的函数attr_val,它成为已定义类的方法:

    >>> Foo = type(
    ...     'Foo',
    ...     (),
    ...     {
    ...         'attr': 100,
    ...         'attr_val': lambda x : x.attr
    ...     }
    ... )
    
    >>> x = Foo()
    >>> x.attr
    100
    >>> x.attr_val()
    100
    >>> class Foo:
    ...     attr = 100
    ...     def attr_val(self):
    ...         return self.attr
    ...
    
    >>> x = Foo()
    >>> x.attr
    100
    >>> x.attr_val()
    100

    例4

    lambda在Python中只能定义非常简单的函数在下面的示例中,外部定义稍微复杂的函数,然后attr_val通过名称在名称空间字典中分配f

    >>> def f(obj):
    ...     print('attr =', obj.attr)
    ...
    >>> Foo = type(
    ...     'Foo',
    ...     (),
    ...     {
    ...         'attr': 100,
    ...         'attr_val': f
    ...     }
    ... )
    
    >>> x = Foo()
    >>> x.attr
    100
    >>> x.attr_val()
    attr = 100
    >>> def f(obj):
    ...     print('attr =', obj.attr)
    ...
    >>> class Foo:
    ...     attr = 100
    ...     attr_val = f
    ...
    
    >>> x = Foo()
    >>> x.attr
    100
    >>> x.attr_val()
    attr = 100

    自定义元类

    再次考虑这个陈旧的例子:

    >>> class Foo:
    ...     pass
    ...
    >>> f = Foo()

    该表达式Foo()创建了一个新的类实例Foo解释器遇到时Foo(),会发生以下情况:

    • 调用Foo的父类方法__call__()由于Foo是标准的新式类,它的父类是type元类,这样type__call__()方法被调用。

    • __call__()方法又调用以下内容:

      • __new__()
      • __init__()

    如果Foo没有定义__new__()__init__(),默认的方法是继承Foo的祖先。但是如果Foo定义了这些方法,它们会覆盖来自祖先的方法,这允许在实例化时进行自定义行为Foo

    在下面,定义了一个自定义方法,并将new()其指定为以下__new__()方法Foo

    >>> def new(cls):
    ...     x = object.__new__(cls)
    ...     x.attr = 100
    ...     return x
    ...
    >>> Foo.__new__ = new
    
    >>> f = Foo()
    >>> f.attr
    100
    
    >>> g = Foo()
    >>> g.attr
    100

    这会修改类的实例化行为Foo:每次Foo创建一个实例时,默认情况下会使用一个名为的`attr`属性对其进行初始化,该属性attr的值为100(这样的代码通常会出现在__init__()方法中,而不是典型的__new__()。这个例子是出于演示目的而设计的。)

    现在,正如已经重申的那样,类也是对象。假设您在创建类时喜欢类似地自定义实例化行为Foo如果您要遵循上面的模式,您将再次定义一个自定义方法,并将其指定__new__()为类Foo的实例的方法。Footype元类的一个实例,所以代码看起来像这样:

    # Spoiler alert:  This doesn't work!
    >>> def new(cls):
    ...     x = type.__new__(cls)
    ...     x.attr = 100
    ...     return x
    ...
    >>> type.__new__ = new
    Traceback (most recent call last):
      File "<pyshell#77>", line 1, in <module>
        type.__new__ = new
    TypeError: can't set attributes of built-in/extension type 'type'

    除此之外,正如您所看到的,您无法重新分配type元类__new__()方法Python不允许。

    这可能也是一样。type是从中派生所有新样式类的元类。无论如何,你真的不应该乱用它。但是,如果你想自定义一个类的实例化,那么还有什么办法呢?

    一种可能的解决方案是自定义元类。从本质上讲,type您可以定义自己的元类,而不是使用元类,但您可以type使用它来进行修改。

    第一步是定义一个元类,派生自type,如下:

    >>> class Meta(type):
    ...     def __new__(cls, name, bases, dct):
    ...         x = super().__new__(cls, name, bases, dct)
    ...         x.attr = 100
    ...         return x
    ...

    定义标头class Meta(type):指定Meta派生自type既然type是元类,那也是一个Meta元类。

    请注意,__new__()已定义自定义方法Metatype直接元类进行这样的操作是不可能的__new__()方法执行以下操作:

    • 经由代表super()__new__()父元类的方法(type)实际创建一个新的类
    • 将自定义属性分配attr给类,值为100
    • 返回新创建的类

    现在是另一半:定义一个新类Foo并指定其元类是自定义元类Meta,而不是标准元类type这是使用metaclass类定义中关键字完成的,如下所示:

    >>> class Foo(metaclass=Meta):
    ...     pass
    ...
    >>> Foo.attr
    100

    瞧! Foo已经自动从Meta元类获取属性attr了当然,您定义的任何其他类也会这样做:

    >>> class Bar(metaclass=Meta):
    ...     pass
    ...
    >>> class Qux(metaclass=Meta):
    ...     pass
    ...
    >>> Bar.attr, Qux.attr
    (100, 100)

    与类作为创建对象的模板的方式相同,元类用作创建类的模板。元类有时被称为类工厂

    比较以下两个例子:

    对象工厂

    >>> class Foo:
    ...     def __init__(self):
    ...         self.attr = 100
    ...
    
    >>> x = Foo()
    >>> x.attr
    100
    
    >>> y = Foo()
    >>> y.attr
    100
    
    >>> z = Foo()
    >>> z.attr
    100

    类工厂:

    >>> class Meta(type):
    ...     def __init__(
    ...         cls, name, bases, dct
    ...     ):
    ...         cls.attr = 100
    ...
    >>> class X(metaclass=Meta):
    ...     pass
    ...
    >>> X.attr
    100
    
    >>> class Y(metaclass=Meta):
    ...     pass
    ...
    >>> Y.attr
    100
    
    >>> class Z(metaclass=Meta):
    ...     pass
    ...
    >>> Z.attr
    100

    这真的有必要吗?

    就像上面的类工厂示例一样简单,它是元类工作方式的本质。它们允许自定义类实例化。

    尽管如此,为了attr在每个新创建的类上赋予自定义属性,这仍然是一件大惊小怪的事情你真的需要一个元类吗?

    在Python中,至少有几种方法可以有效地完成同样的事情:

    简单继承

    >>> class Base:
    ...     attr = 100
    ...
    
    >>> class X(Base):
    ...     pass
    ...
    
    >>> class Y(Base):
    ...     pass
    ...
    
    >>> class Z(Base):
    ...     pass
    ...
    
    >>> X.attr
    100
    >>> Y.attr
    100
    >>> Z.attr
    100

     类装饰器

    >>> def decorator(cls):
    ...     class NewClass(cls):
    ...         attr = 100
    ...     return NewClass
    ...
    >>> @decorator
    ... class X:
    ...     pass
    ...
    >>> @decorator
    ... class Y:
    ...     pass
    ...
    >>> @decorator
    ... class Z:
    ...     pass
    ...
    
    >>> X.attr
    100
    >>> Y.attr
    100
    >>> Z.attr
    100

    结论

    元类很容易转向成为“寻找问题的解决方案”的领域。通常不需要创建自定义元类。如果手头的问题可以用更简单的方式解决,那么它应该是。尽管如此,理解元类是有益的,这样你就可以理解Python类,并且可以识别元类真正适合使用的工具。

  • 相关阅读:
    tool公用工具方法
    angular5.x 拦截器 switchMap
    angular5.x拦截器 给get post请求添加参数user_token
    flex布局 阮一峰
    json.stringify()和json.parse()
    年份月数天数
    打印字母塔
    打印形状
    打印九九乘法表
    C语言猜数字游戏
  • 原文地址:https://www.cnblogs.com/tcppdu/p/10076715.html
Copyright © 2020-2023  润新知