. 1. 类是如何产生的
类是如何产生? 这个问题肯定很傻. 实则不然, 很多人只知道使用继承的表面形式来创建一个类, 却不知道其内部真正的创建是由 type 来创建的.
type? 这不是判断对象类型的函数吗?
是的, type 通常用法就是用来判断对象的类型. 但除此之外, 他最大的用途是用来动态创建类. 当 Python 扫描到 class 的语法的时候, 就会调用 type 函数进行类的创建.
. 2. 如何使用 type 创建类
首先, type() 需要接收三个参数
类的名称: 若不指定, 也要传入空字符串:""
父类: 注意以 tuple 的形式传入, 若没有父类也要传入空 tuple:(), 默认继承 object
绑定的方法或属性
: 注意以 dict 的形式传入
来看个例子
1# 准备一个基类 (父类) 2class BaseClass: 3 def talk(self): 4 print("i am people") 5 6# 准备一个方法 7def say(self): 8 print("hello") 910# 使用 type 来创建 User 类 11User = type("User", (BaseClass, ), {"name":"user", "say":say})
. 3. 理解什么是元类
什么是类? 可能谁都知道, 类就是用来创建对象的模板.
那什么是元类呢? 一句话通俗来说, 元类就是创建类的模板.
为什么 type 能用来创建类? 因为它本身是一个元类. 使用元类创建类, 那就合理了.
type 是 Python 在背后用来创建所有类的元类, 我们熟知的类的始祖 object 也是由 type 创建的. 更有甚者, 连 type 自己也是由 type 自己创建的, 这就过份了.
1>>> type(type)2<class 'type'>3>>> type(object4<class 'type'>5>>> type(int)6<class 'type'>7>>> type(str)8<class 'type'>
如果要形象的来理解的话, 就看下面这三行话.
1str: 用来创建字符串对象的类. 2int: 是用来创建整数对象的类. 3type: 是用来创建类对象的类.
反过来看
1 一个实例的类型, 是类 2 一个类的类型, 是元类 3 一个元类的类型, 是 type
来看下实例
1# Python3.7 2>>> class MetaPerson(type): 3... pass 4... 5>>> class Person(metaclass=MetaPerson): 6... pass 7... 8>>> Tom = Person() 9>>> print(type(Tom))10<class '__main__.Person'>11>>> print(type(Tom.__class__))12<class '__main__.MetaPerson'>13>>> print(type(Tom.__class__.__class__))14<class 'type'>
上面是一个简单的示例.
下面看一个稍微完整的
1# 注意要从 type 继承 2class BaseClass(type): 3 def __new__(cls, *args, **kwargs): 4 print("in BaseClass") 5 return super().__new__(cls, *args, **kwargs) 6 7class User(metaclass=BaseClass): 8 def __init__(self, name): 9 self.name = name1011user = User("wangbm")
. 4. 使用元类的意义
正常情况下, 我们都不会使用到元类. 但是这并不意味着, 它不重要. 假如某一天, 我们需要写一个框架, 很有可能就需要用到元类.
但是, 为什么要用它呢? 不要它会怎样?
经过我的总结, 元类的作用过程如下
拦截类的创建
拦截下后, 进行修改
修改完后, 返回修改后的类
很明显, 使用元类, 是要对类进行定制修改. 使用元类来动态生成元类的实例, 而 99% 的开发人员是不需要动态修改类的, 因为这应该是框架才需要考虑的事.
但是, 这样说, 你一定不会服气, 到底元类用来干什么? 其实元类的作用就是创建 API, 一个最典型的应用是 Django ORM.
. 5. 元类实战: ORM
使用过 Django ORM 的人都知道, 有了 ORM, 使得我们操作数据库, 变得异常简单.
ORM 的一个类 (User), 就对应数据库中的一张表. id,name,email,password 就是字段.
1class User(BaseModel):2 id = IntField('id')3 name = StrField('username')4 email = StrField('email')5 password = StrField('password')67 class Meta:8 db_table = "user"
如果我们要插入一条数据, 我们只需这样做
1# 实例化成一条记录 2u = User(id=20180424, name="xiaoming", 3 email="xiaoming@163.com", password="abc123")45# 保存这条记录 6u.save()
通常用户层面, 只需要懂应用, 就像上面这样操作就可以了.
但是今天我并不是来教大家如何使用 ORM, 我们是用来探究 ORM 内部究竟是如何实现的. 我们也可以自己写一个简易的 ORM.
从上面的 User 类中, 我们看到 StrField 和 IntField, 从字段意思上看, 我们很容易看出这代表两个字段类型. 字段名分别是 id, username,email,password.
StrField 和 IntField 在这里的用法, 叫做 属性描述符, 如果对这个不了解的可以查看文章底部的参考文章, 也是我写的. 简单来说呢, 属性描述符可以实现对属性值的类型, 范围等一切做约束, 意思就是说变量 id 只能是 int 类型, 变量 name 只能是 str 类型, 否则将会抛出异常.
那如何实现这两个属性描述符呢? 请看代码.
1import numbers 2 3class Field: 4 pass 5 6class IntField(Field): 7 def __init__(self, name): 8 self.name = name 9 self._value = None1011 def __get__(self, instance, owner):12 return self._value1314 def __set__(self, instance, value):15 if not isinstance(value, numbers.Integral):16 raise ValueError("int value need")17 self._value = value1819class StrField(Field):20 def __init__(self, name):21 self.name = name22 self._value = None2324 def __get__(self, instance, owner):25 return self._value2627 def __set__(self, instance, value):28 if not isinstance(value, str):29 raise ValueError("string value need")30 self._value = value
我们看到 User 类继承自 BaseModel, 这个 BaseModel 里, 定义了数据库操作的各种方法, 譬如我们使用的 save 函数, 也可以放在这里面的. 所以我们就可以来写一下这个 BaseModel 类
1class BaseModel(metaclass=ModelMetaClass): 2 def __init__(self, *args, **kw): 3 for k,v in kw.items(): 4 # 这里执行赋值操作, 会进行数据描述符的__set__逻辑 5 setattr(self, k, v) 6 return super().__init__() 7 8 def save(self): 9 db_columns=[]10 db_values=[]11 for column, value in self.fields.items():12 db_columns.append('`'+str(column)+'`')13 _value=str(getattr(self, column))14 db_values.append('\''+_value+'\'')15 sql = "insert into `{table}` ({columns}) values({values})".format(16 table=self.db_table,17 columns=','.join(db_columns),18 values=','.join(db_values))19 # mysql_execute 函数可以自己写. 调用驱动插入到数据库 20 # 查看完整代码请点击文章底部查看原文 21 mysql_execute(sql)
从 BaseModel 类中, save 函数里面有几个新变量,
fields: 存放所有的字段属性
db_table: 表名
注意: 上面代码中 class BaseModel(metaclass=ModelMetaClass) 请替换成 class BaseModel(object) 再阅读. 这样更贴合思考顺序.
我们思考一下这个 u 实例的创建过程:
type -> object -> BaseModel -> User -> u
这里会有几个问题.
init 的参数是 User 实例时传入的, 所以传入的 id 是 int 类型, name 是 str 类型. 看起来没啥问题, 若是这样, 我上面的数据描述符就失效了, 不能起约束作用. 所以我们希望 init 接收到的 id 是 IntField 类型, name 是 StrField 类型.
同时, 我们希望这些字段属性, 能够自动归类到 fields 变量中. 因为, 做为 BaseModel, 它可不是专门为 User 类服务的, 它还要兼容各种各样的表. 不同的表, 表里有不同数量, 不同属性的字段, 这些都要能自动类别并归类整理到一起. 这是一个 ORM 框架最基本的.
我们希望对表名有两种选择, 一个是 User 中若指定 Meta 信息, 比如表名, 就以此为表名, 若未指定就以类名的小写 做为表名. 虽然 BaseModel 可以直接取到 User 的 db_table 属性, 但是如果在数据库业务逻辑中, 加入这段复杂的逻辑, 显然是很不优雅的.
上面这几个问题, 其实都可以通过元类的__new__函数来完成.
元类的__new__和普通类的可不一样, 元类的__new__, 可以获取到上层类的一切属性和方法, 包括类名, 魔法方法. 而普通类的__new__ 只能获取到实例化时外界传入的属性.
下面就来看看, 如何用元类来解决这些问题呢? 请看代码.
1class ModelMetaClass(type): 2 def __new__(cls, name, bases, attrs): 3 if name == "BaseModel": 4 # 第一次进入__new__是创建 BaseModel 类, name="BaseModel" 5 # 第二次进入__new__是创建 User 类及其实例, name="User" 6 return super().__new__(cls, name, bases, attrs) 7 8 # 根据属性类型, 取出字段 9 fields = {k:v for k,v in attrs.items() if isinstance(v, Field)}1011 # 如果 User 中有指定 Meta 信息, 比如表名, 就以此为准 12 # 如果没有指定, 就默认以 类名的小写 做为表名, 比如 User 类, 表名就是 user13 _meta = attrs.get("Meta", None)14 db_table = name.lower()15 if _meta is not None:16 table = getattr(_meta, "db_table", None)17 if table is not None:18 db_table = table1920 # 注意原来由 User 传递过来的各项参数 attrs, 最好原模原样的返回, 21 # 如果不返回, 有可能下面的数据描述符不起作用 22 # 除此之外, 我们可以往里面添加我们自定义的参数 23 attrs["db_table"] = db_table24 attrs["fields"] = fields25 return super().__new__(cls, name, bases, attrs)
至此, 我们的简易 ORM 就已经成型.
来源: https://juejin.im/entry/5b17924af265da6e290108eb