这里介绍Python的函数如何定义、调用

函数定义/调用
print("------------------- 函数定义-----------------------")
def greet():
"""
欢迎
"""
print("Hello, World")
def greet_user(username):
"""
欢迎用户
"""
print("Hello,", username)
def two_sum(a, b):
"""
计算两数之和
"""
sum = a+b
return sum
print("---------------------函数调用-------------------------")
greet()
greet_user( "Aaron" )
print(f"two_sum(13, 20) :", { two_sum(13, 20)})
# Python中函数也是对象
print("greeth函数的类型 : ", type(greet))
# 查看函数的文档字符串
print("doc:", greet.__doc__)
# 函数可以赋值给变量,可通过callable函数判断一个变量是否为可调用对象,然后通过()进行调用
f = greet
print("f变量是否为可调用对象: ", callable(f))
f()

函数实参
位置实参
所谓位置实参,即是按照函数定义中的顺序传递的参数
print("----------------位置实参--------------------")
def get_animal_info(name, age, msg):
print(f"I'm {name}, My age is {age}. MSG --->>> {msg}")
# 位置实参:根据其在函数定义中的顺序传递的参数
get_animal_info("Aaron", 18, "Hello")

关键字实参
所谓关键字实参,即是通过指定参数名称来传递的参数。无需按照函数定义中的位置顺序依次传入
print("----------------关键字实参--------------------")
def get_animal_info(name, age, msg):
print(f"I'm {name}, My age is {age}. MSG --->>> {msg}")
# 关键字实参:指定参数名称来传递的参数,无需按照参数在函数定义中的位置顺序依次传入
get_animal_info(msg="你好", name="Bob", age=22)
get_animal_info(age=7, name="Amy",msg="再见")

混用位置实参、关键字实参
当位置实参、关键字实参混用时,必须保证 位置实参在前、关键字实参在后 的顺序
print("----------------混用位置实参、关键字实参--------------------")
def get_animal_info(name, age, msg):
print(f"I'm {name}, My age is {age}. MSG --->>> {msg}")
get_animal_info("Tony", msg="Bye", age=37)

函数形参
普通形参
对于普通形参,调用时通过位置实参、关键字实参传值均可
print("------------------- 普通形参 -----------------------------")
def get_cat_info(name, type):
print("name:",name,"type:",type)
# 对于普通形参,调用时通过位置实参、关键字实参传值均可
get_cat_info("Tom","英短")
get_cat_info("David",type="中华田园猫")
get_cat_info(type="美短",name="Tony")

*形式的可变形参
对于*形式的可变形参,其会收集未匹配的位置实参 并以元组tuple形式进行存储。形参名称习惯上使用args
print("------------------- *形式的可变形参 -----------------------------")
def get_names(first_name, last_name, *args):
print("first name:",first_name,"last name:",last_name)
print("args的类型:",type(args),"args:", args)
get_names("Aaron", "Wang")
get_names("Aaron", "Zhu", "Tom", "Tony")

**形式的可变形参
对于**形式的可变形参,其会收集未匹配的关键字实参 并以字典dict形式进行存储。形参名称习惯上使用kw
print("------------------- **形式的可变形参 -----------------------------")
def get_info(name, **kw):
print("name:",name)
print("kw的类型:",type(kw),"kw:",kw)
get_info("Aaron")
get_info("Aaron", city="NanJing", age=18)
get_info(name="Bob", city="HangZhou", age=23)

限定位置形参
Python 3.8版本引入限定位置形参。具体地,在形参列表中使用 /斜杠 进行分隔。其中 /斜杠 前面的形参都是限定位置形参,调用时只能使用位置实参传参
print("------------------- 限定位置形参 -----------------------------")
def sum(a,b, /, name):
print("a:",a,"b:",b,"name:",name)
sum(1, 2, "Aaron")
sum(1, 2, name="Aaron")

限定关键字形参
限定关键字形参,又称为命名关键字形参。具体地,在形参列表中使用 *星号 进行分隔,其中 *星号 后面的形参都是限定关键字形参,调用时只能使用关键字实参传参
print("------------------- 限定关键字形参 -----------------------------")
def get_stu_info(a,b, *, name, sex):
print("a:",a,"b:",b,"name:",name,"sex:",sex)
get_stu_info(11,22,name="Aaron",sex="Man")
get_stu_info(a=3,b=4,name="Amy",sex="Woman")

组合拳
定义函数时,可以组合使用上述多种形参。形参组合顺序为:限定位置形参、普通形参、*形式的可变形参、限定关键字形参、**形式的可变形参。特别地,如果形参列表中存在 *形式的可变形参 时,则 限定关键字形参 前面无需添加 *星号分隔符;否则需要添加。故,下述函数定义示例中的 my_city、my_sex 均为 限定关键字形参
def mix_func1(a,b,c, /, first_name, last_name, *, my_city, my_sex, **kw):
pass
def mix_func2(a,b,c, /, *, my_city, my_sex, **kw):
pass
def mix_func3(a,b,c, /, first_name, last_name, *args, my_city, my_sex, **kw):
pass
调用函数时,位置实参、关键字实参 与 各种形参的传递规则为:
-
对于位置实参而言,其会按顺序依次传递给 限定位置形参、普通形参,剩余的位置实参全部传递给可变形参* -
对于关键字实参而言,其会根据参数名匹配传递给 限定关键字形参、剩余的普通形参,剩余的关键字实参全部传递给可变形参**
print("------------------- 组合拳 -----------------------------")
# 形参组合顺序:限定位置形参、普通形参、可变形参*、限定关键字形参、可变形参**
def mix_param(a,b,c, /, first_name, last_name, *args, my_city, my_sex, **kw):
print("限定位置形参: ", "a -->",a, "b -->",b,"c --->",c)
print("普通形参: ", "first_name --->",first_name, "last_name --->", last_name)
print("可变形参*: ", "args --->", args )
print("限定关键字形参: ", "my_city --->",my_city, "my_sex --->", my_sex)
print("可变形参**: ", "kw --->", kw)
print("-----------------------------------------------")
mix_param(1,2,3, "Aaron", "Zhu", "Cat","Dog", my_city="BeiJing", my_sex="male", my_age=13, my_phone="119")
print("n-----------------------------------------------")
mix_param(111,222,333, last_name="Wang", first_name="Amy", my_age=35, my_phone="120", my_city="ShangHai", my_sex="female")

