Py学习  »  Python

Python学习之路41-元类

VPointer • 5 年前 • 599 次点击  
阅读 9

Python学习之路41-元类

《流畅的Python》笔记。

本篇主要讨论Python中的元类。Python中所有的类都直接或间接地是元类type的实例。阅读本篇时,请时刻注意你所阅读的内容指的是**"实例"(或者说"对象")"类"还是"元类"**,否则很容易被绕晕。

1. 前言

Python中,几乎所有的东西都是对象。不光类的实例是对象,连类本身也是对象

不像C++、Java等静态语言,在编译前就必须将类定义好,运行时不能再创建新类,Python则可以在运行时动态创建新类,且不通过关键字class创建类的类叫做元类元类也是类,它可以派生出新的元类,但所有元类最顶层的超类只有一个,就是我们经常用到的type。Python中所有的类都直接或间接地是type实例

在运行时能通过元类动态创建类是Python的魅力,但想要理解这个"魅力"确并不是那么容易。本篇内容主要有:元类的简单示例,类装饰器,元类的原理、定义及使用方式,最后使用元类来弥补上一篇中描述符的不足。

本篇只能算是对元类的初步介绍,更深层次的内容还需进一步学习。

2. 初识元类

通常,如果要创建对象,需要先在某个模块中用class关键字定义好类,再在业务代码中创建这个类的实例。与这种事先定义的方式相反,可以通过type在运行时创建类,以下是它的示例:

>>> a = "this is a string"
>>> type(a)
<class 'str'>
>>> MyClass = type("MyClass", (object,), {"x": 1, "x2": lambda self: self.x * 2})
>>> mc = MyClass()
>>> mc.x
1
>>> mc.x2()
2                      # 请留意下方这三个特殊属性
>>> mc.__class__       # __class__的值是实例所属的类
<class 'MyClass'>
>>> MyClass.__bases__  # __bases__的值是类的所有直接超类
(<class 'object'>,)
>>> MyClass.__mro__    # __mro__的值是类的所有超类
(<class 'MyClass'>, <class 'object'>) 
>>> MyClass.__class__  # 这表明MyClass这个类是type的对象
<class 'type'>
复制代码

上述MyClass的定义等同于如下定义:

class MyClass(object):
    x = 1
    def x2(self):
        return self.x * 2
复制代码

type通常被当做函数使用,但它其实是一个类。当只传入一个实例时,它返回实例的类型;当传入3个参数时,它生成并返回一个类:

type(cls_name, bases, attr_dict)
复制代码

其中:

  • cls_name是要创建的类的名称的字符串;
  • bases是一个元组,它存储即将创建的类的直接父类们,比如MyClass继承自object(如果只继承自object,可以将bases设置为空元组);
  • attr_dict是新类的属性字典。不光包括数据属性,还包括了方法(含特殊方法)。不过,如果是数据属性,这些数据属性将成为类属性,而不是实例属性。如果想创建实例属性,请在attr_dict中传入__init__的定义,或者传入__dict__

为了更详细的介绍type的用法,我们用它来构造一个类工厂函数。

3. 类工厂函数

对于数据结构固定的数据,如果想将其包装成类对象,传统的做法是使用class定义每个类,比如为宠物应用定义各种动物类:

class Dog:
    def __init__(self, name, weight, owner):
        self.name = name
        self.weight = weight
        self.owner = owner
复制代码

不知道各位在敲这段代码时有没有抱怨:nameweightowner敲了三遍!如果再多几种动物类,这种样板代码得写多少?当然,对于相关的类可以选择继承。但如果数据间不相关呢?难道定义每个类的时候都将这种样板代码敲一遍?这个时候就可以用类工厂函数来减少这种样板代码。下方代码展示了type更具体的用法,生成的类比较简单,适合用于处理格式固定的数据。这个工厂函数其实是在模仿collections.namedtuple

