python里的metaclass

本文翻译自[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是复杂的。你不应该在非常简单的类改建中使用它们。你可以使用这两种技术改变类:

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

何况99%的情况下,你不需要类修改。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值