具有默认值的形参
规则
Python中形参支持设置默认值。可以从 限定位置形参 或 普通形参中的任意一个形参 开始设置默认值,其后面所有的 限定位置形参、普通形参 都必须设置默认值
# 可以从 限定位置形参 或 普通形参中的任意一个形参 开始设置默认值,其后面所有的 限定位置形参、普通形参 都必须设置默认值
def default_fun1(a,b=2,c=3, /, first_name="Luca", last_name="Li", *, my_city, my_sex, **kw):
pass
def default_fun2(a,b,c, /, first_name, last_name="Li", *, my_city, my_sex, **kw):
pass
限定关键字形参则可以任意设置默认值,不需要保证其后面所有的限定关键字形参都有默认值
# 限定关键字形参则可以任意设置默认值,不需要保证其后面所有的限定关键字形参都有默认值
def default_fun3(a,b,c, /, first_name, last_name, *, my_city="GuangZhou", my_sex, **kw):
pass
陷阱:使用可变对象作为形参默认值
当使用可变对象作为形参默认值,可能会发生意外情况
print("------------------- 将可变对象作为函数参数的默认值 -----------------------------")
def foo(name, names=[]):
names.append(name)
return names
print("显式传入一个list,不使用函数的默认值")
names1 = foo("Aaron", [])
names2 = foo("Bob", [])
print(f"names 1 : {names1}")
print(f"names 2 : {names2}")
print(f"names1 is names2 : {names1 is names2}")
print("n使用函数的默认值")
names3 = foo("Tony")
names4 = foo("Amy")
print(f"names 3 : {names3}")
print(f"names 4 : {names4}")
print(f"names3 is names4 : {names3 is names4}")
当显式传入一个list,不使用函数的默认值时。测试结果符合预期;但使用函数的默认值时,我们会发现names3、names4竟然指向的是同一个列表对象

这是因为Python中,默认值是在函数定义时计算的,其只会被计算一次(通常发生在模块加载时)。具体地,默认值会存储在函数对象的__defaults__属性当中,故其会被重复使用的。所以,如果函数参数的默认值是一个可变对象时,那么每次使用该默认值时,其实是同一个对象。同理,当类的方法形参的默认值使用可变对象,也会存在上述问题
def foo(name, names=[]):
names.append(name)
return names
names3 = foo("Tony")
names4 = foo("Amy")
print(f"foo.__defaults__ : { foo.__defaults__ }")
print(f"foo.__defaults__[0] is name3 : { foo.__defaults__[0] is names3 }")
print(f"foo.__defaults__[0] is name4 : { foo.__defaults__[0] is names4 }")

针对上述问题,可以解决的办法是使用不可变对象作为函数形参的默认值
print("------------------- 将不可变对象作为函数参数的默认值 -----------------------------")
def bar(name, names=None):
if( names == None ):
names = []
names.append(name)
return names
names5 = bar("Red")
names6 = bar("Blue")
print(f"names5 is names6 : {names5 is names6}")
print(f"names 5 : {names5}")
print(f"names 6 : {names6}")

函数注解
函数注解会作为元数据存储在函数的__annotations__属性里,仅此而已。目前Python解释器不会利用它进行任何检查、验证、限制。后续可供IDE、框架、装饰器等工具使用。具体地:
-
函数声明中可在形参后添加 :符号 和 注解表达式 -
对于有默认值的形参, 可在 形参与=号 之间添加 :符号 和 注解表达式 -
对于返回值, 可在 函数声明末尾与:号 之间添加 ->符号 和 注解表达式 -
其中,注解表达式可以是任何类型
import inspect
print("-------------------- 函数注解 -------------------")
def sub_str(word:str, count:int=5) -> str:
"""
截取字符串str前count个字符
"""
if( count<996 ):
return word[:count]
else:
return 1314
print(f"res1 : {sub_str('Hello World', 5)}")
print(f"res2 : {sub_str(5432, 6789)}")
# 通过 __annotations__ 属性查看函数注解
print(f"sub_str annotation : {sub_str.__annotations__}")
# 通过 函数签名 查看函数注解
sig = inspect.signature(sub_str)
for param in sig.parameters.values():
print(f"函数参数{param.name} 的注解: {param.annotation}")
print(f"函数返回值的注解: {sig.return_annotation}")

参考文献
-
Python编程·第3版:从入门到实践 Eric Matthes著 -
Python基础教程·第3版 Magnus Lie Hetland著 -
流畅的Python·第1版 Luciano Ramalho著
原文始发于微信公众号(青灯抽丝):Python之函数
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/288318.html