def record_factory(cls_name, field_name):
    try:   # 假设传入的field_name是字符串,获取属性名
        field_names = field_name.replace(",", " ").split()
    except AttributeError:
        pass  # 如果不是字符串,则当做可迭代对象处理
    field_names = tuple(field_names)   # 将属性名存到元组中
    # __init__不作用于这个工厂函数!这是为要创建的类定义的构造方法
    def __init__(self, *args, **kwargs):        
        attrs = dict(zip(self.__slots__, args))
        attrs.update(kwargs)
        for name, value in attrs.items():
            setattr(self, name, value)

    def __iter__(self):   # 让即将创建的类可迭代
        for name in


    
 self.__slots__:
            yield getattr(self, name)

    def __repr__(self):   # 格式化输出
        values = ", ".join("{}={!r}".format(*i) for i in zip(self.__slots__, self))
        return "{}({})".format(self.__class__.__name__, values)
    # 类将拥有的属性
    cls_attrs = dict(__slots__=field_names, __init__=__init__,
                     __iter__=__iter__, __repr__=__repr__)
    return type(cls_name, (), cls_attrs)   # 继承自object
复制代码

下面是这个类工厂函数的用法:

>>> Dog = record_factory("Dog", "name weight owner")
>>> dog = Dog("test", 5, "Kevin")
>>> dog
Dog(name='test', weight=5, owner='Kevin')
>>> dog.weight = 6
>>> dog
Dog(name='test', weight=6, owner='Kevin')
>>> name, weight, owner = dog
>>> name, weight, owner
('test', 6, 'Kevin')
复制代码

下面我们将进一步了解元类。

4. 元类

面向对象的思想有两大关系:类的继承和类的实例化。在Python中,typeobject就像两个紧密合作的管理员,type主管实例化,object主管继承。

我们都知道,Python中所有的类都是从object继承而来的。但如果你看过下方的代码后,不知道对这一点的理解会不会动摇:

>>> type.__bases__
(<class 'object'>,)
>>> type.__class__
<class 'type'>
>>> object.__bases__
()
>>> object.__class__
<class 'type'>
复制代码

这段代码翻译成中文就是:objecttype的实例,typeobject的子类,因此type也是type自身的实例。这里完美地扯出了一个"先有蛋还是先有鸡"的问题:既然objecttype的实例,那就得先由type创建object;但type又是object的子类,也就是得先有object,再有type,所以到底是谁先有?

这个关系直到现在我也没搞清楚。如果是现实世界,可以说人类暂时还没搞清楚是先有鸡还是先有蛋,但Python这种编程语言可是人造的东西,况且底层还是C语言,所以我肯定不信什么"互为祖先,同时产生"这种说法,而且这在代码里不是死循环吗?查了很多资料,基本都是引用的这张图,其中虚线表示实例关系,实线表示继承关系:

但大家都回避了前面那个问题:objecttype实例化而来,可type又从object派生而来,到底谁先存在?

只能去看源码。源码中type确实继承自object,但object的定义中并没有metaclass关键字出现(后面会讲到,如果以class的形式从元类实例化类,需要使用这个关键字);并且,object中有这么一个定义:

__class__ = None # (!) forward: type, real value is ''
复制代码

这就让疑惑更深了:object究竟是不是type的实例?type中有如下定义:

    __bases__ = (
        object,
    )
    __base__ = object
    __mro__ = (
        None, # (!) forward: type, real value is ''
        object,
    )
复制代码

更深层的源代码暂时还啃不动。官方说明文档中说明了类的构建过程:所有的类,不管指没指明元类,都会经由type(),产生实际使用的类,这也验证了所有的类都是type的实例这一说法。

这两者的具体关系还有待继续研究。但我们毕竟不是语言专家,我们更看重的是元类怎么使用。对于这些概念,我们只需要知道:

  • 元类type可以创建类;
  • 所有的类都直接或间接的是type的实例;
  • type是自身的实例;
  • type可以被继承,用于创建新的元类。

4.1 类装饰器

在继续元类之前,我们先来解决上一篇属性描述符没有解决的问题:储存属性需要手动指定,而自动生成的名称所表达的意思又不够明显:

>>> Food.weight.storage_name
'_Quantity#0'
复制代码

这是上一篇文章中自动生成储存属性的名称时采用的策略,但我们更希望是下面这种形式:

>>> Food.weight.storage_name
'_Quantity#weight'
复制代码

上一篇中也说过,描述符类很难获取托管类的类属性名称的。使用类装饰器则能解决这个问题。类装饰器和函数装饰器非常相似,是参数为类对象的函数,返回原来的类或修改后的类。这里我们将它装饰到Food类上,而不是Quantity类上(FoodQuantity的具体定义请查看上一篇文章。以下代码不能直接运行,请自行导入所需的类):

@entity
class Food:   # 这个类比上一篇有所省略
    weight = Quantity()   # 并没有传入储存属性的名称
    def __init__(self, weight):
        self.weight = weight

