Python元编程入门



Python元编程-入门

最近准备着手阅读sqlalchemy的源码,一个ORM框架离不开元编程,因此整理了一下python中元编程的相关知识。话不多说,下面进入正题。
P.S. 文中代码都是基于python3.6环境

基本概念

大家都知道python中的对象都是通过类的__new__方法创建,__init__方法进行初始化,
同时Python中一切皆对象, 类不用说也是一个对象,那么类又是如何创建的呢,这就引出了元类(metaclass)的概念。
我们使用type函数可以知道一个实例是通过哪个类实例化的,如果type的参数是一个类呢?

In [10]:
# example 1.1
print(type(object))
# using instance to verify
assert isinstance(object, type)
<class 'type'>

这里可以看出object的类型是type,也就是说type创建了object类,这里的type就是一个元类。
元类就是创建类的类,类实例化会创建一个实例,类似的,元类实例化就会创建一个类。

type是所有元类的基类,也是它自己的基类。

我们再来看一下type是如何创建类的。

以下摘录自官方文档 https://docs.python.org/3.6/library/functions.html#type

class type(name, bases, dict)
With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.__class__.

...

With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the __name__ attribute; the bases tuple itemizes the base classes and becomes the __bases__ attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the __dict__ attribute. For example, the following two statements create identical type objects:

简单来说,如果参数是三个的时候,参数和生成的类的属性有如下对应关系

  • name  -> __name__
  • bases -> __bases__
  • dict     -> __dict__
In [1]:
# example 1.2
type('when using type with a single argument, it returns the class of argument')

MyObject = type('MyObject', (object,), {'a': 1})
assert isinstance(MyObject, type)
MyObject.__name__, MyObject.__bases__, MyObject.__dict__
Out[1]:
('MyObject',
 (object,),
 mappingproxy({'a': 1,
               '__module__': '__main__',
               '__dict__': <attribute '__dict__' of 'MyObject' objects>,
               '__weakref__': <attribute '__weakref__' of 'MyObject' objects>,
               '__doc__': None}))

元类的继承

In [19]:
# example 2.1

# 创建一个元类
class Meta(type):
    c = 1
    def __new__(mcs, *args, **kwargs):
        print("I'm creating a class using metaclass {}".format(mcs.__name__))
        cls = super().__new__(mcs, *args, **kwargs)       
        print('Now I got a class named {name}'.format(name=cls.__name__))
        print(cls.a, cls.hello)
        return cls
    
    def __init__(cls, *args, **kwargs):
        print("I'm initializing class {} in metaclass {}".format(
            cls.__name__, type(cls).__name__))
        super().__init__(*args, **kwargs)
        cls.b = 2 # 给类绑定变量
        print(cls.__name__, cls.__bases__, cls.__dict__)
    
    def assign_to_class(cls):
        print('I got a method from metaclass')

# 用定义的元类构造一个类
class MyObject(object, metaclass=Meta):
    a = 1
    def __new__(cls, *args, **kwargs):
        self = super().__new__(cls, *args, **kwargs)       
        return self
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
    @classmethod
    def name(cls):
        return cls.__name__
    
    @property
    def hello(self):
        return 'hello'
MyObject.a, MyObject.b, MyObject.c, MyObject.assign_to_class(), 
    
I'm creating a class using metaclass Meta
Now I got a class named MyObject
1 <property object at 0x10e82f958>
I'm initializing class MyObject in metaclass Meta
MyObject (<class 'object'>,) {'__module__': '__main__', 'a': 1, '__new__': <staticmethod object at 0x10e82b320>, '__init__': <function MyObject.__init__ at 0x10e80c1e0>, 'name': <classmethod object at 0x10e82b240>, 'hello': <property object at 0x10e82f958>, '__dict__': <attribute '__dict__' of 'MyObject' objects>, '__weakref__': <attribute '__weakref__' of 'MyObject' objects>, '__doc__': None, 'b': 2}
I got a method from metaclass
Out[19]:
(1, 2, 1, None)

通过以上示例可以看出,MyObject类生成的时候,先调用元类Meta的__new__,此时MyObject中的实例方法,类方法,类变量已经被绑定,然后是__init__,在元类的init方法中可以将类当做实例一样,可以给类绑定变量,方法等等。
MyObject是Meta的一个实例,因此像对象调用类定义的方法和属性一样,MyObject也可以调用Meta的属性和方法

元类可以用来实现很多“黑魔法”,form表单验证,orm模型都可以用它来实现。因为元类拥有对类的生杀大权,所以很容易写出难以定位的bug,如果不是经验丰富的老司机,尽量要避免使用。

最后总结一下,元类是类的类,类是元类的实例,type函数是一切元类的基类,也是自己的基类。 面向对象的本质就是给数据绑定方法,在python中完全可以不经过继承,直接通过type来创建类,同时为它绑定方法。

后续在读sqlalchemy源码的过程中会继续深入讨论元编程的应用。

Comments