您的位置:首页 > 编程语言 > Python开发

深入理解python元类

2017-01-09 20:57 369 查看
类和对象

什么是元类

__metaclass属性

定制元类

为什么要使用元类

总结

类和对象

在理解什么是元类之前,有必要先理解下,什么是类。
什么是类?通俗的讲,类就是用来创建对象的代码片。在python中,类还有一个奇特的特性,就是类,本身也是一个对象。怎么理解?——在你定义一个类的时候,就会在内存中创建一个名字为类名的对象。
能够创建对象(实体)的对象(类),就称之为类。(说起来很别扭,但大致就是这么个意思)。
既然是一个对象,那就可以:


将它分配给一个变量

复制它

给它添加属性

传递给函数的参数

我们知道,对象不可能凭空产生,它总是由某些”事物”生成的。既然类也是个对象,那也不例外。

当你在使用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(name of the class,tuple of the parent class (for inheritance, can be empty), dictionary containing attributes names and values)

比如

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


你可以继承这个类:

>>> class FooChild(Foo):
... pass


这种继承也能用type来实现:

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


是的,当你使用class关键字时,python做了所有该做的事。而这些事是通过元类(metaclass)来完成的。

什么是元类

现在应该就知道元类是什么以及干什么的了吧?
元类就是那个帮你创建类的"家伙"。
为了创建对象(实体),你定义了类,是不是?
而在python中,类也是对象,这样的对象就是通过元类来创建的。元类就是"类的类"。
上文中提到的type事实上就是一个元类,在python中,所有的类都是使用type创建的。
到此,我们也能够更加理解为什么说在python中,一切皆对象了吧?int,string,function以及class,所有的多少对象。他们都能够从一个类中创建。通过_class_可以验证:


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


25这个整数对象是有类"int"创建的。那"int"这个对象是由谁创建的呢?继续查看_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…

[…]

当你写下class Foo(object)的时候,类对象Foo并不会在内存中创建:Python首先会看类定义中的metaclass。如果找到,就会使用它创建类对象Foo。否则,就使用type来创建。

继续说明:

当你写下如下代码:

class Foo(Bar):

pass

Python:

Foo里面有没有metaclass属性?

如果有,通过使用metaclass里面的元类在内存中创建名为Foo的类对象(在本文中类对象就是指类这个对象,请与类创建的实体对象区别开)。

如果没有,则会在模块(MODULE)层寻找metaclass,并执行相同的创建类对象操作(这种情况只针对老类型定义的类,也就是没有继承任何类的类)。

最终如果找不到任何metaclass,Foo就会使用Bar(第一个父类)的元类来创建类对象。

现在的问题是,metaclass能赋什么值?

回答:能够创建一个类的”事物”,也就是元类type或type的子类(或组合了type的类)

定制元类

想象一个愚蠢的例子。当你想要你模块中的所有类的属性名都是大写字母。有很多种实现方法,其中一种是在模块层设置_metaclass_。这样,模块中的所有类都会使用这个原来来创建,我们只要告诉元类将所有属性转换成大写就行了。
_metaclass_的值不一定要一个正式的类,可以为任何可调用的事物。
比如,函数也可以(当然,记住上面说过的,要组合type):


def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
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(): # 如果这里继承了object,那全局_metaclass_就不会对这个Foo类起作用了哦
# 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'


当然,也可以为元类定义一个类:


# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# _new_ is the method called before _init_
# it's the method that creates the object and returns it
# while _init_ just initializes the object passed as parameter
# you rarely use _new_, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override _new_
# you can do some stuff in _init_ too if you wish
# some advanced use involves overriding _call_ as well, but we won't
# see this
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
# reuse the type._new_ method
# this is basic OOP, nothing magic in there
return type._new_(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)


你可能已经注意到这里多了一个”upperattr_metaclass”参数。这没什么特别的。new方法总是将其所在的类作为第一个参数,这就是对象方法第一个参数是self,类方法第一个参数是cls一样。

上面的定义使用的参数都太长了,按约定俗成的名称会是这样的:

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)


元类在做很多"黑魔法"事情方面的确非常有用,这也是让他看起来复杂的原因。元类本身还是很简单的:
1)拦截类的创建;2)修改这个类;3)返回修改后的类。
为什么要使用元类类(metaclasses classes)而不是函数呢?
考虑到_metaclass_可以接受任何可调用事物,那为什么要用一个看起来明显复杂的类呢?
原因主要有以下几个:


意图更明显。当读到UpperAttrMetaclass(type),你清楚知道要干什么

你能够使用OOP

能够更好的组织你的代码

能够利用new、init和call干点别的。

毕竟它们是元类啊,是不?(意思就是函数不能体现元类作为一个类,一个事物。)

为什么要使用元类?

python大师Tim Peters说过:元类是一种99%的人都不需要关心的深度魔法。当你在好奇你是否需要它,通常这就说明你并不需要(确切需要它的人不需要找任何原因解析为什么需要)。

元类的主要使用场景是创建一个API。典型的例子就是Django ORM。

总结

首先,除了type,一切皆对象,不是类的实体,就是元类的实体。

其次,元类恨复杂。在简单的改变类时你可能并不想要使用。你能通过使用以下两种技术来改变类:

monkey patching

class decorators

需要改变类的99%的情况,你使用这两种更好。而99%的场景,你其实并不需要元类。

【参考文献】

http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

这篇里面也介绍元类也介绍的非常不错,虽然它标题是关于抽象类的:

http://blog.thedigitalcatonline.com/blog/2016/04/03/abstract-base-classes-in-python/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 元类