本文主要介绍Python的MetaClass的用法。

MetaClass也是一个class,这个class继承自type,它用于生产新的class对象。定义class的时候,就会执行到MetaClass的__new__()代码。

从这里出发,我们需要一个新的class的时候,就可以使用MetaClass定义它的行为。本文介绍MetaClass的三种用法:

  1. 定义新的class的时候,执行一些检查操作
  2. 定义新的class的时候,将新class进行注册动作
  3. 定义新的class的时候,自动修改class的属性

MetaClass可以读取和修改class的属性。

Python

1

2

3

4

5

6

7

8

9

10

11

class Meta(type):

       def __new__(meta, name, bases, class_dict):

           print((meta, name, bases, class_dict))

           return type.__new__(meta, name, bases, class_dict)

class MyClass(object, metaclass=Meta):

   stuff = 123

   def foo(self):

           pass

# (<class '__main__.Meta'>, 'MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 123, 'foo': <function MyClass.foo at 0x10af271e0>})Python

Python2和Python3的写法有所不同,上面是Python3的写法,Python2的写法如下。

Python

1

2

3

4

5

6

7

8

9

10

class Meta(type):

     def __new__(meta, name, bases, class_dict):

         print((meta, name, bases, class_dict))

         return type.__new__(meta, name, bases, class_dict)

class MyClass(object):

     __metaclass__ = Meta

     stuff = 123

     def foo(self):

           pass

定义一个新的class相当于调用type创建一个新的class对象,所以覆盖__new__()方法可以实现一些验证的动作。

下面的代码定义了一个多边形的MetaClass,在定义新的class的时候,验证边数是否小于3,如果小于3,这不科学。

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

# -*- coding: utf-8 -*-

class ValidatePolygon(type):

    def __new__(meta, name, bases, class_dict):

        # Don’t validate the abstract Polygon class

        if bases != (object,):

            if class_dict['sides'] < 3:

                raise ValueError(Polygons need 3+ sides)

        return type.__new__(meta, name, bases, class_dict)

class Polygon(object, metaclass=ValidatePolygon):

    sides = None # Specified by subclasses

    @classmethod

    def interior_angles(cls):

        return (cls.sides - 2) * 180

class Triangle(Polygon):

    sides = 3

使用MetaClass对class进行注册

下面的代码,在每次定义一个新的class的时候,就将class注册到一个字典,维护对应的行为。而不必每次都手动进行维护。

Python

1

2

3

4

5

6

7

8

9

class Vector3D(RegisteredSerializable):

       def __init__(self, x, y, z):

           super().__init__(x, y, z)

           self.x, self.y, self.z = x, y, z

   v3 = Vector3D(10, -7, 3)

   print(Before:    , v3)

   data = v3.serialize()

   print(Serialized:, data)

   print(After:     , deserialize(data))

修改class的属性

假设我们想要实现一个Python对象到数据库字段的对应(也就是ORM),我们可以使用前面博客提到的Descriptor,如下。

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

class Field(object):

       def __init__(self, name):

           self.name = name

           self.internal_name = _ + self.name

       def __get__(self, instance, instance_type):

           if instance is None: return self

           return getattr(instance, self.internal_name, )

       def __set__(self, instance, value):

           setattr(instance, self.internal_name, value)

class Customer(object):

       # Class attributes

       first_name = Field(first_name’)

       last_name = Field(last_name’)

       prefix = Field(prefix)

       suffix = Field(suffix)

但是这里存在的问题是,first_name已经写了一次了,还要在创建Field的时候再写一次,不是有些多余吗?Field应该自动根据引用它的变量名自动命名。这可以通过MetaClass来做到。

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

class Meta(type):

       def __new__(meta, name, bases, class_dict):

           for key, value in class_dict.items():

               if isinstance(value, Field):

                   value.name = key

                   value.internal_name = _ + key

           cls = type.__new__(meta, name, bases, class_dict)

           return cls

# Field不需要在接受参数创建实例了

class Field(object):

       def __init__(self):

           # These will be assigned by the metaclass.

           self.name = None

           self.internal_name = None

# 这里由MetaClass代劳

class BetterCustomer(meta=Meta):

       first_name = Field()

       last_name = Field()

       prefix = Field()

       suffix = Field()

综上,MetaClass是非常实用的,可以帮我们减少很多需要重复复制粘贴的代码。

参考:Effective Python 59 SPECIFIC WAYS TO WRITE BETTER PYTHON by Brett Slatkin, Item 33,34,35