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

python里的metaclass

2015-03-30 22:43 369 查看
本文翻译自[What is a metaclass in Python?]

作为对象的类

在理解metaclass之前,你需要掌握python中类的知识。python里类的概念借鉴于Smalltalk语言,是一种非常独特的理念。

在许多语言里,类只是用来定义怎样创建对象的一小段代码而已。在python中也有点相似。

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

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>


但是在python里,类不止是这样。类也是对象。没错,就是对象!!!

只要你使用关键字
class
,Python就会执行它并创建一个OBJECT。

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


这个命令会在内存中创建一个对象,被命名为”ObjectCreator”。

This object (the class) is itself capable of creating objects (the instances), and this is why it’s a class.(这个对象有创建对象的能力,所以它也是一个类。)

但是,它依旧是一个对象,因此:

你可以给它分配一个变量。

你可以复制它。

你可以为它增加属性。

你可以把它作为一个方法参数进行传递。

>>> 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
关键字吗?这个古老而实用的函数让我们知道type一个对象会发生什么:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>


好吧,
type
有一个完全不同的能力,它也可以快速地创建类。
type
可以接收类的描述作为参数,返回一个类。

(我知道,同一个函数接收两种不同的参数产生两种完全不一样的用途,这种行为是丑陋的。这个问题是由Python的向后兼容造成的。)

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>


可以注意到,我们使用”MyShinyClass”作为类的名字并且以相同的名字作为变量。当然,它们可以不同,但没有必要把事情搞复杂。

type
接收一个字典来定义类的属性。

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


能被这样定义:

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


当然,你可以继承它:

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


可以看出:在Python中,类是对象,你可以快速动态地创建一个类。

什么是metaclasses

Metaclasses是创建类的“东西”。

你定义类是为了创建对象,对吗?

但我们已经学到,python中的类就是对象。

好吧,metaclassses就是创建这些对象的东西。它们是类的类,你可以把它们想象成这样:

MyClass = MetaClass()
MyObject = MyClass()


你已经看到了
type
可以让你这样做:

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


这是因为函数
type
实际上是一个metaclass。Python使用它秘密地创建所有的类。

也许,现在你会怀疑为什么要用它的小写形式,而不是
Type


好吧,我猜想这是为了保持和
str
命名的一致性,
str
是用来创建字符串对象,而
int
是用来创建整型对象。
type
只是用来创建类对象的类。

你可以通过
class
属性了解到这一点。

在python中,所有的东西都是对象。它包括ints,stings,functions和classes。所有的东西都是对象。并且它们都是从一个类中创建出来的:

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


所以,一个metaclass只是用来创建类对象的东西。

如果你想的话,可以把它成为”类工厂“。

type
是python的内建metaclass,当然啦,你也可以自定义metaclass。

__metaclass__
属性

当创建一个类时,你可以增加一个
__metaclass__
属性:

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


如果你这样做,python会使用metaclass来创建类
Foo


但要小心,这很微妙。