def entity(cls):
    for key, attr in cls.__dict__.items():
        if isinstance(attr, Validated):      # 如果这个属性是Validated类的实例
            type_name = type(attr).__name__  # 则修改它的storage_name属性的值
            attr.storage_name = "_{}#{}".format(type_name, key)
    return cls
复制代码

其实实现的思路很简单:Quantity之所以无法获取Food类的属性名,是因为在Food中生成Quantity实例时,Food这个类都还没有创建完毕,自然只能手动传入。那等Food类创建完毕了再设置值不就行了?与函数装饰器类似,类装饰器会在Food生成后立即运行。

也可以用类装饰器来替换掉类中的某些方法。但类装饰器有一个重大缺点:只能对直接依附的类有效。这意味着,被装饰类的子类不一定继承装饰器所做的修改,具体情况视改动的方式而定。

小插曲:我看到这个概念的时候,无法理解为什么这被称之为"缺点":继承的时候子类重写了父类的同名方法,这不再正常不过吗?难道是不准让子类重写这个方法,要让这个方法在整个继承体系中都保持唯一?那不重写不就完了吗?如果整个项目就一个人做,当然能保证不重写这个方法。但往往软件开发是个团队项目,其他人并不一定清楚这个方法能不能被重写。

要保持方法在整个继承体系中保持唯一,不被子类所覆盖,这就得祭出元类。

4.2 使用元类

当使用到元类的时候,其实就是在定制化类及其子类的行为。下面我们使用元类替换掉前面的类装饰器:

# Validated和Quantity都在上一篇文章中,以下代码不能直接运行!
class EntityMeta(type):  # 在元类中,通常将self换成cls
    def __init__(cls, name, bases, attr_dict):  # 逻辑和类装饰器是一样的
        super().__init__(name, bases, attr_dict)# 这一步将name,bases,attr_dict绑定到了cls上
        for key, attr in attr_dict.items():
            if isinstance(attr, Validated):
                type_name = type(attr).__name__
                attr.storage_name = "_{}#{}".format(type_name, key)

class Entity(metaclass=EntityMeta):
    """带有验证字段的业务实体"""   # 什么都不用做,除非像添加新方法

class Food


    
(Entity):    # 对这个类进行了简化
    weight = Quantity()
    def __init__(self, weight):
        self.weight = weight
复制代码

请注意区分这些类的关系:

  • EntityMeta是元类type的子类,所以它也是个元类。
  • Entity使用class关键字来定义新的类,而不是调用type()函数来创建新的类;在定义Entity时,使用了metaclass关键字,表明这是元类EntityMeta的实例,而不是EntityMeta的子类,即,这不是继承关系。同时,Entity也(间接)是type的实例。
  • FoodEntity的子类,也是元类EntityMetatype的实例。

列出这些关系,是想提醒大家,如果要自行定义元类,请时刻注意,究竟谁是谁的子类,谁是谁的实例。下面来运行一下这段代码:

>>> Food.weight.storage_name
'_Quantity#weight'   # 行为符合预期
复制代码

这里又产生了3个问题。

第1个问题是:从EntityMeta__init__中可以看到,参数cls存的是元类的实例的引用,即类Entity或者类Food的引用,但整个初始化过程中,根本就没有用到cls,可结果表明,这些修改确实作用到了Food上,那么这是怎么作用Food上的呢?

EntityMeta.__init__()这个方法中的语句并不多,简答分析就能知道,问题出在super().__init__(),即type.__init__()上。但这个方法的具体实现我暂时也不知道,只能给出我的猜想:我们都知道,对于普通的类(例如Food)来说,它的对象(例如f)保存在内存中的某个位置a上,Food__init__操作内存a上的数据;而开篇就提到,所有的类也都是对象,于是类比一下,元类(例如EntityMeta)的实例(例如Food)肯定也存储在内存的某个位置b,那么type.__init__肯定将传入的参数关联到了内存b(实例Food)上。所以,这些操作最后在Food上生效了。平时对类的使用,其实就是用内存b中的数据创建内存a中的数据。

