上一章,我们介绍了快捷定义函数的lambda算子或匿名函数,以及数据类型注释的方法。这一章节,我们继续拓展,讲述更多关于让函数更优雅的内容。
一、函数嵌套
前面我们讲过,判断和循环可以嵌套,那么函数可以吗?当然是可以的。不过,函数内部的子函数是局部的,只在函数内部发挥作用。例如:
def father():print("father", end=" ")def child():print("child")child()father()# Output: father childchild()# Output: NameError: name 'child' is not defined
如果我们在函数外部调用子函数child,解释器会抛出child未定义的错误。
函数嵌套同样奉行局部优先的原则,即子函数优先于外部函数发挥作用,比如:
def power(num: int, exp: int = 2) -> float:print("power operation outside")return num ** expdef square_sum(x: int, y: int) -> float:def power(n: int, e: int = 2) -> float:print("power operation inside")return n ** ereturn power(x) + power(y)print(square_sum(3, 4))# Output:# power operation inside# power operation inside# 25
会发现,调用power函数时,其优先使用的是函数内部的同名子函数。
二、函数重载
学习过Java、C++等语言的读者可能想问,Python能不能重载?
重载(Overloading)是指对于同一个函数命名,根据传入参数的类型、数量、顺序等的不同,函数会执行不同的操作。但由于Python对参数的传递很灵活,甚至是比较随意,因此其不支持传统意义上的重载。如果你强行重载,那么就会产生程序错误。例如:
def function(num):print(f"content: {num}")def function():print("no content")function()# Output: no contentfunction(123)# Output: TypeError: function() takes 0 positional arguments but 1 was given
后一个function会直接覆盖前一个同名函数,使得带参数的函数无法被正确调用。
当然,你可以使用if-else等来间接实现重载:
def function(num):if isinstance(num, int):print("gotten an integer")elif isinstance(num, float):print("gotten a float")else:print(f"gotten a(n) {type(num)}")function(100)function(100.0)function("100")# Output:# gotten an integer# gotten a float# gotten a(n) <class 'str'>
也可以使用一些函数装饰器来辅助实现。
三、装饰器
简单地说,装饰器(Decorator)是修改其他函数的功能的函数。它们有助于让代码更简短。装饰器是很难掌握的概念,就像C语言的指针一样。因此,我们简要介绍一下,不过多展开。
前序内容,我们提到过函数可以赋值给其他变量,变成异名同效的函数,调用方法也是“变量名+括号+参数(若有)”。既然能用于赋值,那么其也能作为函数的返回值:
def function():def laugh():print("hahaha")return laughcall1 = function()call1()# Output: hahahacall2 = functioncall2()()# Output: hahaha
也能用于传参:
def power(num, exp=2):return num ** expdef calc(num, func):return func(num)print(calc(2, power))# Output: 4
根据先前我们提到的装饰器的定义,我们将上面函数作为参数及返回值的内容合并起来,就可以得到一个装饰器。下面,我们给出一个示例,用于说明在用户调用某项功能时,需要先登录账号:
def login_decorator(func):def wrapper(*args, **kwargs):print("User has logged in")return func(*args, **kwargs)return wrapperdef user_call(*args, **kwargs):print("User has called the function")
定义完成后,我们使用login_decorator来修饰user_call函数,使得其先执行登录逻辑:
call = login_decorator(user_call)call()# Output:# User has logged in# User has called the function
这样就会发现,在真正调用函数前,会先执行装饰器中定义在正式调用该函数的代码。或许你会问,这样写好像有些麻烦。其实,我们可以使用“@”来在定义函数时就对其添加装饰器,从而产生同样的效果:
@login_decoratordef user_call(*args, **kwargs):print("User has called the function")user_call()# Output:# User has logged in# User has called the function
上面函数定义时,我们使用了“*args”和“**kwargs”:前者我们已经提到过,是不定长参数;后者则是关键词参数,即以“key=value”的形式传参,然后函数对kwargs使用字典访问的方式获取参数内容:
def function(*args, **kwargs):print(f"*args: {args}, type: {type(args)}")print(f"**kwargs: {kwargs}, type: {type(kwargs)}")function(1, 2, 3, a=4, b=5, c=6)# Output:# *args: (1, 2, 3), type: <class 'tuple'># **kwargs: {'a': 4, 'b': 5, 'c': 6}, type: <class 'dict'>
往期回顾:
GPU加速版Pandas!cuDF库详解,大数据处理速度直接起飞