频道栏目
首页 > 程序开发 > 软件开发 > 其他 > 正文
Python中的元类
2016-10-20 09:18:00      个评论    来源:Shiroh_ms08的专栏  
收藏   我要投稿

stackoverflow上的答案,最高票的答案说得很清楚,同时网上也有人对此进行了翻译。我对其进行翻译主要是在过程中读清楚具体讲了什么,并且实际编写代码测试其正确性和解除自己的疑惑(此处在另外博客中)。

Python中的类也是对象

在了解元类之前,你需要掌握Python中的类。Python中类的概念借鉴于Smalltalk编程语言并且有些奇特。
在大部分的语言中,类只是描述如何产生一个对象的代码片段,在Python中这点也成立。

In [1]: class ObjectCreator(object):
   ...:         pass
   ...:

In [2]: my_object = ObjectCreator()

In [3]: print (my_object)
<__main__.ObjectCreator object at 0x10bfa06d0>

但是在Python中类并不只是这样,类本身也是对象。是的,对象。
每当你使用关键词class,Python在执行的时候会产生一个对象。如下的代码:

class ObjectCreator(object):
    pass

会在内存中产生一个对象,名字为ObjectCreator。
这个对象(该类)自身能够创建对象(实例),这就是为什么称其为类的原因。但是同时类也是对象,因此你能够:

将其赋值给一个变量 拷贝它 为它添加属性 将其作为对象参数传递

示例如下:

In [4]: class ObjectCreator(object):
   ...:         pass
   ...:

In [5]: print(ObjectCreator)
<class>

In [6]: def echo(o):
   ...:     print o
   ...:

In [7]: echo(ObjectCreator)
<class>

In [8]: print(hasattr(ObjectCreator, 'new_attribute'))
False

In [9]: ObjectCreator.new_attribute = 'foo'

In [10]: print(hasattr(ObjectCreator, 'new_attribute'))
True

In [11]: print(ObjectCreator.new_attribute)
foo

In [12]: ObjectCreatorMirror = ObjectCreator

In [13]: print(ObjectCreatorMirror.new_attribute)
foo

In [14]: print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x10bfa0750>

动态地创建类

由于类也是对象,所以你能够动态地创建它们,像任何对象一样。

首先,你能够通过使用class在一个函数中创建一个类:

In [1]: def choose_class(name):
   ...:     if name == 'foo':
   ...:         class Foo(object):
   ...:             pass
   ...:         return Foo
   ...:     else:
   ...:         class Bar(object):
   ...:             pass
   ...:         return Bar
   ...:

In [2]: MyClass = choose_class('foo')

In [3]: print(MyClass)
<class>

In [4]: print(MyClass())
<__main__.Foo object at 0x10d996310>

但是这还不够动态,因为你仍然不得不自己去编写这个类。由于类是对象,所以它们必然是由其它的一些东西生成的。当你使用class关键词的时候,Python自动创建了该对象。但是像Python中大部分的事情一样,Python给你提供了手动处理的方法。

还记得type函数吗?这个能够让你明白一个对象类型的古老而强大的函数。

In [2]: print type(1)
<type>

In [3]: print type("1")
<type>

In [4]: print type(ObjectCreator)
<type>

In [5]: print type(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包含名字和值的属性的字典)

实际的type定义中也有

def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
    """
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    # (copied from class doc)
    """
    pass

例如:

In [6]: class MyShinyClass(object):
   ...:         pass
   ...:

In [7]: MyShinyClass = type('MyShinyClass', (), {})

In [8]: print MyShinyClass
<class>

In [9]: print MyShinyClass()
<__main__.MyShinyClass object at 0x103d51350>

你可以看到我们使用“MyShinyClass”作为类的名字,也可以当做一个变量来作为类的引用。类和变量并不相同,但是我们没有理由把事情弄复杂。

type接受一个字典来定义类的属性,所以:

In [10]: class Foo(object):
   ....:         bar = True

能够被写为:

In [11]: Foo = type('Foo', (), {'bar': True})

并且被像一个正常类一样使用:

In [12]: Foo = type('Foo', (), {'bar': True})

In [13]: print Foo
<class>

In [14]: print Foo.bar
True

In [15]: f = Foo()

In [16]: print f
<__main__.Foo object at 0x103d51850>

In [17]: print f.bar
True

当然你可以继承它,所以:

In [18]: Foo = type('Foo', (), {'bar': True})

In [19]: class Foochild(Foo):
   ....:         pass

可以写为:

In [20]: FooChild = type('FooChild', (Foo,), {})

In [21]: print FooChild
<class>

In [22]: print FooChild.bar
True

并且你可以在动态创建了类之后添加更多的方法,就像添加方法到正常创建的类的对象。

In [6]: def echo_bar_more(self):
   ...:         print 'yet another method'
   ...:

In [7]: Foochild.echo_bar_more = echo_bar_more

In [8]: hasattr(Foochild, 'echo_bar_more')
Out[8]: True

你可以看到我们所向何方:在Python中,类是对象,你能够动态创建一个类。这就是你使用关键词class时候Python做的事情,其是通过使用元类来实现的。

什么是元类(终于进入主题)

元类是创建类的东东。你定义了类来创建对象,是这样吧?但是我们学习到了在Python中类也是对象。因此,元类就是用于创建这些对象。元类是类的类,你能够这样理解他们:

MyClass = MetaClass()
MyObject = MyClass()

你能够发现type能够让你这样做:

MyClass = type(‘MyClass’, (), {})

这是因为函数type实际上是一个元类。type是Python在背后用来创建所有类的元类。

现在你会怀疑为什么type会用小写的形式,而不是写为Type。我猜这是为了和创建字符串对象的str和创建整数对象的int保持一致。type只是用来创建类对象的类。

