理解Python中的元类(metaclass)

在理解元类之前,你需要先掌握Python中的类。Python中类的概念借鉴于Smalltalk,这显得有些奇特。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

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

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

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象.

下面这段代码:

class ObjectCreator(object):
      pass

将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作:

  • 你可以将它赋值给一个变量
  • 你可以拷贝它
  • 你可以为它增加属性
  • 你可以将它作为函数参数进行传递

下面是示例:

>>> print ObjectCreator     # &#x4F60;&#x53EF;&#x4EE5;&#x6253;&#x5370;&#x4E00;&#x4E2A;&#x7C7B;&#xFF0C;&#x56E0;&#x4E3A;&#x5B83;&#x5176;&#x5B9E;&#x4E5F;&#x662F;&#x4E00;&#x4E2A;&#x5BF9;&#x8C61;
<class '__main__.objectcreator'>
>>> def echo(o):
&#x2026;       print o
&#x2026;
>>> echo(ObjectCreator)                 # &#x4F60;&#x53EF;&#x4EE5;&#x5C06;&#x7C7B;&#x505A;&#x4E3A;&#x53C2;&#x6570;&#x4F20;&#x7ED9;&#x51FD;&#x6570;
<class '__main__.objectcreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' #  &#x4F60;&#x53EF;&#x4EE5;&#x4E3A;&#x7C7B;&#x589E;&#x52A0;&#x5C5E;&#x6027;
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # &#x4F60;&#x53EF;&#x4EE5;&#x5C06;&#x7C7B;&#x8D4B;&#x503C;&#x7ED9;&#x4E00;&#x4E2A;&#x53D8;&#x91CF;
>>> print ObjectCreatorMirror()
<__main__.objectcreator object at 0x8997b4c>
</__main__.objectcreator></class></class>

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可

>>> def choose_class(name):
&#x2026;       if name == 'foo':
&#x2026;           class Foo(object):
&#x2026;               pass
&#x2026;           return Foo     # &#x8FD4;&#x56DE;&#x7684;&#x662F;&#x7C7B;&#xFF0C;&#x4E0D;&#x662F;&#x7C7B;&#x7684;&#x5B9E;&#x4F8B;
&#x2026;       else:
&#x2026;           class Bar(object):
&#x2026;               pass
&#x2026;           return Bar
&#x2026;
>>> MyClass = choose_class('foo')
>>> print MyClass              # &#x51FD;&#x6570;&#x8FD4;&#x56DE;&#x7684;&#x662F;&#x7C7B;&#xFF0C;&#x4E0D;&#x662F;&#x7C7B;&#x7684;&#x5B9E;&#x4F8B;
<class '__main__'.foo>
>>> print MyClass()            # &#x4F60;&#x53EF;&#x4EE5;&#x901A;&#x8FC7;&#x8FD9;&#x4E2A;&#x7C7B;&#x521B;&#x5EFA;&#x7C7B;&#x5B9E;&#x4F8B;&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x5BF9;&#x8C61;
<__main__.foo object at 0x89c6d4c>
</__main__.foo></class>

但这还不够动态,因为您仍然需要自己编写整个类。因为类也是对象,所以它们必须从某个对象生成。

[En]

But this is not dynamic enough, because you still need to code the entire class yourself. Because classes are also objects, they must be generated from something.

type能动态的创建类,type可以接受一个类的描述作为参数,然后返回一个类。

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

>>> MyShinyClass = type('MyShinyClass', (), {})  # &#x8FD4;&#x56DE;&#x4E00;&#x4E2A;&#x7C7B;&#x5BF9;&#x8C61;
>>> print MyShinyClass
<class '__main__.myshinyclass'>
>>> print MyShinyClass()  #  &#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x8BE5;&#x7C7B;&#x7684;&#x5B9E;&#x4F8B;
<__main__.myshinyclass object at 0x8997cec>
</__main__.myshinyclass></class>

type 接受一个字典来为类定义属性,因此:

>>> class Foo(object):
&#x2026;       bar = True

等同于:

Foo = type('Foo', (), {'bar':True})

在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

元类就是创建类这种对象的东西。如果你喜欢的话,可以把元类称为”类工厂”。type就是Python的内建元类,当然了,你也可以创建自己的元类。

元类本身而言,它们其实是很简单的:

元类的主要目的就是为了当创建类时能够自动地改变类。

Python 3中创建元类的语法:

class NothingMetaclass(type):
    def __new__(mcs, name, bases, namespace):
         # &#x4EC0;&#x4E48;&#x90FD;&#x6CA1;&#x505A;&#xFF0C;&#x4F60;&#x53EF;&#x4EE5;&#x5728;&#x8FD9;&#x91CC;&#x505A;&#x70B9;&#x4EC0;&#x4E48;
        return type.__new__(mcs, name, bases, namespace)

class Foo(object, metaclass=NothingMetaclass):
    pass
  • new是一个静态方法,而init是一个实例方法.

  • new方法会返回一个创建的实例,而init什么都不返回.

  • 只有在new返回一个cls的实例时后面的init才能被调用.

  • 当创建一个新实例时调用new,初始化一个实例时用init.

看一个例子

# &#x8BF7;&#x8BB0;&#x4F4F;&#xFF0C;'type'&#x5B9E;&#x9645;&#x4E0A;&#x662F;&#x4E00;&#x4E2A;&#x7C7B;&#xFF0C;&#x5C31;&#x50CF;'str'&#x548C;'int'&#x4E00;&#x6837;
# &#x6240;&#x4EE5;&#xFF0C;&#x4F60;&#x53EF;&#x4EE5;&#x4ECE;type&#x7EE7;&#x627F;
class MetaA(type):
    # __new__ &#x662F;&#x5728;__init__&#x4E4B;&#x524D;&#x88AB;&#x8C03;&#x7528;&#x7684;&#x7279;&#x6B8A;&#x65B9;&#x6CD5;
    # __new__&#x662F;&#x7528;&#x6765;&#x521B;&#x5EFA;&#x5BF9;&#x8C61;&#x5E76;&#x8FD4;&#x56DE;&#x4E4B;&#x7684;&#x65B9;&#x6CD5;
    # &#x800C;__init__&#x53EA;&#x662F;&#x7528;&#x6765;&#x5C06;&#x4F20;&#x5165;&#x7684;&#x53C2;&#x6570;&#x521D;&#x59CB;&#x5316;&#x7ED9;&#x5BF9;&#x8C61;
    # &#x4F60;&#x5F88;&#x5C11;&#x7528;&#x5230;__new__&#xFF0C;&#x9664;&#x975E;&#x4F60;&#x5E0C;&#x671B;&#x80FD;&#x591F;&#x63A7;&#x5236;&#x5BF9;&#x8C61;&#x7684;&#x521B;&#x5EFA;
    # &#x8FD9;&#x91CC;&#xFF0C;&#x521B;&#x5EFA;&#x7684;&#x5BF9;&#x8C61;&#x662F;&#x7C7B;&#xFF0C;&#x6211;&#x4EEC;&#x5E0C;&#x671B;&#x80FD;&#x591F;&#x81EA;&#x5B9A;&#x4E49;&#x5B83;&#xFF0C;&#x6240;&#x4EE5;&#x6211;&#x4EEC;&#x8FD9;&#x91CC;&#x6539;&#x5199;__new__
    # &#x5982;&#x679C;&#x4F60;&#x5E0C;&#x671B;&#x7684;&#x8BDD;&#xFF0C;&#x4F60;&#x4E5F;&#x53EF;&#x4EE5;&#x5728;__init__&#x4E2D;&#x505A;&#x4E9B;&#x4E8B;&#x60C5;
    # &#x8FD8;&#x6709;&#x4E00;&#x4E9B;&#x9AD8;&#x7EA7;&#x7684;&#x7528;&#x6CD5;&#x4F1A;&#x6D89;&#x53CA;&#x5230;&#x6539;&#x5199;__call__&#x7279;&#x6B8A;&#x65B9;&#x6CD5;&#xFF0C;&#x4F46;&#x662F;&#x6211;&#x4EEC;&#x8FD9;&#x91CC;&#x4E0D;&#x7528;
    def __new__(cls, name, bases, dct):
        print('MetaA.__new__')
        # &#x8FD9;&#x79CD;&#x65B9;&#x5F0F;&#x4E0D;&#x4F1A;&#x8C03;&#x7528;__init__&#x65B9;&#x6CD5;
        # return type(name, bases, dct)
        # &#x8FD9;&#x79CD;&#x65B9;&#x5F0F;&#x4F1A;&#x8C03;&#x7528;__init__
        return type.__new__(cls, name, bases, dct)

    def __init__(cls, name, bases, dct):
        print('MetaA.__init__')

