stackoverflow 关于元类

原文地址:https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python/6581949#6581949

Classes as objects

在你理解元类之前,你需要去掌握python里的类。python有一个非常特别的关于什么是类的想法,从Smalltalk这门语言带来的。

在大多数语言中,类仅仅是一些描述如何产生对象的一些代码。python也是一样的。

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

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

但是类在python不仅仅是这样。类也是对象。

是的,对象。

当你使用这个关键词class,python执行它并创建一个对象。结构如下

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

在内存中创建一个叫做(ObjectCreator)的对象。

这个对象(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>

Creating classes dynamically

尽管类是对象,你可以随手创建他们,就像任何对象一样。

首先,你可以在函数中使用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? 这个优秀而古老的函数让你知道一个对象的类型是什么:

>>> 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,bases,attrs)
  • name:类的名字
  • bases:父类的元组(为了继承,可以是空的)
  • attrs;包含属性名和值的字典

例子:

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

你也可以在动态地创建这个类后添加更多的方法,就像给一个正常创建的class对象添加方法。

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

You see where we are goging(不知道咋翻合适),在Python里,类是对象,你可以随手地、动态地创建一个类。

这就是当你在使用关键字class 时Python所做的事,Python通过使用元类来做到。

What are metaclasses (finally)

元类是创造类的‘东西’

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

但是我们知道Python类就是对象。

好的,元类就是创建那些对象的东西。他们是类的类,你可以用这种方式看出来。

MyClass= MetaClass()
my_object = MyClass()

你已经看到type 让你做了一些这样的事:

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

这是因为type函数事实上是一个元类。type是Python用来隐藏地创建所有类的元类。

现在你想知道为什么他用小写,而不是Type?

我猜测是一种连续性,str 是创建字符串对象的类,int 是创建整数对象的类,type

是创建类这个对象的类。

你会在查看__class__属性的时候看到。

所有东西,我意味着所有东西,在python里都是一个对象。

包括ints,strings,function,classses.他们全部都是对象。他们全部都是从一个类来创建的。

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

现在,what is the __ class __ of any __ class __?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以,一个元类只是一个创建类这个对象的东西。

如果你想,你也可以叫他为’类工厂‘.

type 是Python使用的内置元类,当然,你可以创建自己的元类。

The __ metaclass __ attribute

在python2里,你可以添加__ metaclass __属性在你写一个类的时候(关于Python3语法看下一个章节)

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

如果你这么做,Python用这个元类来创建这个类Foo。

小心,这是很有技巧性的。

你首先写了class Foo(object): ,但是这个class对象Foo被还没在内存中创建。

python会在类的定义总寻找在__ metaclass __ 。

如果找到,Python会用它来创建这个对象 class Foo,如果没有,它会用type来创建这个类。

多读几遍。

当你做:

class Foo(Bar):
  pass

Python做了以下这些事情:
在Foo里是否有元类这么一个属性?

如果有,那就在内存中创建一个class对象,with the name Foo by using what is in __ metaclass __

如果Python没有找到__ metaclass __ ,他就会在模块级别寻找__ metaclass __,然后做同样的事,(但是是仅对于没有继承什么东西的类,基础的过时的类)

然后如果它不能找到任何的__ metaclass __属性,它会用Bar(第一个父类) 的metaclass(可能是默认的 type)来创建这个类对象。

但是在这里注意,__metaclass__这条属性不会被继承,父类的元类(Bar.__class__)会。如果Bar 通过type()使用__metaclass__属性来创建BAR

(而不是 type.__new__),随后的类不会继承这个行为。

现在最大的问题是,你可以给metaclass输入些什么?

答案是:一些可以创造类的东西。

然后什么创造了一个类?type,或者是任何继承或使用它的东西。

Metaclasses in Python 3

设置元类的语法在Python 3中已经改变了。

class Foo(object,metaclass=someting):

那就是说,__metaclass属性不再被使用,而是在一些基础的类中被一个关键字参数所替代。

元类的行为大体一样。

在Python3中一件事被添加到元类中,你可以以关键字参数的形式传递属性到一个元类,就像

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

阅读之前关于Python如何处理它的章节。

Custom metaclasses

元类的主要目的是自动的改变这个类,当它被创建的时候。

你通常因为API而使用它,当你想要创建匹配当前内容的类。

想一个愚蠢的例子,当你决定将你模块中的所有类的属性大写。有很多办法去实现,

但是其中的一种方式就是在模块级别设置__metaclass__

在这种方式下,这个模块的所有类会使用这个元类创建,我们只是告诉元类让所有属性大写。

幸运的是,__metaclass__可以被任何东西调用,它不需要是一个正常的类(我知道,一些名字有class的东西不需要是一个类,难以置信,但这个很有用的)

所以我们从一个简单的例子开始,通过一个函数。

# 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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

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

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

Let’s check:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

重写上面的东西,但是会更短和有更多有真实意义的变量名,可以让我知道他们意味着什么。

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

你可能注意到这个额外的参数cls .它没有什么特殊的,__new__总是收到类的定义,作为第一个参数。就像你用(self)给普通的方法接收实例作为第一个参数,或者是给类方法定义类。

但是这不是合适的面向对象编程。我们直接调用type而且我们没有重写父类的__new ,改为像下面这样做。

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

我们可以使他变得更简洁通过使用super,是更简单的继承方式(因为是的,你可以有元类,继承于其他元类,继承于type).

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

在python3中如果你调用这个关键字参数,就像下面这样:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

他在元类中会转化成这样来使用

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

就这些东西了。真的没有更多关于元类的东西了。

使用元类代码复杂的原因不是因为元类,是因为你使用元类来

事实上,元类来做一些黑魔法和一些复杂的东西非常有效,但是他们自己而言,是很简单的。

  • 拦截一个类的创建
  • 修改类
  • 返回修改后的类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值