Python高级用法总结--元类

type()

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义,而是运行时动态创建的。

比方说我们要定义一个 Hello 的 class ,就写一个hello.py 模块:

class Hello(object):
        def hello(self, name='world'):
                print('Hello, %s' %name)

当 Python 解释器载入 hello 模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个 Hello 的 class 对象,测试如下:

from hello import Hello

h = Hello()

h.hello() # Hello,world

print(type(Hello)) # <type 'type'>

print(type(h))  #<class 'hello.Hello'>

type() 函数可以查看一个类型或变量的类型,Hello 是一个class,它的类型就是 type ,而 h 是一个实例,它的类型就是 class Hello。

我们说 class 的定义是运行时动态创建的,而创建 class 的方法就是使用 type() 函数。

type() 函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过 type() 函数创建出 Hello 类,而无需通过 class Hello(object) 来定义。

def fn(self, name='world'):  # 先定义函数
        print('Hello, %s'%name)

Hello = type('Hello', (object,), dict(hello=fn))  # 创建Hello class

h = Hello()

h.hello()   # Hello, world

print(type(Hello))  # <type 'type'>

print(type(h))  # <class '__main__.Hello'>

要创建一个 class 对象, type() 函数依次传入3个参数:

  1. class 的名称。
  2. 继承的父类集合,注意 Python 支持多重继承,如果只有一个父类,别忘了 tuple 的单元素写法。
  3. class 的方法名称与函数绑定,这里我们把函数 fn 绑定到方法名上。

通过 type() 函数创建的类和直接写 class 是完全一样的,因为Python 解释器遇到 class 定义时,仅仅是扫描一下 class 定义的语法,然后调用 type() 函数创建出 class。

正常情况下,我们都用 class xxx 来定义类,但是, type() 函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,非常复杂。

metaclass

除了使用 type() 动态创建类以外,要控制类的创建行为,还可以使用 metaclass。

metaclass 直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据 metaclass 创建出类,所以:先定义 metaclass ,然后创建类。

连接起来就是:先定义metaclass ,就可以创建类,最后创建实例。

所以,metaclass 允许你创建类或者修改类。换句话说,你可以把类看出是 metatclass 创建出来的实例。

metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。支持正常情况下,我们不会碰到。

我们先看一个简单的例子,这个metaclass 的类名总是 Metaclass 结尾,以便清楚地表示这是一个 metaclass:

# metaclass 是创建类,所以必须从 type 类型派生
class ListMetaclass(type):
        def __new__(cls, name, bases, attrs):
                attrs['add'] = lambda self, value: self.paaned(value)
                return type.__new__(cls, name, bases, attrs)

class MyList(list):
        __etaclass__ = ListMetaclass  # 指示使用ListMetaclass 来定制类

当我们写下 __metaclass__ = ListMetaclass 语句时,魔术就生效了,它指示 Python 解释器在创建 MyList 时,要通过ListMetaclass.__new__() 来创建,在此,我们可以修改类的定义,比如加上新的方法,然后,返回修改后的定义。

__new__() 方法接收到的参数依次是:

  1. 当前准备创建的类的对象。
  2. 类的名字。
  3. 类继承的父类集合。
  4. 类的方法集合

测试一下 MyList 是否可以调用 add() 方法:

L= MyList()
L.add(1)
print L # [1]

而普通的 list 没有 add() 方法

l = list()
l.add()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'add'

动态修改有什么意义? 直接在MyList 定义中写上 add() 方法不是更加简单吗? 正常情况先,确实应该直接写,通过metaclass 修改纯属变态。

但是,总会遇到需要通过 metaclass 修改类的定义的。

ORM 就是一个典型的例子

ORM 全称 'Object Relational Mapping ' 即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作 SQL 语句。

要编写一个 ORM 框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

实例:编写 ORM 框架

编写底层模块的第一步,就是先把调用接口写出了。比如,使用者如果使用这个ORM框架,想定义一个 User 类来操作对应的数据库表 User。

