支持函数式编程的包 functools和operator
https://docs.python.org/3/library/functools.html https://docs.python.org/3/library/operator.html
# reduce函数和匿名函数实现阶乘计算
from functools import reduce
def fact(n):
print(reduce(lambda a, b: a * b, range(1, n+1)))
return reduce(lambda a, b: a * b, range(1, n+1))
fact(6)
运行结果:
720
# reduce函数和匿名函数实现阶乘计算
from functools import reduce
from operator import mul
def fact(n):
print(reduce(mul, range(1, n+1)))
fact(6)
运行结果:
720
使用functools.partial 固定参数
# 使用functools.partial 固定参数
from functools import partial
from operator import mul
triple = partial(mul, 3)
print(triple(7))
运行结果:
21
- 将partial 应用于 make_element上
# 使用functools.partial 固定参数
from functools import partial
from operator import mul
# 生产HTML标签
def make_element(name, *contents, cls=None, **attrs):
if cls:
attrs['class'] = cls
pairs = [f"{attr}={value}" for attr, value in attrs.items()]
attr_str = ' '.join(pairs)
if not contents:
return f"<{name} {attr_str}/>"
elements = [f"<{name} {attr_str}>{content}</{name}>" for content in contents]
return '\n'.join(elements)
picture = partial(make_element, 'img', cls='pic-frame')
print(picture(src='car.jpg'))
item = partial(make_element, 'item', size='large')
print(item('a', 'b', 'c'))
运行结果:
<img src=car.jpg class=pic-frame/>
<item size=large>a</item>
<item size=large>b</item>
<item size=large>c</item>
解包与多返回值
任何序列或可迭代对象都可以通过一个简单的赋值操作来分解为单独的变量
def test():
return 1, 2
a, b = test()
a, b, c = [1, 2, 3]
d1 = {"a": 1, "b": 2}
d2 = {"c": 3, "d": 4}
d = {**d1, **d2}
print(d)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
装饰器
# 举例: 我们想获得函数的运行时间
import time
def record_time(func):
print("Run decorator.")
def wrapper():
start = time.time()
result = func()
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
def func_foo():
time.sleep(2)
print("Run func_foo.")
func = record_time(func_foo)
func()
运行结果:
Run decorator.
Run func_foo.
func_foo 2.000586986541748
- 装饰器语法糖@
import time
from functools import wraps
import time
def build_func_with_record(func):
print("Run decorator.")
def wrapper():
start = time.time()
result = func()
end = time.time()
print(func.__name__, end - start)
return wrapper
@build_func_with_record
def func_bar():
time.sleep(1)
print("Run func_bar.")
func_bar()
运行结果:
Run decorator.
Run func_bar.
func_bar 1.0002179145812988
- 带参数
"""
重点强调:
- 装饰器一般来说不会修改函数的签名
- 也不会修改被包装函数的返回结果
- 使用*args, **kwargs是为了确保可以接受任何形式的输入参数
- 装饰器的返回值几乎总是同调用func(*args, **kwargs)的结果一致
"""
import time
def record_time(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
@record_time
def make_element(name, *contents, cls=None, **attrs):
"""
生成html标签元素
"""
if cls:
attrs['class'] = cls
pairs = [f"{attr}={value}" for attr, value in attrs.items()]
attr_str = ' '.join(pairs)
if not contents:
return f"<{name} {attr_str}/>"
elements = [f"<{name} {attr_str}>{content}</{name}>" for content in contents]
return '\n'.join(elements)
make_element('img', cls='pic-frame', src='a.jpg')
运行结果:
make_element 0.0010006427764892578
- 保存元数据
"""
上述装饰器的使用,导致原始函数的元数据丢失
像函数名,文档字符串,调用签名等等
"""
print(make_element.__name__)
print(make_element.__doc__)
运行结果:
make_element
None
from inspect import signature
print(signature(make_element))
运行结果:
(*args, **kwargs)
引入@wraps
from functools import wraps
import time
from inspect import signature
def record_time(func):
"""
通过事先获取func的属性,并储存
在返回的最终的函数中设置上述属性
"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
@record_time
def make_element(name, *contents, cls=None, **attrs):
"""
生成html标签元素
"""
if cls:
attrs['class'] = cls
pairs = [f"{attr}={value}" for attr, value in attrs.items()]
attr_str = ' '.join(pairs)
if not contents:
return f"<{name} {attr_str}/>"
elements = [f"<{name} {attr_str}>{content}</{name}>" for content in contents]
return '\n'.join(elements)
print(make_element.__name__)
print(make_element.__doc__)
print(signature(make_element))
运行结果:
make_element
生成html标签元素
(name, *contents, cls=None, **attrs)
- 可接受参数的装饰器
"""
编写一个为函数添加日志功能的装饰器
允许用户指定日志的等级,以及一些其他的细节作为参数
"""
from functools import wraps
import logging
logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s')
logging.root.setLevel(logging.NOTSET)
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else f"Function <{func.__name__}> is running."
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
@logged(logging.DEBUG)
def make_element(name, *contents, cls=None, **attrs):
"""
生成html标签元素
"""
if cls:
attrs['class'] = cls
pairs = [f"{attr}={value}" for attr, value in attrs.items()]
attr_str = ' '.join(pairs)
if not contents:
return f"<{name} {attr_str}/>"
elements = [f"<{name} {attr_str}>{content}</{name}>" for content in contents]
return '\n'.join(elements)
print(make_element('img', cls='pic-frame', src='a.jpg'))
运行结果:
2020-12-01 16:03:15,606 __main__ DEBUG Function <make_element> is running.
<img src=a.jpg class=pic-frame/>
- 闭包
"""
闭包:指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量
关键是它能访问定义体之外定义的非全局变量
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用
但是仍能使用那些绑定
"""
# 基于保存历史的版本
def make_averager():
# 在averager中 series 一般称为自由变量,指未在本地作用域中绑定的变量
nums = []
def averager(new_value):
nums.append(new_value)
total = sum(nums)
return total / len(nums)
return averager
avg = make_averager()
print(avg(1))
运行结果:
1.0
# 不保存历史数据
def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
avg(1)
报错:
UnboundLocalError: local variable 'count' referenced before assignment
"""
当count是不可变类型时,count += 1 其实是 count = count + 1,这个赋值操作会将count变为局部变量
但是之前的series作为列表是可变对象,不存在这个问题
对于不可变类型,只能读取,不能更新,count = count + 1其实会隐式的创建局部变量
引入nonlocal,作用是将变量标记为自由变量,即使函数中为变量赋予了新的值
"""
# 不保存历史数据
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
avg = make_averager()
print(avg(3))
运行结果:
3.0
动态设置装饰器属性
from functools import wraps, partial
import logging
logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s')
logging.root.setLevel(logging.NOTSET)
def attach_wrapper(obj, func=None):
if not func:
return partial(attach_wrapper, obj)
setattr(obj, func.__name__, func)
return func
def logged(level, name=None, message=None):
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else f"Function <{func.__name__}> is running."
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
@attach_wrapper(wrapper)
def set_level(newlevel):
nonlocal level
level = newlevel
return wrapper
return decorate
@logged(level=logging.INFO)
def hello():
print("hello")
hello()
运行结果:
2020-12-01 16:50:29,771 __main__ INFO Function <hello> is running.
hello
hello.set_level(logging.DEBUG)
hello()
运行结果:
2020-12-01 16:51:44,218 __main__ DEBUG Function <hello> is running.
hello
装饰器可以叠加使用
@logged(level=logging.INFO)
@record_time
def hello():
print("hello")