class A(object, metaclass=MetaA):
    pass

print(A())
class ListMetaclass(type):

    # &#x5143;&#x7C7B;&#x4F1A;&#x81EA;&#x52A8;&#x5C06;&#x4F60;&#x901A;&#x5E38;&#x4F20;&#x7ED9;&#x2018;type&#x2019;&#x7684;&#x53C2;&#x6570;&#x4F5C;&#x4E3A;&#x81EA;&#x5DF1;&#x7684;&#x53C2;&#x6570;&#x4F20;&#x5165;
    # mcs&#x8868;&#x793A;&#x5143;&#x7C7B;
    # name&#x8868;&#x793A;&#x521B;&#x5EFA;&#x7C7B;&#x7684;&#x7C7B;&#x540D;&#xFF08;&#x5728;&#x8FD9;&#x91CC;&#x521B;&#x5EFA;&#x7C7B;&#x5C31;&#x662F;&#x7EE7;&#x627F;Model&#x7C7B;&#x7684;&#x5B50;&#x7C7B;User&#xFF09;
    # bases&#x8868;&#x793A;&#x521B;&#x5EFA;&#x7C7B;&#x7EE7;&#x627F;&#x7684;&#x6240;&#x6709;&#x7236;&#x7C7B;
    # namespace&#x8868;&#x793A;&#x521B;&#x5EFA;&#x7C7B;&#x7684;&#x6240;&#x6709;&#x5C5E;&#x6027;&#x548C;&#x65B9;&#x6CD5;&#xFF08;&#x4EE5;&#x952E;&#x503C;&#x5BF9;&#x7684;&#x5B57;&#x5178;&#x7684;&#x5F62;&#x5F0F;&#xFF09;
    def __new__(mcs, name, bases, namespace):
        namespace['add'] = lambda self, value: self.append(value)
        return type.__new__(mcs, name, bases, namespace)

# &#x901A;&#x8FC7;metaclass&#xFF0C;&#x7ED9;&#x8BE5;&#x7C7B;&#x52A8;&#x6001;&#x6DFB;&#x52A0;&#x4E86;add&#x65B9;&#x6CD5;
class MyList(list, metaclass=ListMetaclass):
    pass

l = MyList()
l.add(1)
print(l)

现在回到我们的大话题,你到底为什么要使用这样一个容易出错和晦涩难懂的功能?嗯,一般来说,你根本不需要它:

[En]

Now back to our big topic, why on earth do you use such an error-prone and obscure feature? Well, generally speaking, you don’t need it at all:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” ——

Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义

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

然后可以通过简单点API操作数据库。其背后的魔法就是定义了元类,并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。

class Field:
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

class ModelMetaclass(type):
    def __new__(mcs, name, bases, attrs):
        if name == 'Model':
            return type.__new__(mcs, name, bases, attrs)
        print("Found Model: %s" % name)
        mapping = dict()
        fields = list()
        # &#x5C06;&#x5C5E;&#x6027;&#x4FDD;&#x5B58;&#x5230;mapping&#x4E2D;
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping : %s ==> %s' % (k, v))
                mapping[k] = v
                fields.append(k)
        # &#x5C06;Model&#x4E2D;&#x7684;Field&#x5220;&#x9664;
        for k in mapping.keys():
            attrs.pop(k)

        attrs['__fields__'] = list(map(lambda f: '%s' % f, fields))
        attrs['__mapping__'] = mapping
        attrs['__table__'] = name
        return type.__new__(mcs, name, bases, attrs)

class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        super(Model, self).__init__(kwargs)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []

        for k, v in self.__mapping__.items():
            print("%s------%s" % (k, v))
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))

        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(self.__fields__), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

class User(Model):
    # &#x5B9A;&#x4E49;&#x7C7B;&#x7684;&#x5C5E;&#x6027;&#x5230;&#x5217;&#x7684;&#x6620;&#x5C04;&#xFF1A;
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
</%s:%s>

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类

Original: https://www.cnblogs.com/niuben/p/16334921.html
Author: 牛奔
Title: 理解Python中的元类(metaclass)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/9840/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

发表回复

登录后才能评论
免费咨询
免费咨询
扫码关注
扫码关注
联系站长

站长Johngo!

大数据和算法重度研究者!

持续产出大数据、算法、LeetCode干货,以及业界好资源!

2022012703491714

微信来撩,免费咨询:xiaozhu_tec

分享本页
返回顶部