今天讲一些python中的高级用法,有助于大家更好的使用python这门语言。今天讲的这些知识是层层递进的关系,前面是后面的铺垫。
函数可变参数*args和**kwargs
python支持固定参数,默认参数,也和很多其他语言一样支持可变参数,只不过python支持的可变参数分为两种,*args是tuple,里面可以有任意多个element(包括0个)。**kwargs则是当你需要指定keyword时需要用到的参数类型。
先考虑*args的情况,先看函数定义:
>>>deftake_any_args(*args): ...print("typeofargs:"+str(type(args))) ...print("valueofargs:"+str(args)) ... >>>take_any_args(1) typeofargs:<type'tuple'> valueofargs:(1,) >>>take_any_args("a","b","c") typeofargs:<type'tuple'> valueofargs:('a','b','c') >>>take_any_args() typeofargs:<type'tuple'> valueofargs:() >>>take_any_args(['1','2'],['3','4']) typeofargs:<type'tuple'> valueofargs:(['1','2'],['3','4']) |
再看参数提取:
defprint_args(*args): forarginargs: print(arg) print_args("red","blue","green") defprint_all(*args,**kwargs): forarginargs: print(arg) forkey,valueinkwargs.items(): print("{}->{}".format(key,value)) print_all(1,2) print_all(a="red",b="blue",c="green") red blue green 1 2 a->red c->green b->blue |
下面看看为什么需要**kwargs,对于上面的print_args,下面这种添加了keyword的调用方式会出错,所以就有了**kwargs的用武之地:
>>>defprint_args(*args): ...forarginargs: ...printarg ... >>>print_args(a=1,b=2) Traceback(mostrecentcalllast): File"<stdin>",line1,in<module> TypeError:print_args()gotanunexpectedkeywordargument'a' |
**kwargs的本质其实是dict,如下所示:
>>>defprint_kwargs(**kwargs): ...forkey,valueinkwargs.items(): ...print("{}->{}".format(key,value)) ... >>>print_kwargs(a="lalala",b="papapa") a->lalala b->papapa |
通常再使用的时候都是二者合起来使用,如下所示:
defprint_all(*args,**kwargs): forarginargs: print(arg) forkey,valueinkwargs.items(): print("{}->{}".format(key,value)) print_all(1,2) print_all(a="red",b="blue",c="green") 1 2 a->red c->green b->blue |
上面的知识大家差不多应该都知道,下面这种Unpacking的用法很多人都不太了解:
>>>defsample_function(a,b,c): ...print("{},{},{}").format(a,b,c) ... >>>input=(1,2,3) >>>sample_function(1,2,3) 1,2,3 #和上面的方法等效 >>>sample_function(*input) 1,2,3 |
unpack使用kwargs,记住keyword要和函数声明时的变量名一致才行,否则会报错
>>>defsample_function(a,b,c): ...print("a->{},b->{},c->{}".format(a,b,c)) ... >>>input={"a":1,"b":2,"c":3} >>>sample_function(**input) a->1,b->2,c->3 #与上面方法等效 >>>sample_function(a=input['a'],b=input['b'],c=input['c']) a->1,b->2,c->3 |
lambdafunction
在python中所有的东西都是object,不管是int也好,list也好都是object。函数也是object。这个概念很重要。
>>>deff(n): ...returnn+1 ... >>>id(f) 4374076184 >>>g=f >>>printg(2) 3 >>>id(g) 4374076184 |
上面的g和f所指向的object是同一个object
下面思考这样一个问题,如果numbers=["10","3","40","14","5"],让你找出最大值怎么找?
>>>max(numbers)
'5'
这显然不对,因为max默认按照字母顺序排序了,所以需要额外提供排序信息:
>>>max(numbers,key=int) '40' int就是一个function,然后看看如果用lambdafunction表示就是: >>>max(numbers,key=lambdax:int(x)) '40' |
再举一个例子,下面是几个人的年龄,性别,地址,请找出年纪最大的人:
>>>person_zhangsan={'age':40,'gender':'male','home':'beijing'} >>>person_lisi={'age':35,'gender':'male','home':'hangzhou'} >>>person_wangwu={'age':21,'gender':'female','home':'chongqing'} >>>people=[person_zhangsan,person_lisi,person_wangwu] >>>max(people,key=lambdax:x['age']) {'gender':'male','age':40,'home':'beijing'} python在operator中提供了itemgetter这个函数,它起到的作用和lambdafunction一样,比如: >>>fromoperatorimportitemgetter >>>max(people,key=itemgetter("age")) {'gender':'male','age':40,'home':'beijing'} |
对比一下我还是更喜欢lambdafunction的定义,简洁明了
Decorator装饰器
最长见的decorator的usercase是什么?答:retry。比如网络restfulrequest碰到不稳定的server或者说server给你返回了5XX,你要不要retry。
一开始可能你的code长这样:
importrequests
URL="https://example.com/api"
defget_items():
returnrequests.get(URL+"/items")
当然你还会有很多getfunction,比如get_apple,get_banana,get_orange,...
实际部署之后发现server不稳定,不定期返回500,你就要加retry
如果只有一个get_items,你可能会这么写:
#第二版,加入retry defget_items(): NUM_RETRY=3 current_retry=0 resp=None whileTrue: resp=requests.get(URL+"/items") ifrest.status_code/100==5andcurrent_retry<NUM_RETRY: current_retry+=1 continue break returnresp |
可是每一个fucntion都要改,是不是很累。。。
下面decorator隆重登场,decorator的本质是一个function。这个function的parameter有且仅有一个就是一个functionobject,返回值则是另一个不同的function
#比如已经有了一个普通function defsome_function(arg1,arg2,arg3): #此处省略20行 some_function=some_decorator(some_function) 等效于 @some_decorator defsome_function(arg1,arg2,arg3): #...... |
下面举一个decorator的例子,loggingdecorator
deflogfuncname(func): defwrapper(*args,**kwargs): print("functionname:"+func.__name__) returnfunc(*args,**kwargs) returnwrapper >>>@logfuncname ...defsome_func(n): ...returnn+1 ... >>>printsome_func(3) functionname:some_func 4 |
如上所示,logfuncname就是一个decorator,它的input是func,return了一个wrapperfunction。
下面我们回到一开始retry那个例子:
#第三版,定义decorator defretry(func): defwrapper(*args,**kwargs): NUM_RETRY=3 current_retry=0 resp=None whileTrue: resp=func(*args,**kwargs) ifrest.status_code/100==5andcurrent_retry<NUM_RETRY: current_retry+=1 continue break returnresp returnwrapper @retry defget_items(): returnrequests.get(URL+"/items") |
然后get_apple,get_banana,get_orange什么的上面加上@retry就可以了
接下来问题来了,如果有另一个decorator也想用上怎么办?
decorator是可以叠加的,比如下面的例子,注意上下顺序就是decorator从左到右的顺序
@add2 @multi3 deffoo(n): returnn+1 #相当于foo=add2(multi3(foo)) #那么foo(3)就是14 @multi3 @add2 deffoo(n): returnn+1 #相当于foo=multi3(add2(foo)) #那么foo(3)就是18 |
如果想要改变retry的次数怎么办,比如get_apple想要retry3次,但是get_banana想要retry5次怎么办?
#第四版,定义带参数的decorator defretry(num_retry): defdecorator(func): defwrapper(*args,**kwargs): current_retry=0 resp=None whileTrue: resp=func(*args,**kwargs) ifrest.status_code/100==5andcurrent_retry<num_retry: current_retry+=1 continue break returnresp returnwrapper returndecorator @retry(3) defget_items(): returnrequests.get(URL+"/items") |
这里其实用到了一个closure的概念,就是外层函数的参数在里层函数里是可见的,而里层函数的参数在外层不可见(当然这里也不需要)。
Decorator在flask中的实现原理
下面我们来看看flask中decorator是怎么实现的,简而言之:
classWebApp: def__init__(self): #初始化routes self.routes={} defroute(self,param): defdecorator(func): #定义decorator时为routes赋值key/value self.routes[param]=func defwrapper(*args,**kwargs): returnfunc(*args,**kwargs) returnwrapper returndecorator defget(self,param): try: #get时根据key返回value returnself.routes[param]() exceptKeyError: return"ERROR-nosuchpage" >>>app=WebApp() >>>@app.route("/") ...defindex(): ...return'IndexPage' ... >>>@app.route("/contact/") ...defcontact(): ...return'ContactPage' ... >>>app.get("/") 'IndexPage' >>>app.get("/contact/") 'ContactPage' >>>app.get("/no-such-page/") 'ERROR-nosuchpage' |