class User(Model):
        # 定义类的属性到列的映射
        id = IntegerField('id')
        name = StringField('username')
        email = StringField('email')
        password = StringField('password')

# 创建一个实例
u = User(id=123, name='Michael', email='[email protected]', password='pwd')
# 保存到数据库
u.save()

其中,父类 Model 和属性类型 StringField 、IntegerField 是由ORM框架提供的,剩下的魔术方法比如 save() 全部由 metaclass 自动完成。虽然 metaclass 的编写会比较复杂,但ORM 的使用者用起来却异常简单。

现在,我们就按照上面的接口来实现 ORM

首先来定义 Field 类,它负责保存数据库表的字段名和字段类型:

class Field(object):
        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)

在 Field 的基础上,进一步定义各种类型的 Field ,比如 StringFielf、IntegerField 等等:

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

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

下一步,就是编写最复杂的 ModelMateclass 了:

class ModelMetaclass(type):
        def __new__(cls, name, bases, attrs):
                if name=='Model':
                        return type.__new__(cls, name, bases, attrs)
                mappings = dict()
                for k, v in attrs.iteritems():
                        if isinstance(v, Field):
                                print('Fund mapping: %s==>%s' % (k, v))
                                mappings[k]  = v
                for k in mappings.iterkeys():
                        attrs.pop(k)

                attrs['__table__'] = name # 假设表名和类名一致
                attrs['__mappings__'] = mappings # 保存属性和列的映射关系
                return type.__new__(cls, name, bases, attrs)

以及基类 Model:

class Model(dict):
        __metaclass__ = ModelMetaclass

        def __init__(slef, **kw):
                super(Model, self).__init__(**kw)

        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.__mappings__.iteritems():
                        fields.append(v.name)
                        params.append('?')
                        args.append(getattr(self, k, None))
                sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
                print('SQL: %s' % sql)
                print('ARGS: %s' % str(args))

当用户定义一个 class User(Model) 时, Python 解释器首先在当前类 User 的定义中查找 __metaclass__ ,如果没有找到,就继续在父类 Model 中查找 __metaclass__ 。找到了,就使用 Model 中定义的 __metaclass__ 的ModelMetaclass 来创建 User 类,也就是说, metaclass 可以隐式地继承到子类,但子类自己却感觉不到。

在 ModelMetaclass 中,一共做了几件事情:

  1. 排除掉对 Model 类的修改。
  2. 在当前类(比如User)中查找定义的类所有属性,如果找到一个 Field 属性,就把它保存到一个 __mappings__ 的 dict 中,同时从类属性中删除该 Field 属性,否则,容易造成运行时错误
  3. 把表名保存到 __table__ 中,这里简化为表名默认为类名。

在 Model 类中,就可以定义各种操作数据库的方法,比如 save(), delete(), find(), update() 等等。

编写测试代码:

u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

# 输出如下
Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,uid) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]

最后解释一下类属性和实例属性。直接在class中定义的是类属性:

class Student(object):
    name = 'Student'

实例属性必须通过实例来绑定,比如self.name = 'xxx'。来测试一下:

>>> # 创建实例s:
>>> s = Student()
>>> # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性:
>>> print(s.name)
Student
>>> # 这和调用Student.name是一样的:
>>> print(Student.name)
Student
>>> # 给实例绑定name属性:
>>> s.name = 'Michael'
>>> # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性:
>>> print(s.name)
Michael
>>> # 但是类属性并未消失,用Student.name仍然可以访问:
>>> print(Student.name)
Student
>>> # 如果删除实例的name属性:
>>> del s.name
>>> # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了:
>>> print(s.name)
Student


因此,在编写程序的时候,千万不要把实例属
性和类属性使用相同的名字。

在我们编写的ORM中,ModelMetaclass会删除掉User类的所有类属性,目的就是避免造成混淆。

摘抄至https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820064557c69858840b4c48d2b8411bc2ea9099ba000

猜你喜欢

转载自www.cnblogs.com/xushuhai/p/10131475.html