元类之所以难理解,难就难在我们并不习惯于"根据类来创建类"这种思路,我们习惯的是"根据类来创建实例"。这里也再次申明,没有"根据类来创建类"这种说法,一切都是"根据类来创建实例"!当涉及到元类时,就把元类看做平常使用的类,把元类生成的类看做平常使用的实例(或者说对象)。如果能这样想,下面两个问题也就能很好回答:

  • 两个__init__谁先运行呢?
  • 前面说到,在元类中定义的方法能影响到整个继承体系,即使子类重写这个方法也没有用,那这是怎么做到的呢?

要彻底回答这两个问题,就要涉及到运行时导入时的概念。

5. 运行时&导入时

为了正确地做元编程,必须知道Python解释器在什么时候运行各个代码块。Python程序员区分运行时导入时这两个概念,但其实这两个术语并没有严格定义,而且两者有交集。

在导入时,解释器会编译源文件,解释器会从上到下一次性解析完整个.py模块,然后生成用于执行的字节码,并将其存到.pyc文件中(这类文件在本地的__pycache__文件夹中)。所以,虽然Python是解释型语言,边解释边运行,但解释的不是.py源文件,而是.pyc中的字节码数据。

导入时除了编译,还会做些其他的事情。由于Python中的语句几乎都是可执行的,稍不注意,某些本该在运行时才运行的语句会在导入时就运行,导致用户程序的状态被修改。这里指的就是import语句:

  • 在Java中,import只用作声明,运行的时候才真正执行import后面的包中的代码。
  • 在Python中,import不仅仅是声明。模块首次被导入时,模块中所有的代码都会被运行并缓存,以后再导入相同的模块时直接使用缓存(只做名称绑定);所导入的模块中如果还有import,那么这些模块只要没被导入过,也会被运行一遍。这表明,运行时导入时产生了交集。

之前我在网上搜索这个概念的时候,很多博主都说,导入时会运行模块中所有的代码。其实并不是这样的,Python解释器确实会将模块从头执行到尾,但是:

  • 对于函数,解释器只执行完def关键字所在的行,它会编译函数的定义体,把函数对象绑定到对应的全局名称上,但显然函数定义体不会被执行。只有到运行时,解释器才通过全局名称找到函数定义体,再执行它。
  • 对于类,情况就不一样了。导入时,解释器会执行每个类的定义体,甚至会执行嵌套类的定义体。这么做的结果就是,定义了类的属性和方法(方法的定义体依然不会被执行),并构建了类这个对象。

绕了这么一大圈,终于和元类发生了关系!类这个对象在导入时就会被创建,所以,元类在导入时就会初始化它的实例:类。

为了更真切的体验这个过程,下面创建几个类,并观察解释器在导入时和运行时的行为。

下面的代码会用到类装饰器和元类,前面说到类装饰器在子类中不一定起作用,但元类一定起作用,请留意这两个的行为。

5.1 一般情况

这里指没有元类的情况。首先创建两个模块,代码可能有点长,但都很简单。注意这两个模块的名称,首先是evaltime.py

# evaltime.py
from evalsupport import deco_alpha

print('<[1]> evaltime module start')

class ClassOne:   # 它嵌套了一个类
    print('<[2]> ClassOne body')

    def __init__(self):
        print('<[3]> ClassOne.__init__')

    def __del__(self):
        print('<[4]> ClassOne.__del__')

    def method_x(self):
        print('<[5]> ClassOne.method_x')

    class ClassTwo(object):
        print('<[6]> ClassTwo body')

@deco_alpha
class ClassThree:  # 它被类装饰器装饰
    print('<[7]> ClassThree body')

    def method_y(self):  # 注意才场景2中观察这个方法的行为
        print('<[8]> ClassThree.method_y')

class ClassFour(ClassThree):   # 这里有一个继承,ClassThree被类装饰器装饰过
    print('<[9]> ClassFour body')

    def method_y(self):  # 注意才场景2中观察这个方法的行为
        print('<[10]> ClassFour.method_y')

if __name__ == '__main__':
    print('<[11]> ClassOne tests', 30 * '.')
    one = ClassOne()
    one.method_x()
    print('<[12]> ClassThree tests', 30 * '.')
    three = ClassThree()
    three.method_y()
    print('<[13]> ClassFour tests', 30 * '.')
    four = ClassFour()
    four.method_y()

print('<[14]> evaltime module end')
复制代码

接着是evalsupport.py

# evalsupport.py
print('<[100]> evalsupport module start')

def deco_alpha(cls):
    print('<[200]> deco_alpha')

    def inner_1(self):
        print('<[300]> deco_alpha:inner_1')

    cls.method_y = inner_1
    return cls

