本文最后更新于 1592 天前,其中的信息可能已经有所发展或是发生改变。
# 多行输出结果
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
1. 装饰器
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。
1.1 简单使用
# 由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
def now():
print('2018-8-1 17:50')
f = now
f
f()
# 函数对象有一个__name__属性,可以拿到函数的名字:
f.__name__
now.__name__
<function __main__.now>
2018-8-1
'now'
'now'
# 装饰器例子
def log(func):
def wrapper(*args, **kw):
print('calling %s():' % func.__name__)
return func(*args, **kw)
return wrapper
# 观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2018-8-1 17:55')
now()
now.__name__
calling now():
2018-8-1 17:55
'wrapper'
# 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2018-8-1 20:08')
now()
# 过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':
now.__name__
# 因为返回的那个wrapper()函数名字就是'wrapper',
# 所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
execute now():
2018-8-1 20:08
'wrapper'
1.2 解决装饰器装饰的函数其签名name改变的方法
使用functools的wrapper,即在定义wrapper()的前面加上@functools.wraps(func)。
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2018-8-1 20:08')
now()
now.__name__
execute now():
2018-8-1 20:08
'now'
1.3 计算函数执行时间的装饰器
def factorial(n):
# 递归调用实现n的阶乘
if n == 1:
return 1
return n * factorial(n - 1)
factorial(10)
3628800
# 使用装饰器装饰函数时要注意其直接作用递归函数会出现无限装饰问题,
# 参考知乎大神想法:在你的 timeit 装饰器里边存一下正在执行的函数。然后每次装饰器被调用的时候看看这个函数是不是已经被修饰过了,如果是的话就不重复计时了。
import time
import functools
import sys
if sys.platform == "win32":
# On Windows, the best timer is time.clock()
default_timer = time.clock
else:
# On most other platforms the best timer is time.time()
default_timer = time.time
# cost_time = []
# 经测试此timeit装饰器可以测量递归函数的总时间,而非每次递归调用的时间。
def timeit(func, cur_evaluating=set()):
@functools.wraps(func)
def wrapper(*args, **kargs):
if func not in cur_evaluating:
cur_evaluating.add(func)
start = default_timer()
r = func(*args, **kargs)
end = default_timer()
# cost_time.append((end - start))
# print("time of " + func.__name__ + ": %s" % cost_time[-1])
print("time of " + func.__name__ + ": %s" % (end - start))
cur_evaluating.remove(func)
else:
r = func(*args, **kargs)
return r
return wrapper
@timeit
def factorial(n):
# 递归调用实现n的阶乘
if n == 1:
return 1
return n * factorial(n - 1)
print(factorial(10))
# for _ in range(20):
# factorial(10)
# cost_time_10_20 = sum(cost_time)
# print('\nloop 20 times: ', cost_time_10_20)
# for _ in range(20):
# factorial(50)
# cost_time_50_20 = sum(cost_time)
# print('\nloop 20 times: ', cost_time_50_20)
time of factorial: 8.39757086266846e-06
3628800
1.4 github的定时模块func_timeout
在自定义一个函数后,会调用这个函数来完成我们想要的功能。就拿爬虫来举例,你发送请求,服务器给你响应,
但是有可能服务器没有给你任何数据,无论是他识别了爬虫、还是服务器繁忙什么原因,
这个时候,你的爬虫就会一直等待响应,这个时候就会非常浪费资源,还会造成程序阻塞。
# func_timeout将在指定的参数的线程中运行指定的函数,直到返回,引发异常或超时。如果存在返回或异常,则将正常返回。
import func_timeout
dir(func_timeout) # 查看其所有方法
['FunctionTimedOut',
'StoppableThread',
'__all__',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'__version__',
'__version_tuple__',
'dafunc',
'exceptions',
'func_set_timeout',
'func_timeout',
'py3_raise']
1.4.1 func_timeout使用方法一: 装饰器
# func_timeout将在指定的参数的线程中运行指定的函数,直到返回,引发异常或超时。如果存在返回或异常,则将正常返回。
import time
from func_timeout import func_set_timeout
@func_set_timeout(1)
def task():
print('hello world ')
time.sleep(2)
if __name__ == '__main__':
task()
hello world
-----------------------------------------------------------
FunctionTimedOut Traceback (most recent call last)
<ipython-input-13-df0e5fc9fa5d> in <module>()
9
10 if __name__ == '__main__':
---> 11 task()
D:\Anaconda3\lib\site-packages\func_timeout\dafunc.py in <lambda>(*args, **kwargs)
183 def _function_decorator(func):
184
--> 185 return wraps(func)(lambda *args, **kwargs : func_timeout(defaultTimeout, func, args=args, kwargs=kwargs))
186
187 # def _function_wrapper(*args, **kwargs):
D:\Anaconda3\lib\site-packages\func_timeout\dafunc.py in func_timeout(timeout, func, args, kwargs)
99 thread._stopThread(stopException)
100 thread.join(min(.1, timeout / 50.0))
--> 101 raise FunctionTimedOut('', timeout, func, args, kwargs)
102 else:
103 # We can still cleanup the thread here..
FunctionTimedOut: Function task (args=()) (kwargs={}) timed out after 1.000000 seconds.
# try捕获错误
import time
from func_timeout import func_set_timeout, FunctionTimedOut
@func_set_timeout(1)
def task(arg1, arg2):
print('hello world ')
time.sleep(2)
return 'task_completely, %s, %s' % (arg1, arg2)
if __name__ == '__main__':
try:
taskReturnValue = task('arg1', 'arg2')
print(taskReturnValue)
except FunctionTimedOut:
print('function time out')
except Exception as e:
print('error code: ', e)
hello world
task_completely, arg1, arg2
1.4.2 func_timeout使用方法二: 函数调用
import time
from func_timeout import func_timeout, FunctionTimedOut
def task(arg1, arg2):
print('hello world ')
time.sleep(2)
return 'task_completely, %s, %s' % (arg1, arg2)
if __name__ == '__main__':
try:
taskReturnValue = func_timeout(3, task, args=('arg1', 'arg2'))
print(taskReturnValue)
except FunctionTimedOut:
print('function time out')
hello world
task_completely, arg1, arg2
2. 偏函数(partial function)
functools.partial可以通过设定参数的默认值,可以降低函数调用的难度。
也就是把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数(新函数也可以改变函数参数),调用这个新函数会更简单。
见下例:
# int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
int('12345')
# int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
int('12345', base=16)
int('12345', 16)
12345
74565
74565
# 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
int2('1000')
# functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int2 = functools.partial(int, base=2)
int2('1000')
# 新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值,但是需要加上参数名
int('12345', base=16)
8
8
74565