一、什么是元类?
对于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):
empty_object = self.__new__(self)
self.__init__(empty_object, *args, **kwargs)
return empty_object
class Foo(object, metaclass=MyType):
def __init__(self, name):
self.name = name
v1 = Foo("小黑")
print(v1.name)
因为我们自定义的MyType类是以metaclass 元类 的身份创建Foo对象(基于MyType类创建的Foo对象 ),其优先级高于object ,所以在Foo对象实例化的时候就会调用了MyType类中的__call__方法(Foo是MyType类创建的对象,所以Foo()调用MyType类的call方法 ),然后我们自定义的元类 MyType就会通过call方法依次的执行本身__new__、__init__方法,而其元类本身的方法则会实例化Foo对象的方法。
那么对于type而言,其内部的__call__方法也是根据我们上述操作依次调用的,也相当于元类(通过type生成的类 ),所以对于元类而言重点:可以是type也可以是基于type创建的对象 。
所以上述的type创建类的示例也等价于如下代码:
class Fa(metaclass=type):
name = "大白"
def func(self):
return self.name + "666"
def len(self):
return len(self.name)
obj2 = Fa()
print(obj2.name, obj2.func())
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)
该代码表示Foo对象是基于元类MyType创建的(Foo()调用了TupleType类,而其元类为MyType ),然后依次调用本身的__new__、__init__方法(定义了self.instance = None )但是这里我们在__call__方法中进行instance的判断(示例对象是否已经创建 ),最后通过super函数执行其父类type(因为类是由type衍生的所以此时type会实例化Foo对象 )。
打印
<__main__.Foo object at 0x00000228A2EB8F08>
<__main__.Foo object at 0x00000228A2EB8F08>
二、迭代器和生成器
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
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))
>>> True
print(isinstance(v2,Iterable))
>>> True
print(isinstance(v3,Iterable))
>>> True
print(isinstance(v1 ,Iterator))
>>> False
v1_1 = v1.__iter__()
print(isinstance(v1_1 ,Iterator))
>>> 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/如下:
此时通过装饰器实现了对应路由(即继承装饰器上的路由视图函数 )需要用户登录的判断。
|