python 元类 详解_Python -- 元类metaclass详解

学习契机

项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_metaclass(abc.ABCMeta),故而学习一下Python的元类。不过,虽然@six.add_metaclass(abc.ABCMeta)实现上与元类有关,但实际应用只需要调用其接口,并不需要接触后幕后的元类操作。

翻译这篇答案是为了方便自己记忆理解,其实原文中一些地方我自己不是很明白,所以这个翻译会根据自己理解的程度持续更新。

原链接

Python中的元类是什么

类也是对象

在理解元类之前,需要掌握Python中类概念。Python的类概念稍有奇特,其借鉴于Smalltalk。

在大部分语言中,类用于描述如何生成一个对象,在Python中也是如此:

>>> class ObjectCreator(object):

... pass

...

>>> my_object = ObjectCreator()

>>> print(my_object)

但是,在Python中类的意义更多,类同时也是对象。当你使用关键字class时,Python解释器执行代码时会生成一个对象。以下代码会在内存中创建一个名为“ObjectCreator”的对象:

>>> class ObjectCreator(object):

... pass

...

这个对象(类)自身可以创建对象(实例),这是为什么它是类的原因。

不过它仍然是一个对象,你可以:

可以将它赋值给变量

可以复制

可以添加属性 TODO 添加属性只是对象的特性?

可以将其当作函数参数传递

举例:

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

动态创建类

既然类也是对象,那就可像其他对象那样,动态创建类。

首先,可以使用关键字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

但是这还不够动态,因为还是需要自己编写整个类的代码。所以,想一想,这些类既然是对象,就必然是某种东西生成的。当使用class关键字时,Python自动创建了类这个对象,但是像Python中大部分事情一样,Python中也可以手动创建类。

还记type方法吗?一个可以让你知道一个对象是什么类型的方法:

>>> print(type(1))

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

>>> print(type(ObjectCreator))

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

除此之外,type还有一个完全不同的功能:动态创建类。将类的描述作为参数传递给type,会返回一个类。(我知道,同一个函数根据传参的不同而展示出两种完全不同的功能很不合理,不过这是为了Python的向后兼容。)

type如何创建类:

type(类名,

父类元祖 (可为空),

包含键值对属性的字典)

举例:

>>> class MyShinyClass(object):

... pass

上面这个MyShinyClass类可以用以下方法手动创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类

>>> print(MyShinyClass)

>>> print(MyShinyClass()) # 创建一个类对象

应该注意到了,使用"MyShinyClass"作为类名,也将其作为一个变量名,赋值为类的引用。类名和变量名是可以不同的,但是没必要把事情搞复杂。

type可以接受一个定义类属性的字典作为参数:

>>> class Foo(object):

... bar = True

以上定义等同于:

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

使用起来跟一个普通类一样:

>>> print(Foo)

>>> print(Foo.bar)

True

>>> f = Foo()

>>> print(f)

>>> print(f.bar) # 利用实例打印类属性

True

当然,也可以作为基类,给其他类继承:

>>> class FooChild(Foo):

... pass

以上代码等同于:

>>> FooChild = type('FooChild', (Foo,), {})

>>> print(FooChild)

>>> print(FooChild.bar) # bar属性继承自类Foo

True

你肯定还想为类添加方法。只需要定义一个名称合理的函数,并将这个函数名作为属性传递给type就行:

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

现在已经看到:在Python中,类也是对象,可以动态地创建类。当使用关键字class时,Python使用元类像这样创建类的。

什么是元类(终于讲到了)

元类就是创建类的“东西”。我们定义类是为了创建对象,是吧?但是我们认识到在Python中类也是对象,而元类就是创建类这种对象(类)的,它们是类的类,你可以这样理解:

MyClass = MetaClass()

MyObject = MyClass()

你已经看到type可以让你做如下操作:

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

这是因为type方法实际上是一个元类。事实上,type是Python中用于创建所有类的元类。不过现在你一定很奇怪为什么这个类名首字母是小写,而不是Type?我猜这是同str保持一致,str是用来创建string对象的类,int是用来创建integer对象的类,type则是创建类对象的类。

在Python中,一切,对就是一切,都是对象。包括整数、字符串、函数和类。所有东西都是对象,它们都是创建自某个类。

通过__class__属性可以验证这一点:

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

[...]

如果你像上面这样做,Python就会使用元类创建一个Foo类。

要当心了,这里有些小圈套。你先写下了“class Foo(object)”,但此时内存中还没有创建Foo类对象。Python会在类的声明中寻找属性__metaclass_,如果找到了就会使用其创建Foo类;如果没有,会使用type创建这个类。下面这段文字要多读几遍。

当你编写以下代码时:

class Foo(Bar):

pass

Python做了这些事情:

在类Foo中有定义__metaclass__属性吗?

如果有,则继续;

如果没有,Python会在模块层寻找__metaclass__属性(这只针对没有继承任何其他类的情况);

如果模块层也没有,则会在Bar(第一个父类)中寻找(这就有可能是内置的type)。

这样找到__metaclass__后,使用它在内存中创建名称为Foo的类对象(这边跟上,一个类对象)

