[译]什么是元类metaclass?

原文地址:what is metaclass in Python?
我的简书地址::nummy

类即对象

在理解元类之前,需要先掌握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) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
... 
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

既然类就是对象,那我们就可以像创建其他对象一样动态创建类。
首先,在函数中使用class创建一个类:

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

但是上面的例子也称不上是完全动态的创建类,因为我们还需要在其中编写整个类的代码。
既然类就是对象,那么它们肯定是通过某个东西来创建的。当使用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使用类的相关描述作为参数,然后返回一个类。
type创建类的语法如下:

type(类名,基类元组(可以为空,用于继承), 包含属性或函数的字典)

例如:

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

上面的类可以使用下面的方法手动创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

type也接收一个字典参数来定义类中的属性:

>>> class Foo(object):
...       bar = True

等价于

>>> Foo = type('Foo', (), {'bar':True})

通过type创建的类使用方式跟普通类一样:

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

什么是元类?

通常,我们定义类来创建对象,但是现在我们知道类也是对象。那么是通过什么来创建类呢?答案就是元类。你可以想象关系如下:

MyClass = MetaClass()
MyObject = MyClass()

你已经知道使用type可以创建类:

MyClass = type('MyClass', (), {})

那是因为type函数实际上就是一个元类,Python使用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默认使用它来创建类。当然,我们也可以定义属于我们自己的元类。

metaclass属性

当我们创建类的时候,可以给它添加metaclass属性:

class Foo(object):
  __metaclass__ = something...
  [...]

如果我们定义了metaclass属性,Python就会使用这个元类来创建类Foo。
注意,编译器首先读取class Foo(object),这时并不会在内存中创建Foo类。Python会继续查找类定义中的__meatclass__,如果找到了,就使用它来创建类Foo,如果没有找到,就使用type来创建类。
所以对于以下代码:

class Foo(Bar):
  pass

Python工作流程如下:

  • 首先检查Foo中是否具有属性__metaclass__

  • 如果找到,就使用__metaclass__定义的元类在内存中创建一个类对象。

  • 如果在类定义中没有找到这个属性,就在模块级别中进行查找。

  • 如果还是没有找到,就会使用父类Bar中的元类来创建类。

注意:类中的__metaclass__属性不会被子类继承,但是父类中的__class__会被继承。

自定义元类

元类的主要作用是在创建类的时候自动改变类。
例如,想要实现模块中所有的类属性都是大写格式。可以定义模块级别的__metaclass__来实现。
这样模块中所有的类都是通过这个元类来创建的。

def upper_attr(future_class_name, future_class_parents, future_class_attr):
  """
    返回一个类,该类的所有属性名的都为大写
  """
  # 将不是__开头的属性名转为大写字母
  uppercase_attr = {}
  for name, val in future_class_attr.items():
      if not name.startswith('__'):
          uppercase_attr[name.upper()] = val
      else:
          uppercase_attr[name] = val
  # 使用type创建类
  return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 定义模块级别的元类,这样模块中所有类都会使用该元类创建

class Foo(): 
  # 注意,新式类不支持模块级别的元类,但是可以在类中定义__metaclass__
  bar = 'bip'

print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出: True

f = Foo()
print(f.BAR)
# Out: 'bip'

也可以将metaclass定义为一个真正的类:

# 记住type还是一个类,所以可以继承它
class UpperAttrMetaclass(type): 
    # __new__ 会在__init__之前调用,它会创建并返回一个实例
    # 而__init__仅用于初始化,进行一些参数的配置 
    def __new__(upperattr_metaclass, future_class_name, 
                future_class_parents, future_class_attr):
        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

但是上面的做法并不符合OOP的思想,因为它直接调用了type方法,实际上可以调用type__new__方法。

class UpperAttrMetaclass(type): 
    def __new__(upperattr_metaclass, future_class_name, 
                future_class_parents, future_class_attr):
        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        # 调用type.__new__方法 
        return type.__new__(upperattr_metaclass, future_class_name, 
                            future_class_parents, uppercase_attr)

你可能注意到参数upperattr_metaclass, 它代表要实例化的类。当然,我这里取这么个复杂的名字主要是为了明确它的含义。但是,就像self参数一样,所有参数都有其习惯性命名。所以生产环境下的metaclass定义如下:

class UpperAttrMetaclass(type): 
    def __new__(cls, clsname, bases, dct):
        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        return type.__new__(cls, clsname, bases, uppercase_attr)

更好的方式是使用super方法,以便减轻这种继承关系。