你可以通过检查__class__属性来验证这点。

Python中任何东西都是对象,包括整数 ,字符串,函数和类,所有都是对象,所有都能够被从一个类中创建。

In [18]: age = 35

In [19]: print age.__class__
<type>

In [20]: name = 'bob'

In [21]: print name.__class__
<type>

In [22]: def foo(): pass

In [23]: print foo.__class__
<type>

In [24]: class Bar(object): pass

In [25]: b = Bar()

In [26]: print b.__class__

现在,任意的__class__的__class__是什么?

In [27]: print age.__class__.__class__
<type>

In [28]: print name.__class__.__class__
<type>

In [29]: print foo.__class__.__class__
<type>

In [30]: print b.__class__.__class__

所以,一个元类只是用来创建类对象的东西。你愿意的话可以称它为“类工厂”。type是Python使用的内建元类,当然,你能够创建你自己的元类。

__metaclass__属性

当你编写一个类的时候你能够添加一个__metaclass__属性:

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

如果你这么做,Python将会使用指定的元类来创建类Foo,注意的是,这其中有大坑。

你先编写了 class Foo(object),但是类的对象Foo并没有在内存中被创建。Python将会在类定义中寻找__metaclass__,如果发现了__metaclass__,其将会使用指定的元类来创建对象类Foo,如果没有发现,其将会使用type来创建类。

阅读如下几次:
当你编写

class Foo(Bar):
     pass

Python做了如下的事情:

是否在Foo中有__metaclass__属性?如果有,在内存中通过使用__metaclass__中指定的和Foo这个名字创建类对象。 如果找不到__metaclass__,Python将会使用Bar(Foo的第一个父类)的metaclass(可能是默认的type)来创建类对象。 如果Python找不到__metaclass__,Python将会在模块层次寻找__metaclass__,并且尝试做相同的事情(仅仅对没有继承任何类的类,基本上都是old-style classes。基本上可以认为是没有继承object的类,super就不适用于old-style classes)

现在的大问题是,你能在__metaclass__中间加入什么。答案是一些能够创建类的东西。那么怎么创建类呢?type或者任何type的子类或者用到type的东西。

自定义子类

一个metaclass的主要目的是在类创建的时候自动改变类。

你通常为了API这样做,当你想要创建符合当前上下文的类的时候。想想一个很愚蠢的例子,当你决定你模型中的所有的类的属性都必须小写的时候,有一些方法可以做到,其中一种就是在模块层次设置__metaclass__。

这样,所有在模块的类在创建的时候将会使用这个metaclass,我们只需要告诉metaclass去转换所有的属性为小写。幸运的是,__metaclass__可能任意调用,并非需要是一个正式的类(我知道,有些名字里带有“类”的东西并不一定是类)
所以我们以一个简单的例子开始,通过使用一个函数:

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

让我们使用一个真正的类来做元类,然后再做一次上述的事情:

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

上面这样的写法并不够面向对象,我们直接调用了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, future_class_attr)

你可能注意到了额外的参数upperattr_metaclass,这个参数没什么特殊的,__new__总是接受类作为第一个参数,就像你在普通的方法中的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来让它显得更加清晰,super定义了方法解析顺序,解决继承中的一些问题(是的,你能够使用从元类,从type中继承的元类)。

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__这样的变量等。实际上元类使用适合用于做一些黑魔法,因此会搞出一些复杂的东西来,但是就元类自身而言,它们很简单:

拦截类的创建 修改类 返回修改后的类

为什么要用元类的类而不是函数?

由于__metaclass__能够接受任何调用,为什么还要用一个显然更复杂的类呢?原因如下:

目的更加清晰,当你阅读到UpperAttrMetaclass(type),你知道接下来会发生什么 你能够使用面向对象,元类能够继承元类,覆盖父类的方法,元类甚至能够使用元类 你能够更好组织你的代码,你通常不会像上述例子这样简单地使用元类,它通常是用在很复杂的场合。将多个方法集合到一个类中总是有助于代码的可读性。 你能够使用__new__,__init__和__call__来帮助你做不同的事情,即使你能够将所有事情在__new__中完成,一些人更倾向于使用__init__ 这些东西叫做元类,日了狗了,一定要小心什么。

为什么要使用元类

一个大问题,为什么要用一些晦涩并且容易出错的功能?好吧,通常来说你不会使用它

“元类是深度魔法,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对象,而是会返回一个int,甚至能够直接从数据库中获取它。这是可能的,因为models.Model定义了__metaclass__并且使用一些魔法能够转换你刚刚定义的简单的Person类为一个复杂的到一个数据库项的钩子。Django通过暴露一个简单的API和使用元类让一些复杂的东西看起来简单,在API背后对于代码进行重新创建并完成真正的工作。

结束语

首先,你需要知道类都是对象并且能够创建实例。实际上类自身也是元类的实例。

In [1]: class Foo(object): pass
In [2]: id(Foo)
Out[2]: 140489058020624

在Python中的所有东西都是一个对象,它们要么是类的实例,要么是元类的实例。除了type。type实际上是自己的元类,这是在纯Python中你没办法实现的事情,其是在实现层次中通过一点小手段实现的。

其次,元类很复杂,你可能并不想在很简单的类改造的时候使用元类,你能够通过两个不同的技术来改变类:

monkey patching class decorators

在需要进行类改造的时候,99%情况下你最好使用上面这两个,然而在99%的情况下,你并不需要对类进行改变。

点击复制链接 与好友分享!回本站首页
上一篇:第八周项目1:建立顺序串的算法库
下一篇:GNURadio+HackRF小实验(FM发射与接收)
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站