用户自定义函数
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。Python提供了许多内建函数,比如print()。也可以自己创建函数,这被叫做用户自定义函数。
函数定义
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
语法:
def 函数名(函数参数列表):
函数体
return 函数返回值
与C、Java类似,函数通过函数名进行调用
函数参数
参数类型
python是弱类型语言,变量的类型是由其存储的值决定的,参数也是一种变量,所以函数在定义参数时,可以不定义其类型,根据实际调用时的赋值来决定其类型,这充分体现了python的灵活性。
但是,我们在做一个较大的项目时,代码量可能比较大,我们很容易忘记了某一个方法的参数类型是什么,一旦传入了错误类型的参数,再加上python是解释性语言,只有运行时候才能发现问题, 这对大型项目来说是一个巨大的灾难。
于是,自python3.5开始,PEP484为python引入了类型注解(type hints) 机制。 主要作用如下:
- 类型检查,防止运行时出现参数和返回值类型、变量类型不符合。
- 作为开发文档附加说明,方便使用者调用时传入和返回参数类型。
- 该模块加入后并不会影响程序的运行,不会报正式的错误,只有提醒。pycharm目前支持typing检查,参数类型错误会黄色提示
类型注解就是在变量后面加上冒号和类型,返回值类型是在括号后用->标识,如:
def foo(a:int, b:int) ->int:
return a+b
foo('a', 'b') #pychram会提示类型错误
类型错误并不会影响解释器的运行,但是可以让pycharm提示错误。
类型注释的检查是IDE来完成的,所以不同的IDE有不同的方式。
值传递与引用传递
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
-
不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。
-
可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python中,不可变类型参数都是值传递,可变类型参数都是引用传递。
def changeValue(a:int, b:list):
a += 10 #不可变类型,值传递,在函数内改变,不影响函数外的变量值
b.append('Last!') #可变类型,引用传递,在函数内改变,会函数外的变量值
a = 1
b = ['First', 'Second', 'Three']
changeValue(a, b)
print(f'{a=}, {b=}')
'''
a=1, b=['First', 'Second', 'Three', 'Last!']
'''
按位置传参
位置参数是最常见的参数类型。当定义一个函数时,可以指定一个或多个位置参数。在调用函数时,需要按照参数的顺序提供对应的值。
def foo2(name, age):
print(f'Hello {name}! You are {age} years old.')
foo2('Frica', 25)
'''
Hello Frica! You are 25 years old.
'''
在上面的例子中,name 和 age 是位置参数,按照顺序分别提供 “Frica” 和 25 作为参数值。
按名称传参
关键字参数允许你使用参数的名称来指定值,而不必按照顺序提供参数。使用关键字参数可以使代码更加清晰和易读。
def foo2(name, age):
print(f'Hello {name}! You are {age} years old.')
foo2('Frica', 25)
foo2(age=13, name='John')
'''
Hello Frica! You are 25 years old.
Hello John! You are 13 years old.
'''
在上面的例子中,通过使用 age=25 和 name="Frica" 来指定参数的值,而不必考虑它们的顺序。
缺省参数/默认参数
如果在函数调用时没有提供参数值,函数将使用默认值。默认参数通常在函数定义中指定,并且必须要位于位置参数之后(否则会有SyntaxError异常)。
def foo2(name, age, gender='male'):
if gender == 'male' :
print(f'Hello Mr. {name}! You are {age} years old.')
else:
print(f'Hello Ms. {name}! You are {age} years old.')
foo2('Frica', 25)
foo2(age=13, name='John')
foo2('Dawson', 23, 'female')
foo2(age=23, name='Dawson', gender='female')
注意:
- 默认参数必须在最右端(最后),这样才能被解释器正确识别,否则会产生二义性 .
- 默认参数一定要指向不变对象!
解释器会将默认参数作为一个公共对象来对待,多次调用含有默认参数的函数,就会进行多次修改。
因此定义默认参数时一定要使用不可变对象(int、float、str、tuple)。使用可变对象语法上没错,但在逻辑上是不安全的,代码量非常大时,容易产生很难查找的bug。
def defaultzero(list = []): #我们的本意是提供的list参数为0时 返回只有一个0的list
list.append(0)
return list
print(defaultzero()) #输出:[0]
print(defaultzero()) #输出:[0, 0] 显然重复调用的时候结果不是我们所期望的
#解决方案 使用None
def defaultzero1(list = None):
if list == None:
list = []
list.append(0)
return list
print(defaultzero1()) #输出:[0]
print(defaultzero1()) #输出:[0] 重复调用的时候,也输出相同
''' advanced: 简单询问框 '''
def ask_ok(hint, retries=4, complaint='Yes or no, please!'): # 仅有hint是必须要传入的,retries 和 complaint 均有默认值
while True:
u = input(hint)
if u in ('y','ye','yes'): # in 的用法;若用户回答在('y','ye','yes') return True
return True
if u in ('n','no','nop','nope'): # 若用户回答在('n','no','nop','nope') return False
return False
retries = retries -1 # 若用户输入不在之前所列,可重试,重试次数-1
if retries <= 0 : # 若超出重试次数,raise自定义Error
raise IOError('refusenik user')
print(complaint) # 若用户输入不在之前所列,提示 complaint 信息
result1 = ask_ok("Yes or No?") # 只给必要的参数值hint,可尝试输入'y' 'no' 等;输入其他的如 'x' 超过4次
print(result1) # 查看return的值
# result2 = ask_ok("Yes or No?",2) # 给出retries=2,尝试输入其他的如 'x' 超过2次
# result3 = ask_ok("Yes or No?",'Y or N?') # 不可只省略第二个参数,若尝试输入其他的如 'x',会报错
# result4 = ask_ok("Yes or No?",3,'Y or N?') # 给出所有的参数,可尝试输入'y' 'no' 等;输入其他的如 'x' 超过3次
# print(result4)
可变参数
可变参数就是传入的参数个数是可变的,可以是0个,1个,2个,……很多个。作用:就是可以一次给函数传很多的参数特征。
可变按位置传参
可变数量的位置参数允许接受任意数量的位置参数。在可能不确定函数会接受多少个参数的情况下,可以使用可变数量的参数。在函数定义中,可以使用星号(*)来指定一个可变数量的位置参数。一般使用*args,变量名可以任意。在函数内部,args是一个tuple来保存可变按位置传参。
def print_info(*args):
print(type(args))# 输出: <class 'tuple'>
total = sum(num for num in args)
return total
result = print_info(1, 2, 3, 4, 5)
'''
15
'''
*args可以放在命名参数的前面,也可以放在后面(但要放在默认参数前面)
def join_bysep(*strs,sep): # strs 可为多个参数
return sep.join(strs) # 字符串连接函数 sep.join(str)
print(join_bysep("red","blue","green",sep=" "))
print(join_bysep("red","blue",sep=","))
print(join_bysep("red",sep=","))
print(join_bysep(sep=",")) # 无strs传参,为一空的字符串
'''
red blue green
red,blue
red
'''
可变按名传参
可变数量的关键字参数允许接受任意数量的关键字参数。在函数定义中,可以使用双星号(**)来指定一个可变数量的关键字参数。一般我们会使用 **kwargs,当然,使用其它的变量也可以(这里的关键是 **,而不是kwargs。)kwargs在函数内部是一个dict字典类型,存储参数名:参数值键值对。
def print_info(**kwargs):
print(type(kwargs)) # 输出: <class 'dict'>
for key, value in kwargs.items():
print(key, ":", value)
print_info(name="Frica", age=25, city="GuangZhou")
几种传入参数法也可以组合使用
def print_info(name, *args, age=18, **kwargs):
print("Name:", name)
print("Age:", age)
print("Additional arguments:")
for arg in args:
print("-", arg)
print("Keyword arguments:")
for key, value in kwargs.items():
print("-", key, ":", value)
print_info("Frica", "arg1", "arg2", age=25, city="GuangZhou", country="China")
在上面的例子中,函数print_info接受以下参数:
- name是一个位置参数,必须提供值。
- *args是一个可变数量的位置参数,可以接受任意数量的额外参数。
- age是一个具有默认值的关键字参数。
- **kwargs是一个可变数量的关键字参数,可以接受任意数量的关键字参数。
强制按名传递参数
python可以强制部分参数必须是按名传参,按名传参和按位置位置传参中间使用*号分隔,按位置传参必须在*号前面,如:
def personinfo(name, age, *, gender, city): #只能传递gender和city参数
print(name, age, gender, city)
personinfo('Steve', 22, gender = 'male', city = 'shanghai') #输出:Steve 22 male shanghai
personinfo('Steve', age=23, gender = 'male', city = 'shanghai') #输出:Steve 23 male shanghai
personinfo('Steve', 24, 'male', city = 'shanghai') #提示TypeError错误,要求第3个参数必须是按名传参
前面的位置参数可以按位置,也可以按名传参,*号后面的参数必须按名传参。
如果中间加一个可变按位置参数*args,则就不需要另外加一个*作为分隔了,*args前面都是位置参数,后面都是按名参数
def personinfo(name, age, *args, gender, city): #只能传递gender和city参数
print(name, age, gender, city, f'{args=}')
personinfo('Steve', 22, 'London', 15, gender = 'male', city = 'shanghai') #输出:Steve 22 male shanghai
personinfo('Steve', age=23, 'London', 15, gender = 'male', city = 'shanghai') #参数有二义性,提示SyntaxError
但是,*args前面的按位置参数就不能再按名称传递了,因为这会引发解释器的二义性,解释器直接提示语法错误
返回值
函数如果需要返回值,需要使用return语句。
不带参数值的return语句返回None,即使没有return语句函数也会返回 None.
Python中的return语句可以从函数中带出不同类型的值,这与C、Java等语言不同。
def retTypeValue(n):
if n == 0:
return "OK"
elif n == 1:
return (1, 2, 3)
elif n == 2:
return [1, 2, 3]
elif n == 3:
return {"1": "one", "2":"two", "3":"three"}
else:
return None
注意,python中的return可以返回多个值,多个值通过逗号分隔开,对于函数调用者来看,函数是返回的一个tuple类型,return的时候,也可以用小括号括起来,两种用法是等效的。
return a,b
return (a,b)
Lambda函数
Lambda函数也被称为匿名(没有名称)函数,它直接接受参数的数量以及使用该参数执行的条件或操作,该参数以冒号分隔,并返回最终结果。为了在大型代码库上编写代码时执行一项小任务,或者在函数中执行一项小任务,便在正常过程中使用lambda函数。
定义为:
lambda argument_list:expersion
argument_list
是参数列表,它的结构与Python
中函数(function
)的参数列表是一样的expression
是一个关于参数的表达式,表达式中出现的参数需要在argument_list
中有定义,并且表达式只能是单行的。
普通函数和Lambda函数的区别
1、没有名称
Lambda函数没有名称,而普通操作有一个合适的名称。
2、Lambda函数不需要指定返回值
使用def
关键字构建的普通函数需要通过return语句指定返回值,但在Lambda函数不能通过return指定返回值,它的返回值只能是后面表达式的计算结果。实质上,Lambda返回的是一个带参数的函数对象(第一类对象)。
假设我们想要检查数字是偶数还是奇数,使用lambda函数语法类似于下面的代码片段。
b = lambda x: "Even" if x%2==0 else "Odd"
print(b(9))
’’’
Odd
‘’‘
3、函数只在一行中
Lambda函数只在一行中编写和创建,而在普通函数的中使用缩进
4、不用于代码重用
Lambda函数不能用于代码重用,或者不能在任何其他文件中导入这个函数。相反,普通函数用于代码重用,可以在外部文件中使用。
简单的性能比较
lambda函数会略微提高效率,但效果并不是很明显:
# 测试的Def函数
def square1(n):
return n ** 2
# 测试的Lambda函数
square2 = lambda n: n ** 2
t1 = time.time()
# 使用Def函数
i = 0
while i < 100000000:
square1(100)
i += 1
t2 = time.time()
print(f'def函数用时:{t2-t1}')
t1 = time.time()
# 使用lambda函数
i = 0
while i < 100000000:
square2(100)
i += 1
t2 = time.time()
print(f'limbda函数用时:{t2-t1}’)
‘’'
def函数用时:22.46737313270569
limbda函数用时:21.934571027755737
’‘’
lambda函数首先减少了代码的冗余,其次,用lambda函数,不用费神地去命名一个函数的名字,可以快速的实现某项功能,最后,lambda函数使代码的可读性更强,程序看起来更加简洁。
lambda函数在高阶函数中有较多使用,这个在后面的高阶函数的学习再举例说明