class MetaAleph(type):
    print('<[400]> MetaAleph body')

    def __init__(cls, name, bases, dic):
        print('<[500]> MetaAleph.__init__')

        def inner_2(self):
            print('<[600]> MetaAleph.__init__:inner_2')

        cls.method_z = inner_2    # 实例中的这个属性如果有,则会被替换
                                  # 如果没有,则新建这个属性并赋值为内嵌函数inner_2的引用
print('<[700]> evalsupport module end')
复制代码

上面这两个模块的代码中有<[N]>标记,N表示数字。现在请大家模拟以下两种场景,记录标记出现的顺序,最后再和真实结果比较。

场景1:在Python控制台中以交互的方式导入evaltime.py模块,即

>>> import evaltime.py
复制代码

场景2:在命令行中运行evaltime.py模块,即

$ python3 evaltime.py
复制代码

建议模拟完后再看下面的结果:

# 场景1
>>> import evaltime.py
<[100


    
]> evalsupport module start    # 运行evalsupport.py模块
<[400]> MetaAleph body              # MetaAleph的定义体运行了
<[700]> evalsupport module end      # 函数deco_alpha定义体在导入时并没有被执行!
<[1]> evaltime module start         # 开始运行evaltime.py模块
<[2]> ClassOne body                 # ClassOne的定义体被运行了,但其中的方法没有被运行
<[6]> ClassTwo body                 # 嵌套的ClassTwo的定义体也被运行了
<[7]> ClassThree body               # ClassThree的定义体被执行。
<[200]> deco_alpha    # 跳到了类装饰器中,函数定义体在导入时被执行了!证明导入时创建了类对象
<[9]> ClassFour body                # 类定义体被执行
<[14]> evaltime module end          # 模块执行完毕

# 场景2
$ python3 evaltime.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime module start
<[2]> ClassOne body
<[6]> ClassTwo body
<[7]> ClassThree body
<[200]> deco_alpha
<[9]> ClassFour body                # 在此行之前,和导入时没有区别,毕竟要执行得先导入嘛
<[11]> ClassOne tests ..............................   # 开始执行if中的内容了
<[3]> ClassOne.__init__             # 初始化ClassOne
<[5]> ClassOne.method_x             # 调用ClassOne的method_x方法
<[12]> ClassThree tests .............................. 
<[300]> deco_alpha:inner_1          # ClassThree的method_y被替换了
<[13]> ClassFour tests ..............................
<[10]> ClassFour.method_y           # 类装饰器在子类上不起作用
<[14]> evaltime module end          # 模块运行结束
<[4]> ClassOne.__del__              # ClassOne在被销毁时调用__del__方法
复制代码

不知大家的模拟是否和结果一致?

场景2中的结果证明了,类装饰器在子类中不一定起作用。

两个场景中,类装饰器在导入时都运行了一次,这证明了类对象在导入时创建,而不是在运行时创建。

5.2 加入元类

还剩下的两个问题将在这个例子中找到答案。不过,还得再创建一个模块evaltime_meta.py,并建议大家回顾一下MetaAleph的实现:

# evaltime_meta.py
from evalsupport import deco_alpha, MetaAleph

print('<[1]> evaltime_meta module start')

@deco_alpha
class ClassThree():  # 被类装饰器装饰
    print('<[2]> ClassThree body')

    def method_y(self):
        print('<[3]> ClassThree.method_y')

class ClassFour(ClassThree):
    print('<[4]> ClassFour body')

    def method_y(self):
        print('<[5]> ClassFour.method_y')

class ClassFive(metaclass=MetaAleph):   # 它是元类MetaAleph的实例!
    print('<[6]> ClassFive body')

    def __init__(self):
        print('<[7]> ClassFive.__init__')

    def method_z(self):
        print('<[8]> ClassFive.method_y')

class ClassSix(ClassFive):   # 它也是元类MetaAleph的实例!
    print('<[9]> ClassSix body')

    def method_z(self):
        print('<[10]> ClassSix.method_y')

if __name__ == '__main__':
    print('<[11]> ClassThree tests', 30 * '.')
    three = ClassThree()
    three.method_y()
    print('<[12]> ClassFour tests', 30 * '.')
    four = ClassFour()
    four.method_y()
    print('<[13]> ClassFive tests', 30 * '.')
    five = ClassFive()
    five.method_z()
    print('<[14]> ClassSix tests', 30 * '.')
    six = ClassSix()
    six.method_z()