class UpperAttrMetaclass(type): 
    def __new__(cls, clsname, bases, dct):
        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

元类实际上做了以下三方面的工作:

  • 干涉创建类的过程

  • 修改类

  • 返回修改之后的类

为什么使用类而不是函数来定义元类?

理由如下:

  • 目的更明确,当你阅读UpperAttrMetaclass(type)的时候,你知道它用来做什么。

  • 可以使用面向对象编程,元类可以继承自其它元类,还可以覆盖父类方法。

  • 可以更好的组织代码结构。元类通常用于处理比较复杂的情况。

  • 可以为__new____init____call__编写钩子,为后续开发者提供便利。

为什么使用元类?

现在,终极问题来了,为什么要使用元类这种模糊且容易出错的功能?
一般情况下,我们并不会使用元类,99%的开发者并不会用到元类,所以一般不用考虑这个问题。
元类主用用于创建API,一个典型的例子就是Django的ORM。
它让我们可以这样定义一个类:

class Person(models.Model):
  name = models.CharField(max_length=30)
  age = models.IntegerField()

运行下面的代码:

guy = Person(name='bob', age='35')
print(guy.age)

返回的结果是int类型而不是IntegerField对象。这是因为models.Model使用了元类,它会将Python中定义的字段转换成数据库中的字段。
通过使用元类,Django将复杂的接口转换成简单的接口。

总结

首先,我们知道了类其实就是可以创建实例的对象。而类又是通过元类来创建的。

>>> class Foo(object): pass
>>> id(Foo)
142630324

Python中所有数据类型都是对象,它们要么是类的实例要么是元类的实例。
除了type,它实际上是自身的元类。这一点没法在Python中重现,因为它是在编译阶段实现的。

其次, 元类都是复杂的,对于一般的类是用不着的。可以使用以下两种技巧修改类:

  • monkey patch

  • 类修饰器

当你需要修改类的时候,99%的情况下可以使用元类。但是99%的情况下,你根本不需要修改一个类。


nummy的专栏
Python or Javascript
434 声望
9 粉丝
0 条评论
推荐阅读
Uninformed search Python实现【译】
图的搜索可以分为uninformed搜索和informed搜索,两者的区别是前者是的搜索是盲目的,它不知道目标节点在哪,而后者是启发式的搜索。

fireflow1阅读 3.7k

Ubuntu20.04 从源代码编译安装 python3.10
Ubuntu 22.04 Release DateUbuntu 22.04 Jammy Jellyfish is scheduled for release on April 21, 2022If you’re ready to use Ubuntu 22.04 Jammy Jellyfish, you can either upgrade your current Ubuntu syste...

ponponon1阅读 4k评论 1

日常Python 代码片段整理
1、简单的 HTTP Web 服务器 {代码...} 2、单行循环List {代码...} 3、更新字典 {代码...} 4、拆分多行字符串 {代码...} 5、跟踪列表中元素的频率 {代码...} 6、不使用 Pandas 读取 CSV 文件 {代码...} 7、将列表...

墨城2阅读 354

Python + Sqlalchemy 对数据库的批量插入或更新(Upsert)
由于不同数据库对这种 upsert 的实现机制不同,Sqlalchemy 也就不再试图做一致性的封装了,而是提供了各自的方言 API,具体到 Mysql,就是给 insert statement ,增加了 on_duplicate_key_update 方法。

songofhawk1阅读 2.1k评论 4

封面图
Unicode 正则表达式(qbit)
前言本文根据《精通正则表达式》和 Unicode Regular Expressions 整理。本文的示例默认以 Python3 为实现语言,用到 Python3 的 re 模块或 regex 库。基本的 Unicode 属性分类 {代码...} 基本的 Unicode 子属性Le...

qbit阅读 4.4k

打脸了兄弟们,Go1.20 arena 来了!
大家好,我是煎鱼。大概半年前,我写过一篇文章《Go 要违背初心吗?新提案:手动管理内存》。有兴趣了深入解的同学,可以再回顾一下。当时我们还想着 Go 团队应该不会接纳,至少不会那么快:懒得翻也可以看我再次...

煎鱼1阅读 3.3k

uwsgi 注意事项
http 和 http-socket 选项是完全不同的。第一个生成一个额外的进程,转发请求到一系列的worker (将它想象为一种形式的盾牌,与apache或者nginx同级),而第二个设置worker为原生使用http协议。

zed2015阅读 2.2k

434 声望
9 粉丝
宣传栏