需要注意的是,__metaclass__属性不会被继承,但是父类的元类(Bar.__class__)可以被继承:如果Bar的__metaclass__属性定义了使用type()(不是type.__new())创建Bar类,其子类不会继承这个行为。(Be careful here that the metaclass attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a metaclass attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.)TODO 这边不太理解

现在有个新问题,你可以赋什么值给__metaclass__?

答案是:可以创建一个类的东西。

那什么可以创建类?type、子类化type或者使用type的东西。

自定义元类

元类的主要目的,是在创建类的时候动态地改变类。通常你会想创建符合当前上下文的类供API使用。举一个简单的例子,当你希望一个模块中所有的类属性都是小写时,有几种方法可以实现,其中有一种方法就是在模块层设置__metaclass__。使用这种方法,这个模块中所有的类都将使用此元类创建,我们只需要使元类将所有类属性置为小写。

幸运的是,__metaclass__可以被任意调用,不一定非要是一个正式的类(我知道,名称包含“class”不一定非要是一个类,搞清楚了...这很有用)。

现在先用一个函数举一个简单的例子:

# 元类会自动获取通常传给`type`的参数

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(): # global __metaclass__ won't work with "object" though == 没看懂

# but we can define __metaclass__ here instead to affect only this class

# and this will work with "object" children

bar = 'bip'

print(hasattr(Foo, 'bar'))

# Out: False

print(hasattr(Foo, 'BAR'))

# Out: True

f = Foo()

print(f.BAR)

# Out: 'bip'

现在,完成同样的功能,但是为元类定义一个真实的类:

# 记住`type`实际上是一个像`str`和`int`的类,可以用于被继承

class UpperAttrMetaclass(type):

# __new__放在__init__之前调用,此方法创建对象并反回

# 而__init__则是初始化作为参数传递给此方法的对象

# 除非你想控制如何创建一个对象,否则很少用到__new__

# 在这里,被创建的对象是类,而我们想自定义这个类,所以重写了__new__

# 如果需要的话你也可以在__init__中做一些操作

# 一些高级用法会包括重写__call__,不过这里还不需要

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,没有重写或者调用父类的__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__方法

# 这是基本的OOP,没什么深奥的

return type.__new__(upperattr_metaclass, future_class_name,

future_class_parents, uppercase_attr)

你大概发现了传给type的额外的参数upperattr_metaclass。这没什么奇怪的:__new__的第一个参数总是其定义的类。就像类方法中第一个参数总是self。当然,为了清晰期间,这里我起的名字比较长,但是像self这样的参数通常有一个传统的名字。所以真正的产品代码中,元类是像这样的:

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可以更清晰,which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type)TODO:

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)

以上,关于元类也没有更多了。使用元类的代码比较复杂的原因不在于元类,而在于你通常会依靠自省、操纵继承、__dict__变量等,使用元类做一些晦涩的事情。(it's because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO

元类来用于黑魔法时的确特别有用,因为也会将事情搞得很复杂。但就其本身而言,是简单的:

拦截一个类的创建

修改类

返回修改的类

为什么会用元类代替函数?

既然__metaclass__可以被任意调用,为什么要使用明显更复杂的类呢?有这样一些理由:

意图明显。当你看到UpperAttrMetaclass(type),你知道接下来会发生什么。

可以使用OOP。元类可以继承自元类,重写父类的方法,元类甚至可以使用元类。

如果为一个类指定的元类是类而不是方法,这个类的子类将是元类的一个实例。Children of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.TODO

你可以将代码组织得更好。使用元类时肯定不会仅想像上面举的例子那样简单,通常是用于比较复杂的场景。将多个方法组织在一个类中有益于使代码更容易阅读。

你可以使用__new__,__init__ 和 __call__,这些方法可以处理不用的事情。即使很多时候你可以在__new__中完成所有工作,当然,一些人会更习惯用__init__。

这些东西叫 “metaclass”,小心了!这一定很难搞!

为什么使用元类

好了,现在的问题是:为什么要是用这样晦涩且容易出错的特性?其实,通常你不会用:

元类是99%的用户根本不必操心的深度魔法。如果你在考虑是否需要使用,那就不要用(真正需要的用户很清楚他们的需求,根本不需要解释为什么要使用元类)

Python领袖 Tim Peters

使用元类的一个主要场景是创建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)

它不会返回一个IntegerField的对象,而是返回一个整数,甚至可以从数据库中直接获取数据。TODO

这是因为,在models.Model中定义了__metaclass__,使用了一些魔法将你定义的简单的Person类转换为一个复杂的数据库挂钩。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO

Django使用元类对外提供简单的API,简化了一些复杂的东西,API中重建的代码会去完成幕后真正的工作。

结语

首先,类是可以创建实例的对象。类本身是元类的对象:

>>> class Foo(object): pass

>>> id(Foo)

142630324

在Python中,除了type,一切皆对象,一切都是类或者元类的对象。事实上type是自己的元类,这在纯Python中这是无法实现的,这里在实现层上做了一些手段。

其次,元类是复杂的。你可能不希望对非常简单的类使用元类,那么还有其他两种手段用来改变类:

monkey patching

类装饰器

如果你需要改变类,99%的情况下使用这两种方法。

但其实98%的情况你根本不需要改变类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值