什么是元类,怎么运用元类?

翻译自:https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python


什么是元类,怎么运用元类?

类是对象

  在理解元类之前,必须掌握Python中的类,Python中的类是非常特别的思想,其借鉴自Smalltalk 语言。
在许多语言中,类是一段描述如何产生对象的代码。Python也是如此.
>>> class ObjectCreator(object):
    pass                                       
>>>  my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x00000295A6FC1C50>
>>> 

但是在Python中类不仅仅是如此,类也是对象。当你使用关键字class时,Python解释执行并创建了一个对象。

class ObjectCreator(object):
    pass

# 在内存中创建一个名字是 ObjectCreator的对象类) 自己具有创建对象[实例]的能力 ,这就是其被称为类的原因。但是类的本质还是一个对象,这是因为:
• 可以将它赋值给一个变量
• 可以复制他
• 可以给他添加属性
• 可以将它当做参数传递给一个函数

e.g:

>>> print(ObjectCreator)                                  # 你可以打印类,因为其是一个对象
<class '__main__.ObjectCreator'>
>>> echo = lambda o : print(o)
>>> echo(ObjectCreator)                                  #当做参数传递给一个函数
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator,'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo'        #添加属性给一个类
>>> print(hasattr(ObjectCreator,'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator     #  将类赋值给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x00000295A6FCF748>
>>> 

动态地创建类

由于类是对象,你可以向任何对象一样,随时的创建类。
首选,你可以在函数中用关键字class创建类

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # 返回类,不是实例
...     else:
...         class Bar(object):
...             pass
...         return Bar
...     
>>> MyClass = choose_class('foo') 
>>> print(MyClass)                          # 函数返回的是类不是实例
<class '__main__.Foo'>
>>> print(MyClass())                   # 你可以用这个类创建一个对象
<__main__.Foo object at 0x89c6d4c>

但是其动态性还不足,因为你仍需要写整个类
当你使用class关键字时,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', (), {})      #返回对象(一个类)
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass())                                      # 创建这个类的实例
<__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)
True
>>> FooChild
<class '__main__.FooChild'>
>>> FooChild.__mro__         # FooChild的类搜索树
(<class '__main__.FooChild'>, <class '__main__.Foo'>, <class 'object'>)
>>> 

最后,你可以为类添加方法。可以通过合适的签名定义函数,并将其赋值给类的属性。

>>> 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中,类也是对象。你可以动态地凭空(fly)创建一个类。
这就是当你使用关键字class时,Python所做的事。在元类中也是同样如此。


元类是什么

元类是创建类的原料(stuff)
为了创建对象,你可以定义类。但是我们已经认识到Python的类也是对象。
而元类可以创建这样的对象,这些对象是类的类,例如:
MyClass = MetaClass()
MyObject = MyClass()
你已经明白type可以做如下的事情:
MyClass = type(‘MyClass’, (), {})
这是因为函数type实际上是一个元类。Python用元类type来创建所有的类。
你也许奇怪为什么type是小写而不是大写?
我猜测其是为了与str保持一致,str类用来创建字符串对象。iNt类用于创建整型对象。因此type用来创建类的对象。
通过检查__class__属性,你可以看到这一点。
在Python中一切都是对象。包括整数,字符串,函数和类.他们都是对象,可以由一个类创建:

>>> age.__class__
<class 'int'>
>>> name='non'
>>> name.__class__
<class 'str'>
>>> def foo():pass
>>> foo.__class__
<class 'function'>
>>> 

现在,考虑一下,__class__的__class__属性是什么?

>>> foo.__class__
<class 'function'>
>>> age.__class__.__class__
<class 'type'>
>>> name.__class__.__class__
<class 'type'>
>>> foo.__class__.__class__
<class 'type'>

显然,元类就是创建类对象的原料
你可以称之为类工厂
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__属性?
如果有,在内存中创建一个类对象(class object),并命名为Foo
如果Python没有找到__metaclass__属性,将会在模块层寻找__metaclass__,并尝试做同样的事情。
如果最终找不到__metaclass__属性,他讲使用Bar(Foo的第一个父类)自己的元类(有可能是默认的type)创建类对象。

这里需要注意__metaclass__属性不会继承自父类的__metaclass__属性(Bra.class)。如果Bar使用__metaclass__属性以type()(不是type.__new__())的方式创建Bar,子类不会继承这个行为。
现在有个非常重要的问题,我们可以在__metaclass__中放置什么?
答案是:创建类所需的信息。
谁可以创建一个类?type或者任何使用type的子类。

定制元类

元类的主要目的是在创建类时,动态的改变它。
这通常用于API,你可以创建一个与上下文匹配的类。
举一个比较简单的例子,你决定在某个模块中使所有的类属性全部大写。有许多方式实现,其中一种方式可以在模块层使用__metaclass__实现。
这种方式中,这个模块的所有类以一个元类创建,我们只需要告诉这个元类将所有的属性转变成大写。
幸运地是,__metaclass__实际上是可以回调的,我们不必显示的形式化一个类。
举一个简单的例子:

# 元类将自动的获取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'

现在,将元类定义为一个真实的类:

# -*- coding: UTF-8 –*-
#  `type` 就是类似`str` 和 `int` 的类
# 因此你可以继承它
class UpperAttrMetaclass(type):
    # __new__ 在 __init__前执行
    # __new__创建一个对象并返回
    # __init__ 仅仅通过传递参数给对象实现初始化
    # __new__很少使用,仅仅用户你想控制对象的创建方式

    # 这里我们创建的对象是一个类,需要定制创建,因此需要重写方法__new__
    # 你也可以在 __init__ 中做初始化操作

    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)

__metaclass__ = UpperAttrMetaclass

但是这还不够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
        # 这是基本的OOP,不是魔法
        return type.__new__(upperattr_metaclass,future_class_name,
                            future_class_parents, uppercase_attr)

注意,type.new的第一个参数upperattr_metaclass,这个参数就相当于class中的第一参数self
作为接收实例一样。upperattr_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使代码更简洁。
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)

的确,元类是特别有用的黑魔法,因此显得有些复杂。但是其本身来说,实现下列功能确很简单:
• 拦截类的创建
• 修改类
• 返回修改的类

为何使用元类?
元类主要用于创建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重建代码来实现背后的工作。

结语

1.首先,类时对象可以创建实例。
实际上,类是元类的实例。

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

Python 中一切都是对象,他们是类的实例或元类的实例(type除外)
2.type是它自己的元类。
其次,元类十分复杂。一般情况下,修改简单的类不用元类,二用以下两种技术:
• 猴子补丁
• 类装饰器
99%的情况下采用以上两种方式修改类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值