[TimLinux] Python 再谈元类 metaclass

本博文通过对以下链接进行理解后,编写。

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python

1. 类

类也是对象,具有对象的特点:

  • 你可以将它赋值给变量
  • 你可以copy它
  • 你可以给它添加属性
  • 你可以把它作为函数参数进行传递

类定义语法:

class Foo(object): pass

说明:
1. 这是一种语法糖,一种对类对象的声明方式,与def func, a = 123, 本质上没有区别,都是为了创建一个对象。
2. 对象的创建都关联到一个类,比如 a = 123 关联的类是 'int', b = 'hello' 关联的类是 'str', def func(): pass 关联的类是 'function', 而 class Foo(object): pass 关联的类就是元类 'metaclass', 默认为 'type'

对象重要特性:动态创建,你可以随时随地在你需要的地方创建对象,类也可以动态创建。

2. 动态创建

根据函数参数来创建类,看起来像是类工厂,但是动态性不够。

def choose_class(name):
    if name == 'foo':
        class Foo(object): pass
        return Foo # return the class, not an instance
    else:
        class Bar(object): pass
        return Bar

>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

真正的动态创建,是通过元类(默认type类构造函数)来创建:

# 创建类
>>> 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

# 创建子类
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from 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

3. 动态创建语法糖

为此 python 语言本身又提供了一个语法糖,在声明类的时候,就指定后台自动创建这个类的方法,方法就是 __metaclass__ 变量(python2.7), metaclass=xxx(python3.x)。示例:

# 文件保存为:mymetaclass.py
# 执行 python2 metaclass.py

def upper_attr(name, parents, attributes):
    new_attributes = {}
    for n, v in attributes.items():
        if not n.startswith('__'):
            uppercase_attr[n.upper()] = v
        else:
            uppercase_attr[n] = v
    return type(name, parents, new_attributes)

__metaclass__ = upper_attr  # py3这个无法工作

class Foo(): # py3,需要使用这个语法:class Foo(metaclass=upper_attr):
    bar = 'bip'

    def __init__(self):
        self.link = "link"
    def func(self):
        print("Enter func")

# 类属性,函数都被转换为大写了
print(hasattr(Foo, 'bar'))  # False
print(hasattr(Foo, 'BAR'))  # True

print(hasattr(Foo, 'func'))  # False
print(hasattr(Foo, 'FUNC'))  # True

# 元类转换了类属性,但是没有转换实例属性, __init__ 内的变量没有被转换
print(hasattr(Foo, 'link'))  # False
print(hasattr(Foo, 'LINK'))  # False

print(hasattr(Foo(), 'link'))  # True
print(hasattr(Foo(), 'LINK'))  # False

 以上使用的是函数作为元类实现方式,也可以直接使用类作为元类,元类中实现的是 __new__ 方法,在生成类对象之前会被调用的方法即为 __new__ 方法。而不是 __init__ 方法, __init__ 方法是操作的类对象返回之后,创建的实例对象的初始化行为。

# metaclass 控制的是 __new__ 方法,而不是 __init__ 方法
class UpperAttrMetaclass(type):
    def __new__(cls, name, parents, attributes):
        new_attributes = {}
        for n, v in attributes.items():
            if not n.startswith('__'):
                new_attributes[n.upper()] = v
            else:
                new_attributes[n] = v
        #以下返回值推荐使用super方法,对当前示例两者效果相同,使用super可以使
        #当前元类能够继承其他元类
        #return type.__new__(cls, name, parents, new_attributes)
        #return super(UpperAttrMetaclass, cls).__new__(cls, name, parents, new_attributes)
        return type.__new__(cls, name, parents, new_attributes)

class Foo(object, metaclass=upper_attr):
    bar = 'bip'
    def __init__(self):
        self.link = "link"
    def func(self):
        print("Enter func")

# 效果与使用函数的元类实现一致!!!
# 类属性,函数都被转换为大写了
print(hasattr(Foo, 'bar'))  # False
print(hasattr(Foo, 'BAR'))  # True

print(hasattr(Foo, 'func'))  # False
print(hasattr(Foo, 'FUNC'))  # True

# 元类转换了类属性,但是没有转换实例属性, __init__ 内的变量没有被转换
print(hasattr(Foo, 'link'))  # False
print(hasattr(Foo, 'LINK'))  # False

print(hasattr(Foo(), 'link'))  # True
print(hasattr(Foo(), 'LINK'))  # False

4. 写作最后

99%用户用不到元类,所以大家洗洗睡吧,了解就行。

原文地址:https://www.cnblogs.com/timlinux/p/9698729.html