资讯专栏INFORMATION COLUMN

[译]什么是元类metaclass?

zsirfs / 1177人阅读

摘要:如果还是没有找到,就会使用父类中的元类来创建类。元类通常用于处理比较复杂的情况。这是因为使用了元类,它会将中定义的字段转换成数据库中的字段。中所有数据类型都是对象,它们要么是类的实例要么是元类的实例。

原文地址: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

>>> def echo(o):
...       print(o)
... 
>>> echo(ObjectCreator) # you can pass a class as a parameter

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

>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是上面的例子也称不上是完全动态的创建类,因为我们还需要在其中编写整个类的代码。
既然类就是对象,那么它们肯定是通过某个东西来创建的。当使用class关键字的时候,Python会自动创建类,Python也提供了方法让我们手动来创建类。

还记得type()函数吗?这个函数可以获取对象的类型。

>>> print(type(1))

>>> print(type("1"))

>>> print(type(ObjectCreator))

>>> print(type(ObjectCreator()))

type还有另外一个功能,那就是创建类。type使用类的相关描述作为参数,然后返回一个类。
type创建类的语法如下:

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

例如:

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

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

>>> MyShinyClass = type("MyShinyClass", (), {}) # returns a class object
>>> print(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)

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

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

>>> name = "bob"
>>> name.__class__

>>> def foo(): pass
>>> foo.__class__

>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__

那么__class____class__是什么呢?

>>> age.__class__.__class__

>>> name.__class__.__class__

>>> foo.__class__.__class__

>>> b.__class__.__class__

所以类其实就是通过元类来创建的,你可以将元类称之为类工厂。
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%的情况下,你根本不需要修改一个类。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38050.html

相关文章

  • 再有人问什么元类,就把这篇文章扔给他!

    摘要:同时,在元类中,我们还需要加上一个判断,只有在这个类创建时才需要控制其类的生成,其他的就不需要了。完整代码后台回复元类获取原创不易,如果文章对你有用的话,点赞留言转发是对我的最大支持日常学代码不止,还有美和乐趣 我之前在深入理解python中的类和对象中说过,python中的类也是一个对象,可以说是类对象,可以由type来创建类对象的。有了这个知识我们先看看下面这个函数: showIm...

    王岩威 评论0 收藏0
  • python 类和元类(metaclass)的理解和简单运用

    摘要:什么是元类刚才说了,元类就是创建类的类。类上面的属性,相信愿意了解元类细节的盆友,都肯定见过这个东西,而且为之好奇。使用了这个魔法方法就意味着就会用指定的元类来创建类了。深刻理解中的元类 (一) python中的类 今天看到一篇好文,然后结合自己的情况总结一波。这里讨论的python类,都基于python2.7x以及继承于object的新式类进行讨论。 首先在python中,所有东西都...

    zhangqh 评论0 收藏0
  • Python -- 元类metaclass详解

    摘要:原链接中的元类是什么类也是对象在理解元类之前,需要掌握中类概念。事实上,是中用于创建所有类的元类。类本身是元类的对象在中,除了,一切皆对象,一切都是类或者元类的对象。事实上是自己的元类, 学习契机 项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_me...

    tracy 评论0 收藏0
  • Python: 陌生的 metaclass

    摘要:但一般情况下,我们使用类作为元类。那么,元类到底有什么用呢要你何用元类的主要目的是为了控制类的创建行为。当然,有很多种做法,这里展示用元类的做法。当你创建类时,解释器会调用元类来生成它,定义一个继承自的普通类意味着调用来创建它。 元类 Python 中的元类(metaclass)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。 类也是对象 在 Py...

    miya 评论0 收藏0
  • Python实例一个类背后发生了什么

    摘要:好吧,事实上,类本身也是实例,当然,它们是元类的实例。中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了。 写在最前面 一些很重要的知识,我的写得有点乱,也可以去看这些文章 Python 面向对象(初级篇) Python 面向对象(进阶篇) 深刻理解Python中的元类(metaclass) 首先来看一个例子,正常情况下我们定义并且实例一个类如下 class Foo(ob...

    shusen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<