1、LEGB的概念。
local,本地作用域、局部作用域的local命名空间。函数作用域的local命名空间。函数调用时创建,调用结束时候消亡。
Enclosing:嵌套函数外层函数的命名空间。
Global:全局作用域。一个模块的命名空间.模块被import时候创建,解释器退出时消亡。
Bulid-in。内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open)都是内置的变量。
所以一个名词的查找顺序就是LEGB。(从本级逐级向外找)。
2、递归函数。
1)函数执行流程。(调用函数,保存当前的内容,压栈函数并创建栈帧。执行里面的语句)
全局帧中生成foo1、foo2、foo3、main的函数对象。(栈,先进后出,后进先出)。
main函数调用
main 中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶。
main中全局函数foo1压栈,将常量100,101压栈,调用函数foo1,创建栈帧。Print函数压栈,字符串和变量b、b1压栈,调用函数,弹出栈帧,返回值。
Main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。Foo3完成print函数调用后返回。foo2回复调用,执行print后,返回值。Main中foo2调用结束弹出栈顶,main函数继续执行print函数调用,弹出栈顶,main函数返回。
2)递归:函数直接或者间接调用自身就是递归。
递归需要有边界条件、递归前进段、递归返回段。
递归一定要有边界条件。
当边界不满足的时候递归前进。
当边界条件满足的时候,递归返回。
斐波那契数列:
pre = 0
cur = 1
print(pre,cur,end = ‘ ‘)
n=4
for i in range(n-1):
pre,cur = cur ,pre + cur
print(cur,end=’ ‘)
def fib(n):
return 1 if n<2 else fib(n-1)+fib(n-2)
for i in range(5):
print(fib(i),end=’ ‘)
3)递归要求
递归一定要有退出条件,递归调用一定要执行到这个退出条件,没有退出条件的递归调用,就是无限调用。
递归调用的深度不宜过深。
Python中对递归调用的深度做了限制,以保护解释器。
超过递归调用深度,会抛出异常的。
4)递归的性能
循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算到结果。
改进。左边的fib函数和循环的思想类似。
参数n是边界条件,用n来计数。
上一次的计算结果作为下一次结果的实参。
import datetime
n=35
start = datetime.datetime.now()
def fib(n):
return 1 if n<2 else fib(n-1)+fib(n-1)
for i in range(n):
print(fib(i),end=”)
delta = (datetime.datetime.now()-start).total_seconds()
print(delta)
效率比较低。
pre = 0
cur = 1
print(pre,cur,end=’ ‘)
def fib(n,pre=0,cur=1):
pre,cur = cur,pre + cur
print(cur,end=’ ‘)
if n == 2:
return
fib(n-1,pre,cur)
print(fib(5))
#斐波那契数列改进方式
左边的fib函数和循环的思想类似
参数n是边界条件,用n来计数。
上一次的计算结果作为函数的实参。
效率很高
和循环相比,性能相近,所以说递归效率不一定很低。但是深度有限。
5)间接递归
def foo1():
foo2()
def foo2():
f1oo()
foo1()
是通过别的函数调用了函数本身。
但是,如果构成了循环递归调用时非常危险。
6)递归总结
是一种很自然的表达,符合逻辑思维。
相对运行效率较低,每次调用函数都要开辟栈帧
递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了。
如果有限次数的递归,可以使用递归调用,或者使用循环替代,循环代码稍微复杂,但是只要不是死循环,可以多次迭代直至算出结果。
绝大多数递归,都可以使用循环实现。
即使代码很简单,但是能不用则不用递归。
7)递归练习题
阶乘:def fn1(n):
if n==1:
return 1
return n*fn1(n-1)
print(fn1(5))
倒向打印:
f1 = str(1234)
def fn4(n):
if n == -1:
return ”
else:
return f1[n]+fn4(n-1)
print(fn4(len(f1)-1))
解决猴子摘桃
def peach(day=9,sum=1):
sum = 2*(sum+1)
day -=1
if day ==0:
return sum
return peach(day,sum)
peach()
斐波那契数列:
def fib(n): if n<3: return 1 else: return fib(n-1)+fib(n-1) print(fib(5))
3、匿名函数
匿名:就是没有名字。
匿名函数,即没有名字的函数
没有名字如何定义,如何调用,如果能调用,如何使用。
Python借助lambda表达式构建匿名函数。
格式:
Lambda参数列表:表达式
(Lambda x:x**2)(4)
#foo = lambda x:x+1
foo(1)
上下式等同:
#def foo(x):
return x+1
foo(1)
使用关键字来定义匿名函数:
参数列表不需要小括号
冒号是分隔参数和表达式的
不需要return ,表达式的值就是匿名函数的返回值。
只能写在一行上面,被称为单行函数。
用途:在高阶函数传参时候,使用lambda表达式,往往能简化代码.
print((lambda x,y=3:x+y)(5))
print((lambda :0)())
print((lambda x,y=3:x+y)(5,6))
print((lambda x,*,y=30:x+y)(5))#y 使用的是缺省值。
print((lambda *args:(x for x in args))(*range(5)))
print((lambda *args:[x+1 for x in args])(*range(5)))
[1, 2, 3, 4, 5]
print((lambda *args:{x+1 for x in args})(*range(5)))
[(1, (0, 1, 2, 3, 4)),
(2, (0, 1, 2, 3, 4)),
(3, (0, 1, 2, 3, 4)),
(4, (0, 1, 2, 3, 4)),
(5, (0, 1, 2, 3, 4))]
4、生成器函数。
1)生成器指的是生成器对象,可以有生成器表达式得到,也可以使用yield关键字得到一个生成器函数,调用这个函数得到一个生成器对象。(只要有yield就是生成器函数,调用的时候返回的就是生成器对象,一般都是放在for 循环后面的。) 与之对应的 (预热,预加载,一次性返回所有的值,缓存。)
生成器函数:
函数体内包括yield语句的函数,返回生成器对象,
生成器对象,是一个可迭代对象,是一个迭代器。
生成器对象,是延迟计算、惰性求值。
2)def inc():
for i in range(5):
yield i
print(type(inc()))
print(type(inc()))
x = inc()
print(type(inc()))
print(type(inc()))
for m in x:
print(m,’*’)
for m in x:
print(m,’*’)
普通函数的调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数进行多次执行。
生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂。
def gen():
print(‘line1’)
yield 1
print(‘line2’)
yield 2
print(‘line3′)
return 3
next(gen())
next(gen())
g=gen()
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g),’end’)
3)在生成器函数中,使用多个yield语句,执行一次后暂停执行,把yield表达式的值返回。
再次执行会执行下一个yield语句。
Return语句依然可以终止函数,单return语句的返回值不会被获取的到。
Return会导致无法继续获取下一个值,抛出stopiteration异常。
如果函数没有显示的return语句,如果生产器函数执行到结尾,一样会抛出异常的。
4)生成器函数总结:
包含yield语句的生成器函数生成生成器对象的时候,生成器函数不会立即被执行。
Next(generator)会从函数的当前位置向后执行到碰到的第一个yield语句,会弹出值,并暂停函数执行。
再次调用next函数,和上一条一样的处理过程。
没有多余的yield语句能被执行,继续调用next函数,会抛出异常的。stopiteration
Generator生成器对象。
5、生成器应用;
1)def counter():
i = 0
while True:
i += 1
yield i
def inc(c):
return next(c)
c = counter()
print(inc(c))
print(inc(c))
返回结果为 1,2 因为给counter赋值c了,每次重新调用的时候提前使用
def counter():
i = 0
while True:
i +=1
yield i
def inc():
c= counter()
return next(c)
print(inc())
print(inc())
print(inc())
返回结果为1,1.print的时候是三个不同的对象。
def counter():
i = 0
while True:
i +=1
yield i
def inc():
c= counter()
return next(c)
print(inc())
print(inc())
print(inc())
def inn():
def counter():
i = 0
while True:
i +=1
yield i
c = counter()
return lambda:next(c)
foo = inc()
print(foo())
print(foo())
上式和下式是等同的。
2)协程coroutine
生成器的高级用法
比进程、线程轻量级。 是在用户空间调度函数的一种实现。
Python3 asyncio就是协程实现,已经加入到标准库。
Python3.5中使用async,await关键字职业原生支持协程。
协程调度器实现思路
有两个生成器a和b
Next(A)后,A执行到了yield语句暂停,然后执行next(B),b执行到yield语句也暂停,然后再次调用next(a),在调用next(B),周而复始,就实现了调度的效果。
可以引用调度的策略来实现切换方式。
协程是一种非抢占式调度。
3)yield from:
For x in range(100)
Yield x
等价于
yield from range(100)
def inc():
for x in range(1000):
yield x
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
def inc():
yield from range(1000)
foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))
yield from 是python3.3出现的新的语法。
yield from iterable是for item in iterable:yield item形式的语法糖。
从迭代对象中一个个拿元素。
def counter(n):
for x in range(n):
yield x
def inc(n)
6、高阶函数、柯里化。
1)First class object
函数在python中是一等公民
函数是对象,可调用的对象。
函数可以作为普通变量、参数、返回值等。
2)高阶函数
数学概念,y=g(f(x))
高阶函数满足条件;(1)接受一个或者多个函数作为参数.(2)输出一个函数。
加括号和不加括号概念是完全不一样的。
3)高阶函数的事例
#def counter(base):
def inc(step=1):
nonlocal base
base +=step
return base
return inc
c=counter(10)
c1 = counter(10)
print(c())
print(c1())
高阶函数。
如果 c 不等于c1 也不是 is and == 为假。
如果 c()等于c1() is and == 是真值。
c is c1
False
c ==c1
False
c() is c1()
True
c() ==c1()
True
4)自定义sort函数。
#初步代码
def sort(iterable,reverse = False,key = None ):
lst = []
for x in iterable:
for i,y in enumerate(lst):
if x>y:
lst.insert(i,x)
break
else:
lst.append(x)
return lst
sort([1,5,6,7,4,3,2,9,8,0])
#out
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
#第二步优化:
def sort(iterable,reverse = False,key = lambda x,y:x>y ):
lst = []
for x in iterable:
for i,y in enumerate(lst):
#flag = True if x>y else False
if key(x,y):
lst.insert(i,x)
break
else:
lst.append(x)
return lst
sort([1,5,6,7,4,3,2,9,8,0])
#第三步
def sort(iterable,reverse = False,key = lambda x,y:x>y ):
lst = []
for x in iterable:
for i,y in enumerate(lst):
#flag = True if x>y else False
if key(x,y):
lst.insert(i,x)
break
else:
lst.append(x)
return lst
sort([1,5,6,7,4,3,2,9,8,0],key = lambda x,y:x<y)
#第四步
def sort(iterable,reverse = False,key = lambda x,y:x>y ):
lst = []
for x in iterable:
for i,y in enumerate(lst):
flag = key(x,y) if not reverse else not key(x,y)
if flag:
lst.insert(i,x)
break
else:
lst.append(x)
return lst
sort([1,5,6,7,4,3,2,9,8,0],key = lambda x,y:x<y)
利用索引,i,x这些。。
5)内建函数-高阶函数。
sorted(iterable[,key][,reverse])
排序
(1)sorted(iterable[,key][,reverse]) 返回一个新的列表,对一个可迭代对象的所有元素进行排序,按照key定义的函数。reverse表示是否反转
Sorted(lst,key=lambda x: 6-1) 返回新的列表
List.sort(key=lambda x:6-1)就地修改。
(2)filter(function,iterable)
过滤函数
过滤可迭代对象的元素,返回一个迭代器
function一个具有一个参数的函数,返回bool。
#list(filter(lambda x: x%3==0,[1,9,55,150,-3,78,28,123]))
#out [9, 150, -3, 78, 123]
(3)map(func,*iterables)—map object
映射
对多个可迭代对象的元素按照指定的函数进行映射,返回一个迭代器。
#list(map(lambda x:2*x+1,range(5)))
#out [1, 3, 5, 7, 9]
#dict(map(lambda x:(x%5,x),range(500)))
#{0: 495, 1: 496, 2: 497, 3: 498, 4: 499}
(4)filter(function or None,iterable)
(5)map函数一个参数的函数。后面假定送进来的是一个元素的对象。
一般送进去的参数都是使用lambda。 lambda使用的是一个参数。
map后面的可迭代元素后面只能出现一个元素。
dict(map(lambda x,y: (x,y),zip(‘abcdfe’,range(5))))
这种表达式是错误的,前面的形参是两个,但是后面的实参却是二元组,条件不满足。
7、柯里化:
1)柯里化:指的是将原来接受两个元素的函数,变成接受一个参数的函数的过程,新的函数返回一个以原有第二个参数的函数。z=f(x,y) z=f(x)(y)
#例子
def add(x):
def _add(y):
return x+y
return _add
t=add(4)(5)
print(t)
通过嵌套函数就可以把函数转换为柯里化函数。
8、装饰器。
1)print打印语句耦合太高,输出信息的功能,属于非业务代码。灵活度太差。
print在业务代码中是属于侵入式代码。
2)装饰器
#第一步代码
def add(x,y):
return x+y
def logger(fn):
print(‘before’)
ret=fn(4,5)
print(‘end’)
return ret
print(logger(add))
#第二步多个参数优化
def add(x,y):
return x+y
def add1(x,y,*,z=6):
return x+y+z
def logger(fn,*args,**kwargs):
print(‘before’)
ret=fn(*args,**kwargs)
print(‘end’)
return ret
a=logger(add1,4,5,z=7)
print(a)
#第三步
def add(x,y):
return x+y
def add1(x,y,*,z=6):
return x+y+z
def logger(fn):
def _logger(*args,**kwargs):
print(‘before’)
ret=fn(*args,**kwargs) #调用add1
print(‘end’)
return ret
return _logger
add1=logger(add1) #内层函数_logger
a=add1(4,5,z=7) #logger(add1,4,5,z=7)
print(a)
#第四步
def add(x,y):
return x+y
def add1(x,y,*,z=6):
return x+y+z
def logger(fn):
def _logger(*args,**kwargs):
print(‘before’)
ret=fn(*args,**kwargs) #调用add1
print(‘end’)
return ret
return _logger
#add1=logger(add1) #内层函数_logger
a=logger(add1)(4,5,z=7) #logger(add1,4,5,z=7)
print(a)
#第五步
def add(x,y):
return x+y
def logger(fn):
def _logger(*args,**kwargs):
print(‘before’)
ret=fn(*args,**kwargs) #调用add1
print(‘end’)
return ret
return _logger
@logger
def add1(x,y,*,z=6): #add1 = logger(add1)
return x+y+z
#add1=logger(add1) #内层函数_logger
a=add1(4,5,z=7) #logger(add1,4,5,z=7)
print(a)
#第六步包装函数wrapper
def logger(fn):
def wrapper(*args,**kwargs):
print(‘before’)
ret = fn(*args,**kwargs)
print(‘end’)
return ret
return wrapper
@logger
def add(x,y,*,z=6):
return x+y+z
@logger
def add1(x,y): #add=logger(add)
return x+y
#b=logger(add)
a=add(4,5,z=10)
print(a)
3)@logger的语法格式。
装饰器(无参)
他是一个函数,函数作为他的形参。
返回的值也是一个函数。
可以使用@function调用。
Wrapper表示被包装函数。
Wrapperd表示包装函数。
4)装饰器和高阶函数。
装饰器是高阶函数,但是装饰器是对传入的函数的功能进行装饰,功能增强。
#装饰器
增强函数,而不是非侵入式函数。把函数名作为其参数。
import datetime
import time
def logger(fn):
def wrapper(*args,**kwargs):
print(“args={},kwargs={}”.format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
duration = datetime.datetime.now() – start
print(“function{}took{}s.”.format(fn.__name__,duration.total_seconds()))
return ret
return wrapper
@logger #add = logger(add)
def add(x,y):
print(“===call add =======”)
time.sleep(2)
return x+y
print(add(4,y=1))
装饰器:装饰器函数,前置功能增强,被增强函数,后置功能增强。
#import datetime
import time
def logger(fn):
def wrapper(*args,**kwargs):
print(“{}{}”.format(args,kwargs))
start = datetime.datetime.now()
n = fn(*args,**kwargs)
duration=datetime.datetime.now() – start
print(“function{}took{}s”.format(fn.__name__,duration.total_seconds()))
return n
return wrapper
@logger
def add(x,y):
print(“====call add =======”)
time.sleep(2)
return x+y
b=add(3,y=4)
print(b)
9、文档字符串。
1)Python是文档字符串。Documentation Strings。
在函数语句块的第一行,且习惯是多行的文本,所以多行使用三引号。
惯例是首字母大写。第一行写概述,空一行,第三行写详细描述,
可以使用特殊属性__do__ 访问这个文档。
必须写在第一行。
#
def add(x,y):
“””This is s function of addition””” 文档字符串。
a=x+y
return x+y
print(“name={} \n doc={}”.format(add.__name__,add.__doc__))
print(help(add))
name=add
doc=This is s function of addition
Help on function add in module __main__:
add(x, y)
This is s function of addition
None
2)存在副作用,因为原函数对象的属性都被替换了。
3)#第一次代码
import datetime
import time
def copy_properties(src,dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
def logger(fn):
def wrapper(*args,**kwargs):
print(“args={},kwargs={}”.format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
duration = datetime.datetime.now() – start
print(“function{}took{}s.”.format(fn.__name__,duration.total_seconds()))
return ret
copy_properties(fn,wrapper)
return wrapper
@logger #add = logger(add)
def add(x,y):
“””this is a add function”””
print(“===call add =======”)
time.sleep(2)
return x+y
print(add(4,y=1),add.__name__,add.__doc__)
#第二次代码(带参装饰器)
import datetime
import time
def copy_properties(src):
def _inner(dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
return dest
return _inner
def logger(fn):
@copy_properties(fn)
def wrapper(*args,**kwargs):
“””I am wrapper”””
print(“args={},kwargs={}”.format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
duration = datetime.datetime.now() – start
print(“function{}took{}s.”.format(fn.__name__,duration.total_seconds()))
return ret
# copy_properties(fn,wrapper)
return wrapper
@logger #add = logger(add)
def add(x,y):
“””this is a add function”””
print(“===call add =======”)
time.sleep(2)
return x+y
print(add(4,y=1),add.__name__,add.__doc__)
标示符和名称并不是一一对应的。
3)带参装饰器
通过copy_properties函数将被包装函数的属性覆盖掉包装函数。
凡是被装饰的函数都需要复制这些属性,这个函数很通用。
可以将复制属性的函数构建成装饰器函数,带参装饰器。(带参装饰器进行柯里化)。
本质:装饰器函数,装饰别的函数,增强别的(业务函数)函数功能。
#带参装饰器代码
import datetime
import time
def copy_properties(src):
def _inner(dest):
dest.__name__ = src.__name__
dest.__doc__ = src.__doc__
return dest
return _inner
def logger(durtion)
def _logger(fn):
@copy_properties(fn)
def wrapper(*args,**kwargs):
“””I am wrapper”””
print(“args={},kwargs={}”.format(args,kwargs))
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() – start).total_seconds()
print(duration)
if delta>duration:
print(‘low’)
else:
print(‘fast’)
#print(“function{}took{}s.”.format(fn.__name__,duration.total_seconds()))
return ret
# copy_properties(fn,wrapper)
return wrapper
return _logger
@logger(5) #add = logger(add)
def add(x,y):
“””this is a add function”””
print(“===call add =======”)
time.sleep(2)
return x+y
print(add(4,y=1),add.__name__,add.__doc__)
- 带参装饰器:是一个函数。函数作为他的形参,返回值是一个不带参的装饰器函数。
使用@functionname(参数列表)方式调用,
可以看做是在装饰器外层又加了一层函数。
函数作为形参。
10、functools模块。
Functools.update_wrapper(wrapper,wrappered,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_WPDATES)
类似copy_properties功能
Wrapper包装函数、被更新者,wrapper被包装函数、数据源。
元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性。
‘__module__’, ‘__name__’, ‘__qualname__’, ‘__doc__’, ‘__annotations__’
模块名、名称、限定名、文档、参数注解
元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典。
增加一个__wrapped__属性,保留着wrapped函数。
#代码块
import functools,time,datetime
def logger(durtion,func=lambda neme,durtion:print(‘{}took{}s’.format(name,durtion))):
def _logger(fn):
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
n = fn(*args,**kwargs)
delta = (datetime.datetime.now() – start).total_seconds()
if delta > durtion:
func(fn.__name__,duration)
return n
return functools.update_wrapper(wrapper,fn)
return _logger
@logger(5) #add = logger(5)(add)
def add(x,y):
time.sleep(1)
return x+y
print(add(5,6),add.__name__,add.__wrapped__,add.__dict__,sep=’\n’)
11、类型注解
1)函数定义的弊端。
Python是动态语言,变量可以被赋值,且赋值为不同类型。
难发现,由于不做任何类型检查,直到运行期问题才显现出来,或者线上运行才可以发现。
难使用:函数的使用者看到这个函数的时候,并不知道其函数设计,并不知道应该传入什么类型的参数。
2)如何解决弊端
(1)增加文档注释。(弊端:函数定义更新了,文档未必同步更新).使用双的三引号。
(2)函数注解:只是一个提示性文件。对函数参数进行类型注解。对函数参数做一个辅助的说明,并不对函数参数进行类型检查。第三方工具,做代码分析,发现隐藏的bug。
.__annotations__。
def add(x,y):
“””
:param x:
:param y:
:return: int
“””
return x+y
print(help(add))
(3)3.6.3进行的变量注解。I:int = 3
3)业务应用;函数参数类型检查
(1)函数参数检查,一定是在函数外。
函数应该作为参数,传入到检查函数中。
检查函数拿到函数传入的实际参数。与形参声明进行对比。
__annotations__属性是一个字典,其中包括返回值类型的声明使用inspect模块。
- inspect。提供获取对象信息的函数,可以检查函数的类、类型检查。
import inspect
def add(x:int,y:int,*args,**kwargs):
return x+y
sig = inspect.signature(add)
print(sig,type(sig))
print(‘params:’,sig.parameters)
print(‘return:’,sig.return_annotation)
print(sig.parameters[‘y’],type(sig.parameters[‘y’]))
print(sig.parameters[‘x’])
print(sig.parameters[‘args’])
print(sig.parameters[‘args’].annotation)
print(sig.parameters[‘kwargs’])
print(sig.parameters[‘kwargs’].annotation)
#第一个print:(x:int, y:int, *args, **kwargs) <class ‘inspect.Signature’> sig表现出来的是对象,对象是signature类型
#第二个print:params: OrderedDict([(‘x’, <Parameter “x:int”>), (‘y’, <Parameter “y:int”>), (‘args’, <Parameter “*args”>), (‘kwargs’, <Parameter “**kwargs”>)]) 类型是映射代理 ,返回的是有序的字典。Orderdict。
#第三个print:return: <class ‘inspect._empty’> 通过__annotation查看的是声明的类型。
#第四个print:y:int <class ‘inspect.Parameter’> 返回的是一个类
#第五个print:x:int 返回的是一个类
#第六个print:*args 返回的是一个类
#第七个print:<class ‘inspect._empty’> 表示声明的类型,未声明表示空
#第八个print:**kwargs 返回的是一个类
#第九个print:<class ‘inspect._empty’> 表示声明的类型,未声明表示空
4)模块提供的信息:
Signature(callable)获取签名,(函数签名包含了一个函数的信息包括函数名、参数类型,所在的类和名称空间及其其他信息)
Def add(x,y): add(x,y)就是函数的签名:
- signature(callable,*,follow_wrapped=True)
- Params=sig.parameters()
可变类型收集的就是不同类型数据的,所有后面没有必要加类型注解。
5)模块提供的信息
inspect.isfunction(add) #是否是函数
inspect.ismethod(add) #是否是类方法
inspect.isgenerator(add) #是否是生成器对象
inspect.isgeneratorfunction(add) #是否是生成器函数
inspect.isclass(add) #是否是类
inspect.ismodule(inspect) #是否是模块
inspect.isbuiltin(print) #是否是内建对象
6)Parameter对象
保存在元组中,只是只读的。
name参数的名字。
annotaion参数的注解,可能没有定义。
Default参数的缺省值,可能没有定义。
empty,特殊得类,用来标记default属性或者注释annotation属性的空值。
Kind实参如何绑定到形参,就是形参的类型。
POSITIONAL_ONLY ,值必须是位置参数提供
POSITIONAL_OR_KEYWORD,值可以作为关键字或者位置参数提供。
VAR_POSITIONAL,可变位置参数,对应*args
KEYWORD_ONLY,keyword-noly参数,对应*或者*args之后出现的非可变关键字参数。
VAR_KEYWORD,可变关键字参数,对应**kwargs。
7)#课堂例子:
import inspect
def add(x,y:int=1,*args,z,t=10,**kwargs):
return x+y
sig = inspect.signature(add)
print(sig)
print(‘params:’,sig.parameters)
print(‘return:’,sig.return_annotation)
print(‘~~~~~~’)
for i,item in enumerate(sig.parameters.items()):
name,param = item
print(i+1,name,param.annotation,param.kind,param.default)
print(param.default is param.empty,end=’\n\n’)
(x, y:int=1, *args, z, t=10, **kwargs)
params: OrderedDict([(‘x’, <Parameter “x”>), (‘y’, <Parameter “y:int=1”>), (‘args’, <Parameter “*args”>), (‘z’, <Parameter “z”>), (‘t’, <Parameter “t=10”>), (‘kwargs’, <Parameter “**kwargs”>)])
return: <class ‘inspect._empty’>
~~~~~~
1 x <class ‘inspect._empty’> POSITIONAL_OR_KEYWORD <class ‘inspect._empty’>
True
2 y <class ‘int’> POSITIONAL_OR_KEYWORD 1
False
3 args <class ‘inspect._empty’> VAR_POSITIONAL <class ‘inspect._empty’>
True
4 z <class ‘inspect._empty’> KEYWORD_ONLY <class ‘inspect._empty’>
True
5 t <class ‘inspect._empty’> KEYWORD_ONLY 10
False
6 kwargs <class ‘inspect._empty’> VAR_KEYWORD <class ‘inspect._empty’>
True
8)业务应用
有函数如下
def add(x, y:int=7) -> int:
return x + y
请检查用户输入是否符合参数注解的要求
#第一步代码:解决位置参数(传入的参数为位置参数)
思路:1,为了不侵入原来代码,所以使用,装饰器。
2,导入inspect模块。
3,利用sig获取函数签名(add)
4,利用sig.paramters(fn)获取对象,是一个有序的字典。
5,字典里面的k放在字典里面,为list(params.keys())。
6,字典里面的v值放在字典里面,作为找一个列表,list(paramts.values())
7,用户输入的值利用迭代器取出,判断用户输入的values是否和定义的类型一样。
8,如果为真,打印。
import inspect
def check(fn):
#@funtools.wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters #有序字典
#keys = [x for x in params.keys()]
keys = list(params.keys())
values = list(params.values()) #参数对象列表
for i,val in enumerate(args):
if isinstance(val,values[i].annotation):
print(keys[i],’==’,val)
#n = fn(*args,**kwargs)
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=7)->int:
return x + y
print(add(4,5))
#第二步代码,解决关键词参数传参。(传入的实参采用关键字)
思路:1,迭代,k,v inkwargs.Items()迭代传参后的字典。
2,判断类型v的类型,与签名得到的有序字典的类型判断是否一致。
3,类型一致就进行打印。
def check(fn):
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters #有序字典
keys = list(params.keys()) #定义的
values = list(params.values())# 定义的
for i,val in enumerate(args): #形参和实参声明一致
if isinstance(val,values[i].annotation):
print(keys[i],’==’,val)
for k,v in kwargs.items():
if isinstance(v,params[k].annotation):
print(k,’====’,v)
return fn (*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=1):
return x+y
print(add(3,y=4))
#第三步,解决传参为位置参数和关键字参数混合传参。
import inspect
def check(fn):
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
keys = list(params.keys())
values = list(params.values())
for i,val in enumerate(args):
if isinstance(val,values[i].annotation):
print(keys[i],’==’,val)
for k,v in kwargs.items(): #迭代定义的传参后的字典,
if isinstance(v,params[k].annotation): #判断传参后的v与定义的比较。
print(k,’===’,v)
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=1):
return x+y
print(add(3,y=4))
#第四步;解决没有注解的不进行比较的问题:
思路:采用短路与解决,如果不是定义类型的,就不会进行下一步比较、
import inspect
def check(fn):
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
keys = list(params.keys())
values = list(params.values())
for i,val in enumerate(args):
if values[i].annotation is not inspect._empty and isinstance(val,values[i].annotation):
print(keys[i],’==’,val)
for k,v in kwargs.items(): #迭代定义的传参后的字典,
if params[k].annotation is not params[k].empty and isinstance(v,params[k].annotation): #判断传参后的v与定义的比较。
print(k,’===’,v)
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=1):
return x+y
print(add(3,y=4))
Inpsect模块主要是做检查,检查函数,缺省值等。
12、functools模块
1)Partial方法
2)偏函数,把函数部分的参数固定下来,相当于为部分参数添加了一个固定的默认值,形成一个新的函数返回。
3)一定返回的是新的函数,原来的函数包在内了。
(1)例题
from functools import partial
def add(x,y):
return x +y
newadd = partial(add,4)
print(newadd(5))
例题,把x固定了。
from functools import partial
import inspect
def add(x,y):
return x +y
newadd = partial(add,x=4)
print(newadd(y=5))
print(inspect.signature(newadd))
#打印出的:
9
(*,x=4, y)
根据签名得到,被固定的参数的前面增加,*,
#课堂例子:
import functools
def add(x,y):
return x+y
newadd = functools.partial(add,y=5)
print(newadd(7))
print(newadd(7,y=6))
print(newadd(y=10,x=6))
import inspect
print(inspect.signature((newadd)))
#12
13
16
(x, *, y=5)
被固定的是add的函数的y的值,所以签名是(x,*,y=5),所以传参的方式x使用位置或者关键字均可以,y可以使用关键字传参,不传参的话可以使用默认值。
例题:
import functools
def add(x,y,*args):
print(args)
return x+y
newadd = functools.partial(add,1,3,5,6)
print(newadd(7))
print(newadd(7,10))
print(newadd(9,10,x=2,y=4)) # 一定不可以,因为x,y被固定了,被填充满了,在添加值是不可以的。
print(newadd())
import inspect
print(inspect.signature(newadd))
4)partial函数的本质。(本质就是元组和字典合并。)
def partial(func,*args,**kwargs):
def newfunc(*fargs,**fkwargs):#包装函数
newkwargs = kwargs.copy() #关键字参数拷贝 (调用时候才会执行)
newkwargs.update(fkargs) #关键词传参参数全部更新,(调用时候才会执行)
return func(*(args+fargs),**newkwargs) #元素依次的合并,返回一个新的元组,不去重的。(调用时候才会执行)
newfunc.func = func #保留原来的函数
newfunc.args = args #保留原函数的位置参数 空元组
newfunc.kwargs = kwargs #保留原函数的关键字参数。 #字典。
return newfunc #返回一个新的函数
def add(x,y):
return x+y
foo = partial(add,4)
foo(5)
5)functools.lru_cache(maxsize=128,typed=False)
lru 最近最少使用,cache缓存。
如果maxsize设置的是none,所以禁用了LRU功能,并且缓存可以无限制的增长,当maxsize是2的次幂的时候,LRU执行良好。
如果typed设置为True,则不同类型的函数参数将单独缓存,例如;f(3)和f(3.0)视为不同结果的不同调用。
import functools
@functools.lru_chache()
def add(x,y,z=5):
time.sleep(z)
return x+y
print(add(4,5))
print(add(4.0,5))
print(add(4,6))
print(add(4,6,3))
print(add(6,4))
print(add(4,y=6))
print(add(x=4,y=6))
print(add(y=6,x=4))
总结:第一个print和第二个一样。
第七个和第八个一样。其余的不一样。
缓存机制是什么:利用字典,结果放在value中,k为add的参数。
lru_cache装饰器。
通过一个字典缓存被装饰函数的调用和返回值。
#第一个类型
import functools
functools._make_key((4,6),{‘z’:3},False)
#打印[4, 6, <object at 0x7f98aa654090>, ‘z’, 3]
返回的是一个列表。前面的元素是args。。后面是kwargs。<object at 0x7f98aa654090>代表的是符号。
#第二个类型
import functools
functools._make_key((4,6,3),{},False)
生成的是列表。
#打印出[4, 6, 3]
#第三个类型
functools._make_key(tuple(),{‘z’:3,’x’:4,’y’:6},False)
#打印出[<object at 0x7f98aa654090>, ‘x’, 4, ‘y’, 6, ‘z’, 3]进行了排序。
#第四个类型
functools._make_key(tuple(),{‘z’:3,’x’:4,’y’:6},True)
#打印出[<object at 0x7f98aa654090>, ‘x’, 4, ‘y’, 6, ‘z’, 3, int, int, int]
总结:pyted改为True显示类型。缺省值就是false。
Object把二元组里面的元素加进去。
_make_key..
顺序,拆开k显示的是不同的。
@functools.lru_cache()改造斐波那契数列。
import functools
@functools .lru_cache(maxsize=100)
def fib(n):
if n<3:
return 1
else:
return fib(n-1)+fib(n-2)
n=fib(5)
print(n)
import functools
@functools .lru_cache(maxsize=100)
def fib(n):
if n<3:
return 1
else:
return fib(n-1)+fib(n-2)
print([fib(x) for x in range(35)])
原因利用缓存:因为计算的结果下一项利用到了上一项。倒向运算。所有的计算所需的数据都来自缓存,无须再次计算啦。
6)总结:
使用前提,同样的函数参数一定得到同样的结果。
函数执行时间长,且要执行很多次。
本质上是函数调用函数 =》返回值。
缓存的缺点,不支持缓存过期,key无法过期,失效。不支持清除操作,不支持分布式,是一个单机的缓存。
使用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询。
练习题:
- 写一个命令分发器
#第一次代码
cmds = {} #利用空字典收集命令
def default_func(): #定义默认函数
pass
def reg(cmd,fn): #定义注册函数
cmds[cmd] = fn
def py(): #定义命令函数
print(‘py’)
def lin(): #定义命令函数
print(‘lin’)
#注册过程,名称cmd和function之间的关系。
reg(‘py’,py)
reg(‘lin’,lin)
def dispatcher(): (用户输入过程)
while True:
cmd = input(‘>>>’).strip()
if cmd == ‘quit’:
break
cmds.get(cmd,default_func)() 调用
dispatcher() 调用
#第二步代码,柯里化。
cmds = {}
def default_func():
pass
def reg(cmd):
def _reg(fn):
cmds[cmd] = fn
return fn
return _reg
def dispatcher():
while True:
cmd = input(‘>>>’).strip()
if cmd == ‘quit’:
break
cmds.get(cmd,default_func)()
@reg(‘py’)
def py():
print(‘py’)
@reg(‘lin’)
def lin():
print(‘lin’)
# reg(‘py’,py)
# reg(‘lin’,lin)
dispatcher()
#第三步:
ddef cmd_disapher():
cmds = {}
def default():
print(‘this is no command’)
def reg(cmd):
def _reg(fn):
cmds[cmd] = fn
return fn
return _reg
def disapher():
while True:
cmd = input(‘>>>’).strip()
if cmd == ‘quit’:
break
else:
cmds.get(cmd,default)()
return reg,disapher
reg,disapher = cmd_disapher()
@reg(‘py’)
def py():
print(‘python’)
@reg(‘lin’)
def lin():
print(‘linux’)
# reg(‘py’,py)
# reg(‘lin’,lin)
disapher()
- 课堂习题2
import inspect
from collections import OrderedDict
#import datetime
import datetime
local_cache = {}
def cache(fn):
def wrapper(*args,**kwargs):
def make_key(fn):
#key = ()
sig = inspect.signature(fn)
params = sig.parameters
keys = list(params.keys())
#values = list(params.values())
params_dict = OrderedDict()
#位置参数
for i,val in enumerate(args):
k = keys[i]
params_dict[k] = val
#key += (kyes[i],val)
#关键字参数
for k,v in sorted(kwargs.items()):
#k,v = item
params_dict[k] = v
#默认值参数
for k,param in params.items():
if k not in params_dict.keys():
params_dict[k] = param.default
return tuple(params_dict.items())
key = make_key(fn)
# 查询和缓存
if key not in local_cache.keys():
local_cache[key] = fn (*args,**kwargs)
return local_cache[key]
return wrapper
def logger(fn):
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now()- start).total_seconds()
print(fn.__name__,delta)
return ret
return wrapper
@logger
@cache
def add(x,y,z=6):
return x+y+z
add(2,3,z=4)
add(2,3,4)
add(4,5)##
add(x=2,y=3,z=4)
#第二步代码
import inspect
from collections import OrderedDict
#import datetime
import datetime
local_cache = {}
def cache(fn):
def wrapper(*args,**kwargs):
def make_key(fn):
#key = ()
sig = inspect.signature(fn)
params = sig.parameters
keys = list(params.keys())
#values = list(params.values())
params_dict = OrderedDict()
#位置参数
for i,val in enumerate(args):
k = keys[i]
params_dict[k] = val
#key += (kyes[i],val)
#关键字参数
# for k,v in sorted(kwargs.items()):
# #k,v = item
# params_dict[k] = v
#默认值参数
for k,param in params.items():
if k not in params_dict.keys():
if k in kwargs.keys():
params_dict[k] = param.default #将上句循环改为一个循环。
else:
params_dict[k] = v.default
return tuple(params_dict.items())
key = make_key(fn)
# 查询和缓存
if key not in local_cache.keys():
local_cache[key] = fn (*args,**kwargs)
return local_cache[key]
return wrapper
def logger(fn):
def wrapper(*args,**kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now()- start).total_seconds()
print(fn.__name__,delta)
return ret
return wrapper
@logger
@cache
def add(x,y,z=6):
return x+y+z
add(2,3,z=4)
add(2,3,4)
add(4,5)##
add(x=2,y=3,z=4)
13、装饰器的作用
1)装饰器是AOP面向切面编程的思想体现。
面向对象往往需要通过继承或者组合依赖等方式调用一些功能,这些功能的代码往往可能在多个类中出现。比如logger,这样造成代码的重复,增加了耦合,logger改变影响所有使用他的类或者方法。
而AOP在需要的类或方法上切下,前后的切入点可以加入增强的功能,比如logger函数功能就是对 业务函数增加日志,而业务函数中应该把业务函数无关的日志功能剥离干净。
- 装饰器的应用场景:
日志、监控、权限、设计、参数检查、路由等处理:
这些功能与业务功能无关,很多业务都需要公共功能,所以适合独立出来,需要的时候,对目标对象增强。
本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/96813