type()动态创建类
Python创建类:运行时动态创建
(内部创建)方法:type()
type()接收参数:(calss名称,
class继承的父类集合(如只有一个父类,参照tuple单元素写法(xxx,))
class的方法名称与函数绑定)
Eg:
>>> 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)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>
一般使用时class Hello(object):......就可以创建
另外type还可用于检测对象类型
metaclass元类 又一创建类的魔法类,控制类的创建行为
一般顺序:先定义类,然后创建实例。
但如果想创建出类呢?:先定义metaclass,然后创建类。metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
最终结论:先定义metaclass,就可以创建类,最后创建实例。
metaclass使用步骤(创建xxx类):
1. 定义XXXMetaclass()类
# metaclass是类的模板,所以必须从`type`类型派生: class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs)
__new__()方法接收参数:
(当前准备创建的类的对象,
类的名字,
类继承的父类集合,
类的方法集合)
2.创建XXX类
使用关键字metaclass,传入metaclass
作用:它指示Python解释器在创建MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
class MyList(list, metaclass=ListMetaclass): pass
3.创建实例调用
>>> L = MyList() >>> L.add(1) >> L [1]
所以问题来了,为什么不直接在Mylist中定义add方法呢?额一般来说就是这么做的,metaclass有更特殊的应用场景
metaclass应用场景之 —— ORM框架
定义:“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
要编写一个ORM框架,所有的类都只能动态定义,方便添加(属性)改动表
假设使用者想要这样定义表-对象
class User(Model): # 定义类的属性到列的映射: id = IntegerField('id') name = StringField('username') email = StringField('email') password = StringField('password') # 创建一个实例: u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd') # 保存到数据库: u.save()
- 直接在类中定义属性,调用相关类型函数传入属性名称,得到具有指定类型的属性
- 父类
Model
和属性类型StringField
、IntegerField
是由ORM框架提供的,剩下的魔术方法比如save()
全部由metaclass自动完成。
StringFiled,IntergerField等由单独类实现,步骤如下
1.首先来定义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)
2.在Field的基础上,进一步定义各种类型的Field
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')
编写Model类的元类——ModelMetaclass
class ModelMetaclass(type): def __new__(cls, name, bases, attrs): if name=='Model': return type.__new__(cls, name, bases, attrs)
#当然这两句不是什么重点-------------
print('Found model: %s' % name) mappings = dict()
#---------------------
#attrs是传入类的属性方法集合
#k是属性或方法的类型
#v是属性或方法的值
#筛选出类型是Field(当然子类也算比如StringField)的对象(比如id)
for k, v in attrs.items():
if isinstance(v, Field):#如果v是Field的实例对象
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v #将其添加到mapping
for k in mappings.keys():#遍历mapping,将其从attr中pop
attrs.pop(k)
#最后为正在创建的对象/类,添加两个属性 attrs['__mappings__'] = mappings # 保存属性和列的映射关系 attrs['__table__'] = name # 假设表名和类名一致 return type.__new__(cls, name, bases, attrs)
代码解释:
1.排除掉对Model
类的修改;
2.当前类(比如User
)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__
的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);
3.把表名保存到__table__
中,这里简化为表名默认为类名。
!1:第一个for循环有点难搞,贴一个小test
(出自:https://www.liaoxuefeng.com/discuss/969955749132672/1092153449118144)
class ModelMetaclass(type): def __new__(cls, name, bases, attrs): for k, v in attrs.items(): print('key:%s,value:%s' % (k, v)) return type.__new__(cls, name, bases, attrs) class Test(object, metaclass=ModelMetaclass): name = "dlj" age = 123 def aaa(self): pass
a = Test() 打印结果: key:name,value:dlj key:__module__,value:__main__ key:aaa,value:<function Test.aaa at 0x109fa10d0> key:age,value:123 key:__qualname__,value:Test
按照后续调用(在Model子类中)比如
class User(Model): # 定义类的属性到列的映射: id = IntegerField('id') name = StringField('username') email = StringField('email') password = StringField('password')
得到的key: password , value:<StringField:password>,这个可以翻到最后看结果,value格式在Field中定义过
!2:Python类的__new__()方法和metaclass的__new__()是有区别滴
(出自:https://blog.csdn.net/oqqZhe1234/article/details/95944314)
区别1:作用不同
类的__new()__ 方法是返回一个对象;
(解说见https://blog.csdn.net/qq_41020281/article/details/79638370)
而metaclass是返回一个类(其实类也是type的一个对象,万物皆为对象啊
区别2:时间不同:
前者在生成对象的时候调用,后者在定义类的时候调用。
类中定义__new()__的优点:
通过在父类中定义__new()__,子类在生成对象的时候可以临时修改该子类的类属性(注意这里是临时! 因为每定义一个子类对象都会运行___new()__);而使用metaclass需要定义一个父类和一个metaclass,比较麻烦,代码也不简洁。
使用metaclass的优点:
在定义子类的时候就执行了metaclass的__new()__,不用在每生成一个对象的时候分别再调用,比上者效率要高。虽然还得多写一个metaclass,代码也不简洁,但是metaclass还是优先提倡的。
编写基类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 = [] 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))
代码解释:
1.super(Model, self).__init__(**kw),初始化时设置指定属性-值
首先找到Model的父类(dict),然后把类Model的对象self转换为类dict的对象,然后转换后的dict对象调用__init__,传入关键字参数**kw
2.__getattr__和__setattr__实现动态存取属性-值
3.在Model
类中,定义各种操作数据库的方法,比如save()
,delete()
,find()
,update
等
代码中实现了save函数--Insert操作
for循环:
首先遍历属性列表,将属性放入fields
占位符?放入params
参数放入args,不存在返回None
创建sql语句:
'分隔符'.join(要连接的序列) 将序列中的元素以指定的字符连接生成一个新的字符串。(','.join(['a','b','c','d']->'a,b,c,d'))
我们实现了save()
方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT
语句。
Q:当用户定义一个class User(Model)
时,Python解释器做了什么?
首先在当前类User
的定义中查找metaclass
如果没有找到,就继续在父类Model
中查找metaclass
,
找到了,就使用Model
中定义的metaclass
的ModelMetaclass
来创建User
类,
也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。
最后是测试代码:
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,id) values (?,?,?,?) ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]
Over...这分析真是让人头秃