Python的迭代器、生成器、装饰器

Python三大器可能在平常写代码时会不知不觉的用到,但是却从未系统的学习过,于是乎特意记录一篇,尤其是装饰器,Flask中很常用,但是不是很了解具体的工作原理,有必要了解一下

迭代器

迭代

迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。

可迭代对象(iterable)

该对象可以被用于for…in…循环,例如:集合,列表,元祖,字典,字符串,迭代器等。

在python中如果一个对象实现了 __iter__方法,我们就称之为可迭代对象,可迭代对象必须提供一个__next__() 方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往前,不能退后)

如果一个对象未实现 __iter__方法,但是对其使用for…in则会抛出TypeError: ‘xxx’ object is not iterable

可以在一个对象内部实现一个__iter__方法将一个类实例对象变为可迭代对象:

1
2
3
4
5
6
from collections.abc import Iterable

class MyIterable:
def __iter__(self):
pass
print(isinstance(MyIterable(), Iterable)) # True

迭代器

对可迭代对象进行迭代的方式或容器,并且会记录当前迭代进行到的位置。在python中如果一个对象同时实现了__iter__和__next__(获取下一个值)方法,那么它就是一个迭代器对象。可以通过内置函数next(iterator)或实例对象的__next__()方法,来获取当前迭代的值。

使用while模拟for循环:

1
2
3
4
5
6
7
list1 = [1, 2, 3, 4]
l = list1.__iter__()
while True:
try:
print(l.__next__()) # 也可以是 print(next(l))
except StopIteration:
break

优缺点

优势

迭代器对象表示的是一个数据流,可以在需要时才去调用next来获取一个值;因而本身在内存中始终只保留一个值,对于内存占用小可以存放无限数据流。优于其他容器需要一次将所有元素都存放进内存,如:列表、集合、字典…等

缺点

缺点也很明显,除了遍历完计数之外,没有办法获取存放的元素的长度,并且不能像列表、字典等可以取任意位置的元素值,而且迭代器是一次性的,元素迭代结束生命周期就相应结束。

生成器

在Python中,一边循环,一边计算的机制,称为生成器。

生成器的作用

对象中的元素是按照某种算法推算出来的,在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

生成器的生成

简单生成器

通过将列表生成时的[]改变为()即可得到一个生成器对象:

1
2
3
4
5
6
7
8
9
10
11
# 生成器
_generator = (i for i in range(10))
print(type(_generator)) # <class 'generator'>
print(_generator) # <generator object <genexpr> at 0x0000027A2E797F48>
# 生成器对象取值
print(_generator.__next__()) # 0
print(next(_generator)) # 1
# 注意从第三个元素开始了!
print("-------------------------")
for x in _generator:
print(x) # 2, 3, 4, 5, 6, 7, 8, 9

函数对象生成器

带yield语句的函数对象的返回值则是个生成器对象:

  1. 当我们想从生成器取一个数时,生成器会执行,直至出现 yield 语句,生成器把yield 的参数给我们,之后生成器就不会往下继续运行。
  2. 当我们问它取下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给我们,之后停下。如此反复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def gen_generator():
yield 1
yield 2
yield 3
yield 4
def func():
return 1
print(gen_generator(), type(gen_generator()))
# <generator object gen_generator at 0x000001CD94BA7F48> <class 'generator'>
print(func(), type(func()))
# 1 <class 'int'>
g = gen_generator()
print(next(g)) # 1
print("-----------")
for i in g:
print(i) # 2, 3, 4

装饰器

一些概念

装饰器是给已经定义好的函数添加功能的工具。装饰器就是一个函数,这个函数既是实参高阶函数,又是返回值高阶函数。

对象引用

对象名是一个绑定内存地址的变量,举个例子:

1
2
3
4
5
6
7
8
9
10
def func():   # 函数名仅仅只是个绑定内存地址的变量       
print("i`m running")

# 这是调用
func() # i`m running
# 这是对象引用,引用的是内存地址
func2 = func
print(func2 is func) # True
# 通过引用进行调用
func2() # i`m running

闭包

定义一个函数A,然后在该函数内部再定义一个函数B,并且B函数用到了外边A函数的变量

1
2
3
4
5
6
7
8
9
10
11
12
def out_func_A():
out_a = 10

def inner_func_B(inner_x):
return out_a + inner_x

return inner_func_B


out = out_func_A()
print(out) # <function out_func_A.<locals>.inner_func_B at 0x000001CD05EFC168> out_func返回的是inner_func的内存地址
print(out(inner_x=2)) # 12

装饰器和闭包不同点在于:装饰器的入参是函数对象,闭包入参是普通数据对象

一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def decorator_get_function_name(func):
"""
获取正在运行函数名
:return:
"""

def wrapper(*arg):
"""
wrapper
:param arg:
:return:
"""
print(f"当前运行方法名:{func.__name__} with params: {arg}")
return func(*arg)

return wrapper

@decorator_get_function_name
def test_func_add(x, y):
print(x + y)


def test_func_sub(x, y):
print(x - y)


test_func_add(1, 2)
# 输出:
# 当前运行方法名:test_func_add with params: (1, 2)
# 3
# 不使用语法糖的话也可以用以下方法,效果是一样的
decorator_get_function_name(test_func_sub)(3, 5)
# 我们还可以换种写法达到跟👆一样的效果,就是使用引用
dec_obj = decorator_get_function_name(test_func_sub) # 这里等同于wrapper对象
dec_obj(3,5) # 这里等同于wrapper(3,5)
# 输出:
# 当前运行方法名:test_func_sub with params: (3, 5)
# -2

使用场景

常用于鉴权、日志记录、缓存等

下面举一个鉴权的实际例子:

1
2
3
4
5
6
7
8
9
10
11
12
def login_check(func):
def wrapper(request, *args, **kwargs):
if not request.session.get('login_status'):
return HttpResponseRedirect('/api/login/')
return func(request, *args, **kwargs)

return wrapper

@login_check
def edit_config(request, *args, **kwargs):
# do some edit func
pass

当我们执行edit_config时,首先将edit_config作为参数传入login_check,然而login_check返回了wrapper函数对象,我们进入wrapper可以看到,会检查登录状态,如果未登录直接重定向到登录页面,否则执行func(request, *args, **kwargs),而此时我们知道func就是传入的edit_config,因此最终如果登录成功就会执行edit_config(request, *args, **kwargs)。

静态方法

@staticmethod

静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,主要是一些逻辑属于类,但是和类本身没有交互,即在静态方法中,不会涉及到类中的方法和属性的操作。可以理解为将静态方法存在此类的名称空间中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import time
class TimeTest(object):
def __init__(self,hour,minute,second):
self.hour = hour
self.minute = minute
self.second = second
@staticmethod
def showTime():
return time.strftime("%H:%M:%S", time.localtime())


print(TimeTest.showTime())
t = TimeTest(2,10,10)
nowTime = t.showTime()
print(nowTime)

输出:

1
2
16:39:03
16:39:03

静态函数可以通过类名以及实例两种方法调用。

类方法

@classmethod

类方法是当调用这个方法时,将类作为第一个参数传递,而不是该类的实例(就像我们通常对方法所做的那样)。这意味着您可以在该方法中使用类及其属性,而不是在特定实例中使用。

类方法可以通过实例对象和类对象进行调用,在编码过程中发现类方法调用在实例对象初始化之前,故如果实现功能时需要在初始化前做一些校验的工作时可以考虑使用类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class testFunction():

classAttribute ='monica'
def __init__(self,example_name):
self.example_name = example_name
@staticmethod
def testStatic():
print('静态方法使用')
print('调用类属性',testFunction.classAttribute)
#print('调用实例属性',self.example_name)
def testExample(self):
print('实例方法')
#print('调用实例属性:',self.example_name)
print('调用类属性:',testFunction.classAttribute)
@classmethod
def testClassMethod(cls):
print('类方法')
print('调用类属性:',cls.classAttribute)
#print('调用实例属性:',cls.example_name)

tf = testFunction('example_monica')
#实例对象调用静态方法
tf.testStatic()
#类对象调用静态方法
testFunction.testStatic()
#实例对象调用方法
tf.testExample()
#类对象调用实例方法
testFunction.testExample('test')
#实例对象调用类方法
tf.testClassMethod()
#类对象调用类方法
testFunction.testClassMethod()

上面的例子表明,类方法是不能调用实例属性的。

其他python问题

python如何完成异常处理

Python 异常处理 | 菜鸟教程 (runoob.com)

Python __init__ 与 __new__的区别

二者均是Python面向对象语言中的函数,__new__比较少用,__init__则用的比较多。

__new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。
__init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法。
也就是: __new__先被调用,__init__后被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。

Python2与Python3的区别

1.print 从语句变为函数:

原: print 1, 2+3

改为: print ( 1, 2+3 )

2.range 与 xrange

原 : range( 0, 4 ) 结果 是 列表 [0,1,2,3 ]

改为:list( range(0,4) )

原 : xrange( 0, 4 ) 适用于 for 循环的变量控制

改为:range(0,4)

3.try except 语句的变化

原:

 try:
    ......
 except    Exception, e :
    ......

改为

try:
    ......
except    Exception as e :
    ......

4.除法运算符

python 2.4.2以前

10/3 结果为 3

python 3.0

10 / 3 结果为 3.3333333333333335

10 // 3 结果为 3

5.lib缓存

在导入模块运行时 python2 只会在模块同级目录下生成.pyc文件 python3 生成__pycache__目录

6.默认编码

python2的编码是ascii码,python3的默认编码是utf-8

7.输入

python3中input得到的数据都是str型

python2中input默认是int型,str要使用引号包裹,raw_input得到的都是str

python _xxx __xxx和__xxx__的区别

  1. 单前导下划线 _xxx:内部私有变量,protected
  2. 单末尾下划线 xxx_:避免与python关键字产生命名冲突,比如想用class名称作为变量,可以用class_
  3. 双前导下划线__xxx: “__spam”这种形式(至少两个前导下划线,最多一个后续下划线)的任何标识符将会被“_classname__spam”这种形式原文取代,private
  4. 双前导和双末尾下划线 __xxx__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例__init__(), __del__(),__call__()这些特殊方法

设计模式

单例模式

确保某一个类只有一个实例存在

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton(object):
def __init__(self):
pass

def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"): # 反射
Singleton._instance = object.__new__(cls)
return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2) # <__main__.Singleton object at 0x000002511B67B448> <__main__.Singleton object at 0x000002511B67B448>

工厂模式

创建一个特定类型的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Person:
def __init__(self):
self.name = None
self.gender = None

def getName(self):
return self.name

def getGender(self):
return self.gender

class Male(Person):
def __init__(self, name):
print("Hello Mr." + name)
self.name = name
self.gender = 'M'

class Female(Person):
def __init__(self, name):
print("Hello Miss." + name)
self.name = name
self.gender = 'F'

class Factory:
def getPerson(self, name, gender):
if gender == 'M':
return Male(name)
if gender == 'F':
return Female(name)


if __name__ == '__main__':
factory = Factory()
person = factory.getPerson("Chetan", "M")
print(person.name, person.gender)

Python的迭代器、生成器、装饰器
https://chujian521.github.io/blog/2022/11/05/Python的迭代器、生成器、装饰器/
作者
Encounter
发布于
2022年11月5日
许可协议