UP | HOME

Python 元类

目录

1 前言

元类Python 中是一个很强大也很有趣的功能, 强大在于你可以通过元类来控制 创建类 时的行为,有趣在于通过元类你可以体会到 类和实例 之间奇妙的关系。

2 type

Python 作为动态语言, 类型是在运行时创建和确定的, 默认情况下, 用于创建类的就是 type.

type(name, bases, dict) -> a new type

type 用于创建类时, 参数分别为:

  • name - 类名
  • bases - 基类元组
  • dict - 属性字典

举个栗子:

def __repr__(self):
    return 'The type of this instance is %s.' % self.__class__


TestClass = type('TestClass', (object,), {'__repr__': __repr__})

print(TestClass())

## output ##
# The type of this instance is <class '__main__.TestClass'>.

这个例子中, 创建了名为 TestClass 的类, 其基类为 object, 只定义了方法 __repr__.

等价于以下定义:

class TestClass(object):
    def __repr__(self):
        return 'The type of this instance is %s.' % self.__class__

在这种情况下, type 相当于一个 , 因此我们可以继承 type, 然后使用 type 的子类即 元类 来创建类。

此时, 我们需要关注元类的几个方法: __new__, __init__, __call__.

3 元类的定义

元类(metaclass) 属于 type 的子类, 而 属于 元类实例.

使用 元类 创建类, 就是创建 元类的实例.

和创建实例密切相关的两个方法为 __new____init__. 对于这两者的关系与使用可以查看博客: Python 构造对象实例.

这里来看它们在 元类 中的使用:

class MyMetaClass(type):
    def __new__(metaclass, name, bases, attrs):
        attrs['creator'] = metaclass.__name__
        return type.__new__(metaclass, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        cls.name = cls.creator + '.' + name

    def __call__(cls, *args, **kwargs):
        print('Create an instance with type %s.' % cls.name)
        obj = cls.__new__(cls, *args, **kwargs)
        obj.__init__(*args, **kwargs)
        return obj


class TestClass(object, metaclass=MyMetaClass):
    def __init__(self, name):
        self.name = name


print(TestClass('job').name)


## output ##
# Create an instance with type MyMetaClass.TestClass.
# job

对于 __new____init__ 其实没有什么好说的, __new__ 创建元类的实例 , __init__ 中设置创建的 的属性。

比较有趣的方法是元类中的 __call__ 方法。

当我们将一个实例当做函数使用时, 会调用方法 __call__. 而 元类 的实例是 , 什么时候我们会把 当做函数使用 ?

创建类的实例的时候.

因此, MyMetaClass.__call__ 会在创建 TestClass 的实例的时候调用, 因此我们会在上面那个例子中看到输出:

Create an instance with type MyMetaClass.TestClass.

创建实例的过程可以参考 Python 构造对象实例 中的代码:

def create_instance(obj_type, *args, **kwargs):
    obj = obj_type.__new__(*args, **kwargs)
    if obj is not None and issubclass(obj, obj_type):
        obj.__init__(*args, **kwargs)
    return obj

这个时候, 我们就得到了这样一条关系链条:

元类 --> 类 --> 类的实例

到了这个地方, 我觉得需要讨论一下 类属性实例属性 的关系。

4 类属性和实例属性

搞清楚 类属性实例属性 的关系之前, 需要明确的两点是:

  • Python 之中, 一切皆是对象
  • 获取属性设置属性 的行为不同
class OneClass(object):
    num = 10


A = OneClass()
B = OneClass()
print(A.num, B.num, OneClass.num)

A.num += 2
print(A.num, B.num, OneClass.num)

OneClass.num += 2
print(A.num, B.num, OneClass.num)

以上代码的执行结果为:

10 10 10
12 10 10
12 12 12

对此的理解:

  1. 是一个对象, 实例 也是一个对象
  2. 通过类似 self.xxx = xxx 的方式设置的属性直接 绑定实例对象
  3. 直接在 内部定义的属性 没有绑定实例, 而是 绑定 对象
  4. 访问一个 实例对象 不存在的 属性 的时候会尝试从 类对象 获去那个属性
  5. 设置 一个 实例对象 不存在的 属性 的值的时候, 会尝试从 类对象 获取属性的值,然后将运算后的结果 绑定实例 上, 使之成为 实例属性

这里需要明白的是: 元类和类, 类和实例, 都满足这一关系。

即: 获取 不存在的属性会尝试获取 元类 的属性。

class MyMetaClass(type):
    language = 'English'


class TestClass(object, metaclass=MyMetaClass):
    pass


print(TestClass.language)
print(TestClass().language)


## output ##
# English
# AttributeError: 'TestClass' object has no attribute 'language'

这个例子中, 获取 TestClass 的实例的 language 属性出错, 但获取 TestClasslanguage 属性没有问题。

所以, 获取 类的实例 不存在的属性不会传递到 元类.

5 元类的使用

元类的使用我也没有多少经验, 只能简单的列出一些自己尝试的结果。

  • __new__ 方法的参数

    一般情况下, 我们使用元类都是这样使用的:

    class TestClass(object, metaclass=MyMetaClass):
        pass
    

    这种情况下, 类的创建是隐式完成的, 参数应该和 type 相同, 否则会出错。

    另一种情况, 显示创建类时, 我们可以定义自己的参数:

    TestClass = MyMetaClass(...)
    

    这种情况下, 我们需要保证的是在 MyMetaClass__new__ 方法中保证使用 type.__new__ 创建类的参数正确。

    当然, 个人十分不推荐这种用法。

  • __init____new__ 参数的一致性

    创建类时, 得到元类的实例 后调用 __init__ 方法的过程我们无法插手, 因此需要保证 __init__ 的参数比 __new__ 具有更高的 包容性.

    最低限度是能够容纳所有的 name, basesattrs 这三个参数。

    这一点对于 类的实例 也是基本适用的, 特殊情况就是在 元类__call__ 方法中进行调整。

    当然, 我同样十分不推荐这种做法。

  • Python2Python3 的兼容

    Python2Python3 中定义元类的方式不同:

    # Python2
    class TestClass(object):
        __metaclass__ = MyMetaClass
    
    
    # Python3
    class TestClass(object, metaclass=MyMetaClass):
        pass
    

    如果需要兼容, 可以这样做:

    from six import with_metaclass
    
    class Meta(type):
        pass
    
    class Base(object):
        pass
    
    class MyClass(with_metaclass(Meta, Base)):
        pass
    

6 结语

Python 给了我们很大程度上的自由, 我们可以运用各种奇淫技巧写出各种神奇的代码。

但是, 我感觉有一句话还是很有道理的: 如非必要, 勿增实体.

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可