文章目录
一、什么是元类?
对于python而言,我们在定义一个类的时候一般都是通过class Foo(object)
来创建,然后根据需求去编写其魔法方法。
1.常用自定义类
那么我们就通过大家常用的方式创建一个类Foo如下:
# 定义类
class Foo(object):
def __init__(self,name):
self.name = name
def __new__(cls,*args,**kwargs):
data = object.__new__(cls)
return data
obj = Foo("小白")
print(obj.name)
该类创建完成后,赋值给了对象,然后我们就可以通过该对象去调用类中定义的方法或参数,所有由此可见,对象是基于类创建出来的。
打印
小白
那么类又是谁创建出来的呢?
2.使用type创建类
类默认由type创建。
而通过type创建类参数的主要形式为:
- 类名
- 继承类
- 成员(
可以有多个用字典包起来
)
代码如下:
def func(self):
return self.name + "666"
Fa = type("Foo2", (object,), {"name": "大白", "func": func, "len": lambda self: len(self.name)})
obj2 = Fa()
print(obj2.name, obj2.func(), obj2.len())
打印
大白 大白666 2
由此可见通过type的方式也可以生成类,也证明了类是由type派生出来的。
3.元类metaclass
那么如果我们想在一个类的创建中改成其他或者在其基础上编写东西,那么该怎么做呢? 这个时候我们就用到了元类 metaclass。
代码如下:
class MyType(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __new__(cls, *args, **kwargs):
new_cls = super().__new__(cls, *args, **kwargs)
return new_cls
def __call__(self, *args, **kwargs):
# 调用自己类的__new__方法
empty_object = self.__new__(self)
# 调用自己类的__init__方法
self.__init__(empty_object, *args, **kwargs)
return empty_object
class Foo(object, metaclass=MyType):
def __init__(self, name):
self.name = name
v1 = Foo("小黑")
v2 = Foo
print(v1,v1.name)
print(v2)
我们需要知道的就是,当一个类使用了metaclass的时候,那么它的优先级最高,最先执行,我们称为元类。
所有的元类都继承于type,type继承于object。
继承该元类的所有子类在实例化时都会调用一遍该元类的方法。
所以上述示例执行顺序为MyType为元类先执行,然后通过 __ new __ 、__ init __ 方法获取到了Foo的类示例。
通过Foo(“小黑”)对象执行元类的__ call __ 方法调用父类的object函数。
打印
<__main__.Foo object at 0x000001F41FFDA048> 小黑
<class '__main__.Foo'>
4.通过元类实现单例模式
在了解元类之后我们就可以通过元类来实现单例模式了。
代码如下:
class MyType(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance = None
def __new__(cls, *args, **kwargs):
new_cls = super().__new__(cls, *args, **kwargs)
return new_cls
def __call__(self, *args, **kwargs):
if not self.instance:
self.instance = self.__new__(self)
self.__init__(self.instance, *args, **kwargs)
return self.instance
class TupleType(object, metaclass=MyType):
pass
class Foo(TupleType):
pass
v1 = Foo()
v2 = Foo()
print(v1)
print(v2)
打印
<__main__.Foo object at 0x00000228A2EB8F08>
<__main__.Foo object at 0x00000228A2EB8F08>
了解这些后我们来看看type是如何进行实例化操作的。
我们总结一下:
- 通过type可以创建一个类需要3个参数(
类名、继承类、成员
) - 创建元类的方式:类(metaclass=“
type或继承type的子类
”) - 通过自定义metaclass元类(
可以是type也可以是继承type的子类
)可以直接创建、修改继承该元类中的实例对象 - 当类执行的时候就等于调用该类中__ call __ 方法(
有元类会执行元类的__ call __ 方法
)
好了我们查看一下type部分源码如下:
class type(object):
def __call__(self, *args, **kwargs): # real signature unknown
""" Call self as a function. """
pass
def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
pass
def __new__(*args, **kwargs):
pass
def __setattr__(self, *args, **kwargs): # real signature unknown
""" Implement setattr(self, name, value). """
pass
我们查看type中的__ init __ 方法中所需参数有四个分别是:
- self:当前准备创建的类的对象
- what:类的名字
- bases:类继承的父类集合
- dict:类的
方法集合
先通过简单的示例来打印一下看看吧:
class MYfunc(type):
def __init__(self, what, bases, dict):
print(what)
print(bases)
print(dict)
type.__init__(self, what, bases, dict)
def __new__(cls, what, bases, dict):
return type.__new__(cls, what, bases, dict)
class func(object, metaclass=MYfunc):
def __init__(self,name):
self.name = name
a1 = func("666")
print(a1.name)
打印:
func
(<class 'object'>,)
{'__module__': '__main__', '__qualname__': 'func', '__init__': <function func.__init__ at 0x0000021225A49AF8>}
666
可以看到,我们自定义的元类MYfunc打印出的类名为继承该类中子类func,以及该子类func继承的父类ojbect和func类中调用的方法,我们在func函数中的方法__ init __ 定义了一个name,所以获取到的就是 __ init __ 方法的函数,之后通过对象.name打印出了参数,对此可以看出在dict参数中,我们可以自定义、修改方法。
5.示例:简易版ORM框架
而ORM框架就是一个典型事例,代码如下:
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类,用于保存数据库表的字段名和字段类型。
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')
然后在Field类的基础上创建各种类型。
自定义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()
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['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
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 = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
args.append(str(getattr(self, k, None)))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
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')
可以看到Model类继承元类ModelMetaclass、dict。
而User类继承了Model,并且我们实例化了User对象,那么我们接着上面讲,都讲到这里了,我们应该明白此时第一次执行的类一定是元类ModelMetaclass,然后到 __ new __ 方法中,此时并不会执行里面的操作,而是通过反射回调用其元类的基类Model
中,执行其方法,然后将Model类中的所有方法保存
,完毕后继续返回到ModelMetaclass元类中执行 __ new __ 方法
,此时类名为Model所以会为Model创建实例。
那么User类继续往下执行本身的赋值字段操作,调用我们之前定义的字段类,因为继承于Model,所以其本身也会去执行一遍ModelMetaclass元类。
此时是User类名,所以会跳过这个判断,继续往下走,通过迭代的形式将继承Field类的字段获取到,存入到一个字典中,然后将之前传入的attrs中的字段方法移除,最后通过元类的方式创建了新方法__ mappings 、 table __方法
mappings方法存放我们传入的字段名、字段内容,而table方法存放表名。
回到Model类中,__ getattr __ 、__ setattr __ 方法用于判断当前实例对象通过.的形式获取参数,然后通过getattr方法来判断当前类是否有该参数,有则返回没则报错,而setattr方法则是当该实例对象修改、赋值时执行。
而这个save函数,则也是我们在ORM中用的很多于保存数据到数据库的操作,非常方便,而我们通过简易版实现的方法是创建2个空列表fields(存放字段名
)、args(所有字段的参数,这里用了一个函数getattr该参数需要3个参数(对象,对象属性,默认值),且会调用__ getattr __方法,如果没有对象属性则返回默认值这里是None
)。
完整代码实现:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' Simple ORM using metaclass '
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)
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')
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name == 'Model':
print('Found model: %s' % name)
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
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['__mappings__'] = mappings # 保存属性和列的映射关系
# print(attrs)
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
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 = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
args.append(str(getattr(self, k, None)))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
# testing code:
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.name = "sehun"
u.id = 666
u.save()
打印:
Found model: Model
Found model: User
Found mapping: id ==> <IntegerField:id>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (id,username,email,password) values (666,sehun,test@orm.org,my-pwd)
ARGS: ['666', 'sehun', 'test@orm.org', 'my-pwd']
二、迭代器和生成器
1.迭代器类型的定义
对于迭代器的定义需要通过在类中定义__iter__和__next__两个方法。并且__iter__方法需要返回对象本身,即self,而__next__方法需要返回下一个数据,如果没有数据了,则会抛出一个StopIteration异常。
2.创建迭代器类型
我们通过上述迭代器的定义来创建一个类如下:
class Foo(object):
def __init__(self):
self.counter = 0
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == 3:
raise StopIteration()
return self.counter
然后我们将Foo类实例化之后通过__next__、next()方法来获取下一个参数
如下:
f1 = Foo()
print(f1.__next__())
print(next(f1))
print(f1.__next__())
打印
1
2
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/untitled/test.py", line 65, in <module>
print(f1.__next__())
File "C:/Users/Administrator/PycharmProjects/untitled/test.py", line 57, in __next__
raise StopIteration()
StopIteration
除此之外还可以用for循环(执行迭代器对象的__iter__方法并获取返回值,一直去反复的执行 next(对象)直到结束
)的形式进行遍历Foo对象。
如下:
f2 = IT()
for item in f2:
print(item)
打印
1
2
3.生成器类型的定义
队医生成器而言,内部是根据生成器类generator创建的对象,并且生成器的内部也声明了:__ iter__ 、__next__方法,所以生成器也可以说是一个特殊的迭代器
。
4.创建生成器类型
所以生成器内部实现方法和迭代器一致,也是支持for循环和__next__、next()方法来获取参数的,所以在__iter__方法中返回的对象可以是迭代器对象也可以是生成器对象,而它们都属于可迭代对象
。
生成器通过yield返回值,代码如下:
# 创建生成器函数
def func():
yield 1
yield 2
不过对于生成器为什么会被称为特殊的迭代器
,是因为还有一个send函数的方法,该方法为生成器所有,拥有一个参数,多用于发送任务相关的操作,而传进来的参数会存放在yield的返回值(即generator对象
)中。
from collections.abc import Iterator
def coroutine():
for i in range(1, 10):
a = yield i
print("From generator {}".format(a))
c = coroutine()
print(c, isinstance(c, Iterator))
next(c)
try:
while True:
print("From user {}".format(c.send(1)))
except StopIteration:
pass
此时我们先打印看一下生成器是否为可迭代对象,以及属于上面类如下:
<generator object coroutine at 0x000001D1645EF748> True
可以看到为生成器对象,并且是可迭代对象(后面会讲到
),不过对于一个生成器对象而言我们必须要执行一次初始化操作才可以调用send的函数方法,我们可以通过next(生成器)
或者生成器对象.send(None)
,之后便可以通过循环的形式依次执行生成器操作。
打印
From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...
...
可以看到对于生成器对象使用send(参数)函数调用后,该生成器函数通过yield i(即生成器对象
)发送出去后获取到的返回值是调用send时传的参数(如果是第一次初始化生成器,yield不会产生返回值
)。
5.可迭代对象
在上面中我们举例的迭代、生成器对象的示例,本质上都是可迭代对象,因为该内部类中都拥有着__iter__方法 ,而判断是否有该方法,我们则可以通过dir(对象)
的方式去查看。
那么我们可以知道了其实在python中的range函数其实也是可迭代对象。
如下:
v1 = range(1000)
print(dir(v1))
打印
['__bool__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index', 'start', 'step', 'stop']
可以看到此时v1对象是拥有__iter__方法的,所以我们就可以通过range(参数)继续for循环遍历,并可以基于可迭代对象&迭代器实现自定义range代码如下:
class IterRange(object):
def __init__(self, num):
self.num = num
self.counter = -1
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == self.num:
raise StopIteration()
return self.counter
class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
return IterRange(self.max_num)
obj = Xrange(10)
for item in obj:
print(item)
而生成器也是特殊的迭代器所以我们也可以通过生成器实现自定义range代码如下:
class Srange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
counter = 0
while counter < self.max_num:
yield counter
counter += 1
obj2 = Srange(10)
for item in obj2:
print(item)
所以由此可知,对于可迭代对象而言,其内部__iter__方法会返回一个迭代对象,且该迭代对象拥有__next__、__iter__方法。
所以对于迭代对象而言其内部__iter__方法相当于返回对象本身(即self
)。
那么对于我们的常用于for循环的数据类型dict、list、tuple其实也是可迭代对象,对于判断是否为可迭代对象或者是否是迭代对象,也可以通过collections.abc中的Iterator、Iterable加上isinstance来进行判断。
from collections.abc import Iterator,Iterable
v1 = [11,22,33]
v2 = {"v1":11,"v2":22,"v3":33}
v3 = ((1,11),(2,22),(3,33))
print(isinstance(v1,Iterable)) # 可迭代对象(是否有__iter__方法)
>>> True
print(isinstance(v2,Iterable))# 可迭代对象(是否有__iter__方法)
>>> True
print(isinstance(v3,Iterable))# 可迭代对象(是否有__iter__方法)
>>> True
print(isinstance(v1 ,Iterator)) # 是否迭代器(是否有__iter__、__next__方法)
>>> False
v1_1 = v1.__iter__()
print(isinstance(v1_1 ,Iterator)) # 是否迭代器(是否有__iter__、__next__方法)
>>> True
三、装饰器
在了解装饰器之前我们需要先分析一个需求,假设我想在执行函数之前打印一个before和之后打印一个after,我可以这样写。
代码如下:
def func():
print("before")
print("我是func函数")
print("after")
func()
打印
before
我是func函数
after
这是第一种方式,也是最容易想到的方式,那么现在还有一种方式也同样的可以去完成这个需求。
代码如下:
def func1():
print("我是func函数")
def outer(origin):
def inner():
print("before")
origin()
print("after")
return inner
result = outer(func1)
result()
这里我们通过定义了一个函数outer并带入了参数(这里是func1函数,origin参数可以是想要执行的函数
),然后就可以在inner函数中继续操作。
显然在这种情况下,我个人认为还是第一种方式简单易懂还方便,但是既然我们讲了第二种的方式那么就有它的道理,那么由此也引出了一个python中支持特殊语法(@函数名)
即我们现在要说的装饰器
,也就是第二种方法的语法糖。
1.装饰器的使用
代码如下:
def outer(origin):
def inner():
print("before")
res = origin()
print("after")
return res
return inner
@outer
def func1():
print("我是func函数")
value = (11, 22, 33, 44)
return value
result = func1()
print(result)
我们对第二种方法进行修改,因为python编译器是自上而下的,所以以outer为装饰器的函数func1必须在outer下面,然后通过语法糖的形式(@函数,这里指@outer
)放在要定义装饰器的函数上,并多加了一个返回值。
打印
before
我是func函数
after
(11, 22, 33, 44)
现在一看我还是觉得第一种方式好,但是这只是在只有一个函数的情况下,那么如果当有多个函数需要使用outer函数,那么此时装饰器的优势就显示出来了,并且以后维护起来非常的方便,只需在需要用到的函数上加上@outer即可。
那么我们怎么让调用装饰器的函数的参数呢?我们可以使用万能的*args,**kwargs,那么我们修改一下我们的装饰器。
2.装饰器传参优化
代码如下:
def outer(origin):
def inner(*args, **kwargs):
print("before")
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func1(a1, a2):
print("我是func函数")
return a1 + a2
result = func1(a1=11, a2=22)
print(result)
打印
before
我是func函数
after
33
当然如果当装饰器需要参数(这里指outer函数
)也可以在需要执行装饰器的函数上加个括号然后把参数传入,并在用一个函数将装饰器再包裹一层。
代码如下:
def outer(name):
def inner(origin):
def test(*args, **kwargs):
print(name)
print("before")
res = origin(*args, **kwargs)
print("after")
return res
return test
return inner
@outer("我是outer")
def func1(a1, a2):
print("我是func函数")
return a1 + a2
result = func1(a1=11, a2=22)
print(result)
打印
我是outer
before
我是func函数
after
33
当然对于类也可以进行装饰器,这里就不详细的讲解了,有兴趣的朋友可以自行查看。
3.装饰器使用案例
这里我们基于python的第三方模块Flask(框架)快速写一个网站,而请求的路由需要登录成功后才有权限访问,此时我们就可以用到装饰器了。
安装Flask
pip3 install flask
第三方模块Flask代码如下:
from flask import Flask, request
app = Flask(__name__)
token = "1a16aece-64a9-4cd2-b5c5-4ffc387e79bb"
# 装饰器代码位置下面会写(主要关注装饰器)
def index():
print(request.args.get("token"))
return "首页"
@auth
def info():
return "用户中心"
@auth
def order():
return "订单中心"
def login():
return "登录页面"
app.add_url_rule("/index", view_func=index)
app.add_url_rule("/info", view_func=info)
app.add_url_rule("/order", view_func=order)
app.add_url_rule("/login", view_func=login)
app.run()
装饰器(用于判断是否登录的装饰器
)代码如下:
def auth(func):
def inner(*args, **kwargs):
user_token = request.args.get("token")
print(user_token)
if not user_token:
return "请先登录"
if user_token != token:
return "请先注册"
res = func(*args, **kwargs)
return res
return inner
这里只是单单为了演示装饰器的作用,真正项目中不可能这么写的,我们把token写死(假设只创建了一个用户
),不涉及到表结构,我们直接用token去判断当前用户是否处于登录的状态,然后把需要用户登录才能访问的路由直接加上装饰器即可实现了该功能。
此时我们运行一下flask,如果不出意外我们会报错,而报错内容如下:
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/untitled/flask_test.py", line 42, in <module>
app.add_url_rule("/order", view_func=order)
File "C:\Users\Administrator\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 98, in wrapper_func
return f(self, *args, **kwargs)
File "C:\Users\Administrator\AppData\Roaming\Python\Python37\site-packages\flask\app.py", line 1284, in add_url_rule
"existing endpoint function: %s" % endpoint
AssertionError: View function mapping is overwriting an existing endpoint function: inner
引发错误的原因是装饰器的链式调用,具体解决方法使用模块funtools.wraps方法,也就是我们下面要讲的扩展了。
4.扩展:funtools.wraps
在讲解之前,我们要知道对于类来说,通过__name__方法可以调用创建类的名字,__doc__方法可以获取创建类的注释,每个类名按我们自己定义的来说都是不一样的
。
代码如下:
def func1():
"""我是func1函数"""
print("func1")
def func2():
"""我是func2函数"""
print("func2")
print(func1.__name__)
print(func1.__doc__)
print(func2.__name__)
print(func2.__doc__)
打印
func1
我是func1函数
func2
我是func2函数
但是如果当我们使用了装饰器后,效果就不一样了。
代码如下:
def auth(origin):
def inner(*args, **kwargs):
"""我是auth装饰器的inner函数"""
res = origin(*args, **kwargs)
return res
return inner
@auth
def func1():
"""我是func1函数"""
print("func1")
@auth
def func2():
"""我是func2函数"""
print("func2")
print(func1.__name__)
print(func1.__doc__)
print(func2.__name__)
print(func2.__doc__)
打印
inner
我是auth装饰器的inner函数
inner
我是auth装饰器的inner函数
可以看到此时装上装饰器auth的函数是代指向inner函数的,那么我们想让继承装饰器的函数名代指回自己就需要funtools.wraps了,所以这就是导致flask报错的主要原因。
所以我们修改上述装饰器代码如下:
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
user_token = request.args.get("token")
print(user_token)
if not user_token:
return "请先登录"
if user_token != token:
return "请先注册"
res = func(*args, **kwargs)
return res
return inner
此时我们在装饰器auth中创建了一个functools.wraps(func)装饰器,将被装饰器的函数func传入,然后就代指回自己了(内部相当于inner.__ name __ = func.__ name __ 和 inner.__ doc __ = func.__ doc __
)。
此时我们启动flask并访问http://127.0.0.1:5000/如下:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/66844.html