print('<[15]> evaltime_meta module end')
复制代码

还是那两个场景:

场景1:在Python控制台中导入evaltime_meta.py

>>> import evaltime_meta.py
复制代码

场景2:在命令行中运行evaltime_meta.py

$ python3 evaltime_meta.py
复制代码

以下是两个场景的结果:

# 场景1
>>> import evaltime_meta.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body        # 到这里为止,和上一个场景1的情况一样
<[6]> ClassFive body        # 执行了ClassFive定义体
<[500]> MetaAleph.__init__  # 元类中的初始化方法在导入时被执行了!也证明导入时创建了类对象
<[9]> ClassSix body
<[500]> MetaAleph.__init__  # 再次触发元类中的初始化方法,
<[15]> evaltime_meta module end

# 场景2
$ python3 evaltime_meta.py
<[100]> evalsupport module start
<[400]> MetaAleph body
<[700]> evalsupport module end
<[1]> evaltime_meta module start
<[2]> ClassThree body
<[200]> deco_alpha
<[4]> ClassFour body
<[6]> ClassFive body
<[500]> MetaAleph.__init__
<[9]> ClassSix body
<[500]> MetaAleph.__init__   # 到此行位置,和场景1的情况一样
<[11]> ClassThree tests ..............................
<[300]> deco_alpha:inner_1   # 方法被类装饰器替换
<[12]> ClassFour tests ..............................
<[5]> ClassFour.method_y     # 类装饰器对子类不起作用
<[


    
13]> ClassFive tests ..............................
<[7]> ClassFive.__init__     # 初始化ClassFive的实例five
<[600]> MetaAleph.__init__:inner_2  # 方法被替换
<[14]> ClassSix tests ..............................
<[7]> ClassFive.__init__     # 初始化ClassFive的子类ClassSix的实例six
<[600]> MetaAleph.__init__:inner_2  # 子类的方法也被替换了!
<[15]> evaltime_meta module end
复制代码

这组例子再一次证明了类对象在导入时创建!并且元类对它的类对象的初始化也在导入时进行。其实,导入时对于元类来说就是它的运行时。

现在来回答之前留下的两个问题:

  • 元类只要有实例,元类的__init__方法就一定先于实例的__init__方法先执行。比较这两者的__init__方法有些牵强,毕竟类对象(例如ClassFive)在运行时创建,因此元类的__init__方法必定在导入时执行;而类实例在运行时才创建,类对象的__init__方法也就只能在运行时才执行。其实就是一个显而易见的逻辑:什么时候创建"实例",就什么时候执行"类"中的__init__方法咯。不过得清楚,这里的"实例"和"类"究竟指代的是谁。
  • 上一条解释其实已经回答了"元类为什么能覆盖所有子类的方法"。ClassFive是元类MetaAleph的实例,而不是继承自MetaAlephClassSix虽继承自ClassFive,但又不是继承自MetaAleph,它仅仅只是MetaAleph的又一个实例而已。这里说的覆盖对元类而言根本就不是覆盖,元类仅仅只是在为它的实例的属性赋值而已:你(ClassSix)只是我(MetaAleph)的实例,你继承自我的另一个实例(ClassFive),又不是继承自我,所以你跟我谈什么继承与覆盖?我只是在给你的属性赋值而已!

本文对元类的介绍到此结束。这些内容仅仅只是元类的皮毛。其中有很多地方依然没有弄懂,继续努力吧!

5.3 补充

其实如果想弥补本文中类装饰器的缺陷,可以不用定义元类,现在有更方便的方法:定义特殊方法__init_subclass__。它的作用和本文中的元类一样,但比创建元类简单直观得多。在创建子类时,子类都会被这个方法初始化。

6. 总结

本文首先展示了元类的基本用法:直接用type()函数创建类,然后将元类用到了类工厂函数中。之后加入了一个小插曲,类装饰器;接着深入介绍了元类的概念性知识,并展示了如何使用classmetaclass关键字从元类创建类。最后,介绍了运行时与导入时的概念,并通过代码展示了这两者的区别,尤其展示了类的创建时间。


迎大家关注我的微信公众号"代码港" & 个人网站 www.vpointer.net ~


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/hmXtGq75Gs
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/23179
 
599 次点击