Python开发4——函数对象、名称空间与作用域、闭包、装饰器、迭代器、生成器、内置函数、递归调用、匿名函数
一、函数对象:
函数是第一类对象,即函数可以当作数据传递
1、可以被引用,即可以赋值给一个变量
2、可以当作参数传递
3、返回值可以是函数
4、可以当作容器类型的元素
#利用该特性,优雅的取代多分支的if
def select(sql):
print('==============================>select')
def delete(sql):
print('==============================>delete')
def update(sql):
print('==============================>update')
def insert(sql):
print('==============================>insert')
dic = {'select':select,'delete':delete,'update':update,'insert':insert}
def main():
while True:
sql = input('sql>>>').strip().split()
if not sql:continue
if sql[0] in dic:
dic[sql[0]](sql)
elif sql[0] == 'exit' or sql[0] == 'quit':
break
main()
二、函数的嵌套
1、函数的嵌套调用
在调用函数的过程中又调用其他函数
def max(x, y):
return x if x > y else y
def max4(a, b, c, d):
res1 = max(a, b)
res2 = max(res1, c)
res3 = max(res2, d)
return res3
print(max4(1, 2, 3, 4))
2、函数的嵌套定义
def f1():
def f2():
def f3():
print('from f3')
f3()
f2()
f1()
#f3() # 报错
三、名称空间和作用域
1、名称空间:
①说明:名称空间是存放名字的地方。其他语言中如 C 中,变量名是内存地址的别名,而在 Python 中,名字是一个字符串对象,它与它指向的对象构成一个{name:object}关联。
②LEGB:代表名字查找顺序: locals -> enclosing function -> globals -> __builtins。
③名称空间分类:
locals(function):是函数内的名字空间,包括局部变量和形参(局部名称空间:随着函数的调用而产生。在函数调用时临时绑定,调用结束解除绑定)。
enclosing function(封闭空间名称:非局部(non-local)也非全局(non-global)):外部嵌套函数的名字空间(闭包函数中常见)。
globals(module):全局变量,函数定义所在模块的名字空间(全局名称空间:随着文件的执行产生,文件级别定义的名称都会存入此空间)。
builtins(Python):Python内置模块的名字空间(内置名称空间:随着python解释器的启动而产生)。
2、作用域:
①说明:作用域即作用范围(作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关)。
②作用域分类:
全局作用域:全局有效,在任何位置都能被访问到,除非del删掉,否则会一直存活到文件执行完毕。
局部作用域:局部有效,只能在局部范围调用,只在函数调用时才有效,调用结束就失效。
3、global 和 nonlocal关键字:
①当局部作用域想修改全局作用域的变量时,就要用到global关键字了。
#以下实例修改全局变量 num:
num = 1
def fun1():
global num # 需要使用 global 关键字声明
print(num)
num = 123
print(num)
fun1()
②如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了。
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
四、闭包
1、说明:定义在函数内部,包含对外部作用域而非全局作用域的引用的函数称为闭包函数。
2、闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
#闭包应用:延迟计算
from urllib.request import urlopen
def index(url):
def get():
return urlopen(url).read()
return get
baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))
# 查看闭包的元素
print(baidu.__closure__[0].cell_contents)
五、装饰器
1、装饰器:修饰别人的工具,修饰添加功能,工具指的是函数。装饰器本身可以是任何可调用对象,被装饰的对象也可以是任意可调用对象。
2、装饰器原则:
①在不修改被装饰对象的源代码以及调用方式的前提下,为其添加新功能。
②开放封闭原则:对修改是封闭的,对扩展是开放的。
3、装饰器语法:
被装饰函数的正上方,单独一行
@deco1
@deco2
@deco3
def foo():
pass
foo=deco1(deco2(deco3(foo)))
# 无参装饰器
import time
def timmer(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
@timmer #index=timmer(index)
def index(name):
time.sleep(3)
print('welcome to index')
print(name)
return 1
index('haha')
# 无参装饰器
login_user={'user':None,'status':False}
def auth(func):
def wrapper(*args,**kwargs):
if login_user['user'] and login_user['status']:
res=func(*args,**kwargs)
return res
else:
name=input('>>: ')
password=input('>>: ')
if name == 'egon' and password == '123':
login_user['user']='egon'
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
return wrapper
@auth
def index():
print('welcome to index page')
@auth
def home(name):
print('%s welcome to home page' %name)
index()
home('egon')
# 有参装饰器+多装饰器
import time
def timmer(func):
def wrapper(*args,**kwargs):
print('====>timmer.wrapper')
start_time=time.time()
res=func(*args,**kwargs) #auth_wrapper
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return res
return wrapper
login_user={'user':None,'status':False}
def auth(driver='file'):
def auth2(func):
def wrapper(*args,**kwargs):
print('=======>auth.wrapper')
time.sleep(5)
if driver == 'file':
if login_user['user'] and login_user['status']:
res=func(*args,**kwargs)
return res
else:
name=input('>>: ')
password=input('>>: ')
if name == 'egon' and password == '123':
login_user['user']='egon'
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
elif driver == 'ldap':
print('==========ldap的认证')
elif driver == 'mysql':
print('==========mysql的认证')
return func(*args,**kwargs)
else:
print('=========未知的认证来源')
return wrapper
return auth2
@timmer #index=timmer(auth_wrapper)
@auth('file') #@auth2====>index=auth2(index)===>index=auth_wrapper
def index():
time.sleep(3)
print('welcome to index page')
@auth(driver='mysql')
def home(name):
print('%s welcome to home page' %name)
index()
home('haha')
六、迭代器
1、迭代器的概念:
重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值。
2、为什么要有迭代器:
对于没有索引的数据类型,必须提供一种不依赖索引的迭代方式。
3、可迭代对象:
有__iter__(iter())方法的,都是可迭代对象(字符串、列表、元组、字典、集合、文件对象)。执行__iter__(iter())方法,得到的结果是迭代器。
4、迭代器对象:
执行__iter__(iter())方法,得到的结果就是迭代器,迭代器对象有__iter__与__next__(iter() 和 next())方法(文件对象)。对于迭代器对象来说,执行__iter__方法,得到的结果仍然是它本身。
5、判断一个对象是可迭代的对象,还是迭代器对象:from collections import Iterable,Iterator
from collections import Iterable,Iterator
#下列数据类型都是可迭代对象
print(isinstance('abc',Iterable))
print(isinstance([],Iterable))
print(isinstance((),Iterable))
print(isinstance({'a':1},Iterable))
print(isinstance({1,2},Iterable))
print(isinstance(f,Iterable))
#只有文件是迭代器对象
print(isinstance('abc',Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({'a':1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))
6、迭代器的优缺点
①优点:
提供统一的且不依赖于索引的迭代方式;
惰性计算,节省内存。
②缺点:
无法获取迭代器对象的长度;
不如序列类型取值灵活,是一次性的,只能往前走,不能往后退。
7、遍历迭代器对象
①迭代器对象可以使用常规for语句进行遍历:
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
for x in it:
print (x, end=" ")
②迭代器对象也可以使用 next() 函数遍历:
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
while True:
try:
print (next(it))
except StopIteration:
break
七、生成器
1、生成器:
函数体内有 yield 关键字的函数的执行结果被称为生成器(generator),生成器就是一个迭代器。
2、yield的功能:
①相当于为函数封装好__iter__和__next__;
②每次遇到 yield 时,挂起函数的运行状态,返回yield的值。并在下一次执行 next()方法时,从当前位置继续运行。
注意事项:生成器只能遍历一次
①yield的语句形式: yield 1
#应用一:管道tail -f a.txt |grep 'python'
import time def tail(filepath): with open(filepath,encoding='utf-8') as f: f.seek(0,2) while True: line=f.readline().strip() if line: yield line else: time.sleep(0.2) # t=tail('a.txt') # for line in t: # print(line) def grep(pattern,lines): for line in lines: if pattern in line: yield line g=grep('python',tail('a.txt')) print(g) for i in g: print(i)
②yield的表达式形式: x=yield
#协程函数
def deco(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
next(res)
return res
return wrapper
@deco
def eater(name):
print('%s ready to eat' %name)
food_list=[]
while True:
food=yield food_list
food_list.append(food)
print('%s start to eat %s' %(name,food))
g=eater('alex')
# print(g)
# next(g) #等同于 g.send(None)
# print(g)
print(g.send('脚趾头1'))
print(g.send('脚趾头2'))
print(g.send('脚趾头3'))
#x=yield
#g.send('1111'),先把1111传给yield,由yield赋值给x
# 然后再往下执行,直到再次碰到yield,然后把yield后的返回值返回
③表达式形式的yield的用途
#grep -rl 'python' /root
import os
def init(func):
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
next(res)
return res
return wrapper
@init
def search(target):
while True:
search_path=yield
g=os.walk(search_path)
for par_dir,_,files in g:
for file in files:
file_abs_path=r'%s\%s' %(par_dir,file)
# print(file_abs_path)
target.send(file_abs_path)
@init
def opener(target):
while True:
file_abs_path=yield
# print('opener func==>',file_abs_path)
with open(file_abs_path,encoding='utf-8') as f:
target.send((file_abs_path,f))
@init
def cat(target):
while True:
file_abs_path,f=yield #(file_abs_path,f)
for line in f:
tag=target.send((file_abs_path,line))
if tag:
break
@init
def grep(target,pattern):
tag=False
while True:
file_abs_path,line=yield tag
tag=False
if pattern in line:
tag=True
target.send(file_abs_path)
@init
def printer():
while True:
file_abs_path=yield
print(file_abs_path)
x=r'F:\Python\老男孩\note\day5\a'
g=search(opener(cat(grep(printer(),'python'))))
print(g)
g.send(x)
'''
面向过程的程序设计:是一种流水线式的编程思路,是机械式
优点:
程序的结构清晰,可以把复杂的问题简单
缺点:
1 扩展性差
应用场景:
1 linux内核,git,httpd
'''
八、内置函数
1、abs( x ):返回数字的绝对值
print(abs(-45))
2、all(iterable):可迭代参数 iterable 中所有元素都为True( False:0、''、False、None),则返回 True,否则返回 False
#函数等价于:
def all(iterable):
for element in iterable:
if not element:
return False
return True
示例:
print(all(['a', 'b', 'c', 'd'])) # True:列表list,元素都不为False
print(all(['a', 'b', '', 'd'])) # False:列表list,存在一个为空的元素
print(all([0, 1, 2, 3])) # False:列表list,存在一个为0的元素
print(all([False, 1, 2, 3])) # False:列表list,存在一个为False的元素
print(all([None, 1, 2, 3])) # False:列表list,存在一个为None的元素
print(all([' ', 1, 2, 3])) # True:列表list,元素都不为(空格不算False)
print(all({0:'a',1:'b'})) # False:字典dict,存在一个为0的key
print(all({'a':0,1:'b'})) # True:字典dict,key都不为False
print(all({'0':'a',1:'b'})) # True:字典dict,key都不为False('0'不是False)
print(all('')) # True:空字符串
print(all(' ')) # True:空格字符串
print(all([])) # True:空列表
print(all(())) # True:空元组
print(all({})) # True:空字典
3、any(iterable):可迭代参数 iterable 中任一元素为True( False:0、''、False、None),则返回 True,否则返回 False
#函数等价于:
def any(iterable):
for element in iterable:
if element:
return True
return False
示例:
print(any([0, 1, 2, 3])) # True:列表list,有元素为True
print(any([False, 1, 2, 3])) # True:列表list,有元素为True
print(any([None, 1, 2, 3])) # True:列表list,有元素为True
print(any(['', 1, 2, 3])) # True:列表list,有元素为True
print(any(['', 0, False, None])) # False:列表list,元素都为False
print(any({'':'d',0:'a',False:'b',None:'c'})) # False:字典dict,key都为False
print(any({'a':0,0:'b'})) # True:字典dict,有key值为True
print(any({'0':'a',1:'b'})) # True:字典dict,有key值为True
print(any('')) # False:空字符串
print(any(' ')) # False:空格字符串
print(any([])) # False:空列表
print(any(())) # False:空元组
print(any({})) # False:空字典
4、bin(x):将一个整数转换成二进制字符串
print(bin(10)) # 返回字符串:0b1010
5、hex(x):将一个整数转换成16进制字符串
print(hex(10)) # 返回字符串:0xa
6、oct(x):将一个整数转换成8进制字符串
print(oct(0xa)) # 返回字符串:0o12 print(oct(10)) # 返回字符串:0o12
7、bool([x]):将给定参数转换为布尔类型,如果没有参数,返回 False
print(bool('0')) # True
print(bool(' ')) # True
print(bool('')) # False
print(bool(False)) # False
print(bool(0)) # False
print(bool(None)) # False
print(issubclass(bool, int)) # True:bool 是 int 的子类
8、callable(object):检测一个对象是否是可调用的
注:对于函数, 方法, lambda 函数, 类, 以及实现了 __call__ 方法的类实例, 它都返回 True。
def add(a, b):
return a + b
print(callable(add)) # 函数返回 True
print(callable("runoob")) # False
9、chr(i):返回当前整数对应的ascii字符
参数i:范围在 range(256)内(即0~255)的整数,可以是10进制也可以是16进制的形式的数字
返回值:整数参数对应的ascii字符
print(chr(0x30), chr(0x31), chr(0x61)) # 十六进制,结果:0 1 a print(chr(48), chr(49), chr(97)) # 十进制,结果:0 1 a
10、cmp(x,y):比较2个对象,x < y 返回 -1, x == y 返回 0, x > y 返回 1 (py3中已经没有此函数了)
参数:
x -- 数值表达式。
y -- 数值表达式。
import operator a = 1 b = 2 print(operator.lt(a, b)) # True:相当于 a < b print(operator.le(a,b)) # True:相当于 a <= b print(operator.eq(a,b)) # False:相当于 a == b print(operator.ne(a,b)) # True:相当于 a != b print(operator.gt(a,b)) # False:相当于 a > b print(operator.ge(a, b)) # False:相当于 a>= b
11、eval(expression[, globals[, locals]]):执行一个字符串表达式,并返回表达式的值
①expression -- 表达式。
②globals -- 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
③locals -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
data = "{'backend':'oldboy3.org','record':{'server':'100.1.7.9','weight':'20'}}"
data_dict = eval(data)
for k,v in data_dict.items():
print(k,v)
string = "3 * 4 + 5"
print(eval(string))
12、exec(object[, globals[, locals]]):python 动态执行字符串代码片段(也可以是文件)
注:exec 是一个语法声明,不是一个函数。也就是说和if,for一样。
13、execfile(filename[, globals[, locals]]):执行一个文件,返回表达式执行结果
①filename -- 文件名。
②globals -- 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
③locals -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
'''
假设文件 hello.py,内容如下:
print('runoob')
'''
#execfile 调用该文件:
execfile('hello.py')
#结果:runoob
14、compile(source, filename, mode):将一个字符串编译为字节代码
①source -- 字符串或者AST(Abstract Syntax Trees)对象。
②filename -- 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
③mode -- 指定编译代码的种类。可以指定为 exec, eval。
string1 = "for i in range(0,10): print(i)" c = compile(string1,'','exec') # 编译为字节代码对象 print(c) exec(c) string2 = "3 * 4 + 5" a = compile(string2,'','eval') print(eval(a))
15、complex([real[, imag]]):创建一个值为 real + imag * j 的复数或者转化一个字符串或数为复数。如果第一个参数为字符串,则不需要指定第二个参数。
print(complex(1, 2)) #(1+2j)
print(complex(1)) # 数字:(1+0j)
print(complex("1")) # 当做字符串处理:(1+0j)
# 注意:这个地方在"+"号两边不能有空格,也就是不能写成"1 + 2j",应该是"1+2j",否则会报错
print(complex("1+2j")) #(1+2j)
16、dict():创建一个字典
print(dict()) # 创建空字典:{}
print(dict(a='a', b='b', t='t')) # 传入关键字:{'a': 'a', 'b': 'b', 't': 't'}
print(dict(zip(['one', 'two', 'three'], [1, 2, 3]))) # 映射函数方式来构造字典:{'one': 1, 'two': 2, 'three': 3}
print(dict([('one', 1), ('two', 2), ('three', 3)])) # 可迭代对象方式来构造字典:{'one': 1, 'two': 2, 'three': 3}
17、dir([object]):函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表
print(dir()) # 查看当前模块的属性列表 print(dir([ ])) # 查看列表的方法
18、divmod(a, b):把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)
print(divmod(7, 2)) # (3, 1) print(divmod(8, 2)) # (4, 0)
19、enumerate(sequence, [start=0]):用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和索引,一般用在 for 循环中
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
print(list(enumerate(seasons))) #[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
print(list(enumerate(seasons, start=1))) # 索引从 1 开始:[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
seq = ['one', 'two', 'three']
for i, element in enumerate(seq,1):
print(i, seq[i-1])
20、float([x]):将整数和字符串转换成浮点数
print(float(1)) # 1.0
print(float('123')) #123.0
21、def max(*args, key=None):返回给定参数的最大值,参数可以为序列
22、def min(*args, key=None):返回给定参数的最小值,参数可以为序列
23、sorted(iterable[, cmp[, key[, reverse]]]):对所有可迭代的对象进行排序操作(排序规则:reverse = False 升序(默认),reverse = True 降序),返回重新排序的列表。
#max,min,zip,sorted的用法
salaries={
'egon':3000,
'alex':100000000,
'wupeiqi':10000,
'yuanhao':2000
}
# print(max(salaries)) # max()只比较key值
res=zip(salaries.values(),salaries.keys())
# print(list(res))
print(res)
print(max(res)) # 注意:迭代器、生成器等职能遍历一次,上面print(list(res))遍历之后,此处会报错
# def func(k):
# return salaries[k]
#
# print(max(salaries,key=func)) # (此时比较value值)
print(max(salaries,key=lambda k:salaries[k])) # (此时比较value值)
print(min(salaries,key=lambda k:salaries[k])) # (此时比较value值)
print(sorted(salaries)) # 默认的排序结果是从小到大,默认比较key值
print(sorted(salaries,key=lambda x:salaries[x])) # 默认的排序结果是从小到大(此时比较value值)
print(sorted(salaries,key=lambda x:salaries[x],reverse=True)) # reverse=True:排序结果从大到小(此时比较value值)
24、filter(function, iterable):过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的生成器(py2返回序列)
①说明:
序列的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
②参数:
function -- 判断函数。
iterable -- 可迭代对象。
③返回值:
新列表。
l=['alex_SB','wupeiqi_SB','yuanhao_SB','egon']
res=filter(lambda x:x.endswith('SB'),l)
print(res)
print(list(res))
25、map(function, iterable, ...):根据提供的函数对指定序列做映射
①说明:序列的每个元素作为参数传递给函数,返回包含每次 function 函数返回值的新列表。
②参数:
function -- 函数,有两个参数
iterable -- 一个或多个序列
③返回值:
新列表。
l=['alex','wupeiqi','yuanhao'] res=map(lambda x:x+'_SB',l) print(res) print(list(res)) nums=(2,4,9,10) res1=map(lambda x:x**2,nums) print(list(res1))
26、reduce(function, iterable[, initializer]):对参数序列中元素进行累积
①说明:
传给reduce中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个元素用 function 函数运算,最后得到一个结果。
②参数:
function -- 函数,有两个参数
iterable -- 可迭代对象
initializer -- 可选,初始参数
③返回值:
函数计算结果
from functools import reduce l=[1,2,3,4,5] print(reduce(lambda x,y:x+y,l,10))
九、递归调用
递归调用:在函数调用过程中,直接或间接地调用了函数本身,这就是函数的递归调用。
#二分法
l = [1, 2, 10,33,53,71,73,75,77,85,101,201,202,999,11111]
def search(find_num,seq):
if len(seq) == 0:
print('not exists')
return
mid_index=len(seq)//2
mid_num=seq[mid_index]
print(seq,mid_num)
if find_num > mid_num:
#in the right
seq=seq[mid_index+1:]
search(find_num,seq)
elif find_num < mid_num:
#in the left
seq=seq[:mid_index]
search(find_num,seq)
else:
print('find it')
search(77,l)
search(72,l)
search(-100000,l)
十、匿名函数
1、说明:
python 使用 lambda 来创建匿名函数,lambda 函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
2、语法:
lambda [arg1 [,arg2,.....argn]]:expression
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
结合max,min,zip,sorted的使用
#max,min,zip,sorted的用法
salaries={
'egon':3000,
'alex':100000000,
'wupeiqi':10000,
'yuanhao':2000
}
# print(max(salaries)) # max()只比较key值
res=zip(salaries.values(),salaries.keys())
# print(list(res))
print(res)
print(max(res)) # 注意:迭代器、生成器等只能遍历一次,上面print(list(res))遍历之后,此处会报错
# def func(k):
# return salaries[k]
#
# print(max(salaries,key=func)) # (此时比较value值)
print(max(salaries,key=lambda k:salaries[k])) # (此时比较value值)
print(min(salaries,key=lambda k:salaries[k])) # (此时比较value值)
print(sorted(salaries)) # 默认的排序结果是从小到大,默认比较key值
print(sorted(salaries,key=lambda x:salaries[x])) # 默认的排序结果是从小到大(此时比较value值)
print(sorted(salaries,key=lambda x:salaries[x],reverse=True)) # reverse=True:排序结果从大到小(此时比较value值)
结合map、reduce、filter的使用
l=['alex','wupeiqi','yuanhao']
res=map(lambda x:x+'_SB',l)
print(res)
print(list(res))
nums=(2,4,9,10)
res1=map(lambda x:x**2,nums)
print(list(res1))
from functools import reduce
l=[1,2,3,4,5]
print(reduce(lambda x,y:x+y,l,10))
l=['alex_SB','wupeiqi_SB','yuanhao_SB','egon']
res2=filter(lambda x:x.endswith('SB'),l)
print(res2)
print(list(res2))

浙公网安备 33010602011771号