首先,你写了
class Foo(object
,但类对象
Foo
并没有在内存中被创建。

Python将要在类定义中寻找
__metaclass__
,如果找到了,它将使用它创建类对象
Foo
。如果没有,它将使用
type
创建类。

以上,请多读几遍!!!

当你这样做的时候:

class Foo(Bar):
pass


Python会做下面的事情:

Foo
中是否有
__metaclass__
属性?

如果有,会使用
__metaclass__
中的内容在内存中创建一个类对象,并命名为
Foo


如果Python不能找到
__metaclass__
,他会在MODULE等级上寻找
__metaclass__
,并尝试做同样的事(但仅仅对没有任何继承关系的类有效,主要是old-style类)。

那么,如果它找不到任何
__metaclass__
,它将使用
Bar
(第一个父类)的metaclass(可能是默认的
type
)创建类对象。

这里需要注意的是,
__metaclass__
属性不会继承。它的父类的metaclass(
Bar.__class__
)将会被继承。如果
Bar
__metaclass__
属性使用
type()
(并且不是
type.__new__()
)创建
Bar
,它的子类不会实现这种行为。

现在最主要的问题是,我们要在
__metaclass__
属性中放点什么。

并且什么能创建类?
type
,或者它的子类以及任何使用它的东西。

自定义metaclasses

metaclass的主要目的是当创建类的时候自动改变它。

你通常使用它来定义APIs,它使你能够创建匹配当前上下文的类。

设想一个不恰当的例子,你决定把模块中的所有类的属性都写成大写形式。有许多方式可以来完成它,在模块级别上设置
__metaclass__
便是其一。

使用这种方式,模块中所有的类都会使用这个metaclass来创建,我们只需要告诉这个metaclass把所有的属性转成大写就行了。

幸运的是,
__metaclass__
实际上可以是任何一个callable,而不需要只是一个正式的类。

所以,我们将以一个简单的例子开始,使用函数:

# the metaclass will automatically get passed the same argument
# that you usually pass to `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

# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

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'


现在我们使用一个真正的类来做这件事:

# 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
,而不是重写或者调用父类的
__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
。这没有什么特别之处:一个方法总是接收当前实例作为参数,就像普通方法中的
self


当然,我在这里使用的名字是为了更加清晰,但是就像
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
关键字使它更清晰,它会缓解继承(ease inheritance??)。

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)


就是这样,关于metaclasses,这里没有更多解释了。

隐藏在使用metaclasses代码的复杂性之后的真正原因不是由于metaclasses本身,而是因为你经常做一些扭曲的东西,比如内省、操纵继承、变量
__dict__
等。

时间上,metaclasses对特别适合用来施展一些黑魔法和复杂的东西。但它们本身是简单的:

拦截类的创建

修改类

返回修改后的类

为什么你要用metaclasses类来代替函数?

由于
__metaclass__
可以接收任意callable,为什么你还要使用一个更复杂的类呢?

这里有许多原因:

意图明确。当你读到
UpperAttrMetaclass(type)
时,你知道接下来会干什么。

你可以使用OOP。Metaclasses能继承metaclasses,重写父类方法。Metaclasses甚至可以使用metaclasses。

你可以更好的构造你的代码。你不会使用metaclasses做和上面的例子一样琐碎的事情。它通常被用来做复杂的事情。这种把许多方法合并到一个类中的能力是很有用的,它使你的代码更容易阅读。

你可以在
__new__
__init__
__call__
上进行hook。它允许你做不同的事情。甚至你可以在
__new__
中完成所有工作,当然,一些人使用
__init__
会感觉更加舒服。

它们被叫做metaclassess,该死!!!它一定有某种意思!

你为什么要使用metaclasses?!!!

现在,难题来了。你为什么要使用这种晦涩难懂、容易出错的特性呢?

好吧,通常你不会这样:

Metaclasses are deeper magic than 99% of users should never worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

Python Guru Tim Peters

一个metaclass主要的使用案例是创建API。一个典型的例子是Django ORM。

它允许你这样定义东西:

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


但是,如果你这样做:

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


它不会返回一个
IntegerField
对象。它将会返回一个
int
,甚至能直接从数据库里拿到它。

这可能是因为
models.Model
定义了
__metaclass__
,它使用了一些魔法把刚刚用简单语句定义的
Person
转变成对应于数据库域的复杂hook。

通过暴露一个简单的API与metaclasses的使用,Django使一些复杂的东西看起来简单了。在这种情景下,是从API里重新创建代码做真正的工作。

最后的话

首先,你知道了类是能够创建实例的对象。

而实际上,类本身也是实例。是metaclasses的。

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


在Python中一切都是对象,它们要么是类的实例,要么是metaclass的实例。

除了
type


实际上,
type
是它自己的metaclass。在纯粹的Python里,这是不能被复制的,并且它在实现层面上进行了一些伪装。

其次,metaclass是复杂的。你不应该在非常简单的类改建中使用它们。你可以使用这两种技术改变类:

monkey patching

class decorators

一旦你需要进行类修改,99%的情况下,最好使用这些技术。

何况99%的情况下,你不需要类修改。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息