Python元类

type()

>>> 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实际上是一个元类。比如,我们可以通过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绑定到方法名hello上。

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

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

metaclass

我们在创建一个类的时候,Python会在自定义中寻找metaclass属性,如果找到了,Python就会用它来创建类,如果没有找到,就会用内建的type来创建这个类。举个栗子:

class Foo(Main):
    pass

Python做了如下的操作:

首先,Python会在Foo类里面寻找metaclass属性,很明显,我们并没有写这个属性,所以他会去父类Main中去找这个属性。当父类中也没有这个属性的时候,

它就会在模块层次中去寻找metaclass,并尝试做同样的操作。如果还是找不到metaclass,Python就会用内置的type来创建这个类对象。当然如果在本地就有

metaclass属性,Python会在内存中通过metaclass创建一个名字为Foo的类(当然,这个类也是一个对象)

那么,metaclass是拿来做什么的呢?

一般来说,我们不会使用元类来改变类的属性,因为那会使你的代码变得晦涩,不过在编写API时这会变得很方便。

再举个栗子,比如我现在做一个让我的一个类里面所有属性名都变为大写。有好几种方法可以办到,但其中一种就是通过在模块级别设定metaclass。采用这种方法,这个模块

中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

class UpperAttrMetaclass(type): # 按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)


class Foo(metaclass=UpperAttrMetaclass):
    bar = 'fib'


print(hasattr(Foo, 'bar'))
print(hasattr(Foo, 'BAR'))

返回的结果:

False
True

在上面的代码中我们使用到了new方法,new 是在init之前被调用的特殊方法,用来创建对象并返回这个对象。

其实元类所做的事情主要有以下三件:

1.拦截类的创建

2.修改类

3.返回修改之后的类

那么为什么要使用元类以及它的主要用途在哪里呢?

“If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)” —— Python界的领袖 Tim Peters

元类的主要用途就是用来创建API,ORM就是一个典型的例子。我们来做一个简单的ORM框架(摘录自廖雪峰老师网站)。

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

class User(Model):
    # 定义类的属性到列的映射:
    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()

其中,父类Model和属性类型StringFieldIntegerField是由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,比如StringFieldIntegerField等等:

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

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

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.items():
            if isinstance(v, Field):
                print('Found mapping: %s==>%s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__table__'] = name # 假设表名和类名一致
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        return type.__new__(cls, name, bases, attrs)

以及基类Model

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **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 = []
        print(self.__mappings__)
        for k, v in self.__mappings__.items():
            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))

最后,做个测试;

x = {"id": 123, "name": "Michael", "email": "test@orm.org", "password": "xxz"}

u = User(**x)
u.save()

结果:

Found model: User
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (uid,username,email,password) values (?,?,?,?)
ARGS: [123, 'Michael', 'test@orm.org', 'xxz']

那么上面的ModelMetaclass做了哪些事呢?

1.如果得到的类名为Model,那么不对其属性就行修改;

2.在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该

Field属性,为什么要这么做呢,下面我会讲到这方面;

3.把表名保存到table中,这里简化为表名默认为类名。

那么为什么我们要删除类的属性呢?因为生成的实例中有id,name,email,password这些属性,为了避免混淆需要删除类的中相同名字的属性。否则我们得到的
结果会是这样:

Found model: User
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (uid,username,email,password) values (?,?,?,?)
ARGS: [<__main__.IntegerField object at 0x10670c470>, <__main__.StringField object at 0x10670c4a8>, <__main__.StringField object at 0x10670c4e0>, <__main__.StringField object at 0x10670c518>]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值