函数类似于Java中的方法,是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
1、函数定义
函数定义的规则:
def 函数名(参数列表): [文档字符串,可选] 函数体
可以用return [表达式]结束函数,也可以不用,依情况而定。如果不使用return返回值,实际上函数也会返回一个值None
例子:
# 生成指定边界的斐波那契数列def fibonacci(n): # 文档字符串类似于Java中的文档注释,可以借助一些工具生成API文档 """文档字符串:Print a Fibonacci series up to n.""" a, b = 0, 1 result = [] while a < n: result.append(a) a, b = b, a+b if len(result) != 0: return result else: pass# 在函数定义结束之后,最好空一行print(fibonacci(2000))# 当没有调用pass语句时,函数返回Noneprint(fibonacci(0))
2、全局变量、局部变量和参数调用
python中的函数和其他语言的函数/方法一样:
- 函数内局部变量在每次函数调用时生成,函数结束时销毁
- 由于python中没有Java中类似的“无引用的基本类型”,所以一直传递进函数的都是“引用”:形参在初始时,指向和传入的实参相同的地址。“更改形参指向的地址”并不会影响到实参,但是“更改形参指向的地址的内容”则会影响实参。
def method(num,str,tuple,list): # "="实际上意味着更改了类型的指向 num += 1 str = 'new str' tuple = ('a', 'b') list[0] = 1n = 0s = 'str't = ('a', 'b', 'c', ['this', 'is', 'a', 'list'])l = [123, 345, 567]method(n, s, t, l)# 只有l被改变print(n)print(s)print(t)print(l)
和Java中不同:
- python中的函数无法对全局参数进行赋值(这是由于和Java中不同,python的赋值语句和变量申明语句并没有任何差别,不像Java声明变量时需要指定类型),对外部的参数进行赋值必须通过global或者nonlocal实现
# 全局变量n = 0def method(): # python解释器会认为是在方法中重新声明了一个“同名的局部变量” n = 1
- 同样由于赋值语句和变量声明语句没有任何差别,python中有一种“类型可以改变”的假象:
# 看起来是类型改变了,实际上丢弃了原有的对象,新声明了一个对象num = 123num = '123'# 如果Number和String进行运算,则会报错num = 123+'123'
2.1、全局变量、局部变量详解
需要注意的有:
- 方法内部只能访问全局变量,无法修改全局变量。同样,嵌套的方法也只能访问其外层方法的变量,而无法修改。
- “无法修改”仅限于无法修改值,实际上对于“引用”,仍然可以修改。例如:
如上面所说,修改无效:
In [16]: num = 1In [17]: def fun1(): ...: num = 2 ...: print(num) ...: In [18]: fun1()2In [19]: numOut[19]: 1
但是对引用对象,不改变引用的情况下,可以改变值:
In [20]: list = [1,2,3,4,5]In [21]: def fun1(): ...: list[0] = 'a new number' ...: print(list) ...: In [22]: fun1()['a new number', 2, 3, 4, 5]In [23]: listOut[23]: ['a new number', 2, 3, 4, 5]
- 如果在方法内对全局变量(或上级局部变量)进行修改,或造成“shadowing”,导致无法对全局比变量进行访问,同时抛出错误“UnboundLocalError”。即如果下文中要对某个全局变量进行修改,就不能在这之前访问他。比如:
In [1]: num = 1In [2]: def fun1(): ...: print(num) ...: In [3]: fun1()1In [4]: def fun1(): ...: print(num) ...: num = 2 ...: In [5]: fun1()---------------------------------------------------------------------------UnboundLocalError Traceback (most recent call last)in ()----> 1 fun1() in fun1() 1 def fun1():----> 2 print(num) 3 num = 2 4 UnboundLocalError: local variable 'num' referenced before assignment
- 局部变量不能在函数外部被访问、修改。这个原则同样适用于函数内部的“函数”,因为和C语言类似,python中的函数其实也是一种特殊的对象(这点和Java不同)。例如:
In [26]: def fun1(): ...: def fun2(): ...: print("定义在fun1()内部的fun2()只能在fun1()内部调用") ...: fun2() ...: In [27]: fun1()定义在fun1()内部的fun2()只能在fun1()内部调用In [28]: fun2()---------------------------------------------------------------------------NameError Traceback (most recent call last)in ()----> 1 fun2()NameError: name 'fun2' is not defined
- 想要访问内部的函数,可以通过“闭包”来实现,见下方闭包的说明。
- 可以使用Global和nonlocal强行在内部作用域中对外部变量进行修改,具体见:https://my.oschina.net/pierrecai/blog/906419
3、深入函数定义
注意,其它高级语言常见的函数重载,Python 是没有的,这是因为 Python 有默认参数这个功能,函数重载 的功能大都可以使用默认参数达到。
3.1、函数默认参数
- 在定义参数时,可以给参数设定默认值
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): """可以设定默认参数""" while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no'): return False retries -= 1 if retries < 0: raise OSError('uncooperative user') print(complaint)# 函数调用时,没有默认值的参数必须给出,但是有默认值的参数可以不给出ask_ok('Do you really want to quit?')ask_ok('Do you really want to quit?',2)ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
需要注意的是:
- 函数的默认参数只被赋值一次,这在参数是“不可变类”和普通类的情况下会导致不同的结果:
i = 5def method(num=i): print(num)i = 6# 由于Number为不可变类,每次print出的num一样method()method()
def method(list1=[]): list1.append('a') print(list1)# 由于默认参数只赋值一次,每次方法调用都操作的是同一个List,效果会叠加method()method()method()# 最后一个输出['a', 'a', 'a', 'a']method()
- 如果不想让普通类默认值在后续调用中累积,可以使用下面的方法:
def method(list1=None): if list1 is None: list1 = [] list1.append('a') print(list1)
3.2、关键字参数
python中在调用函数时,可以用“keyword = value”的形式进行:
def method(var1, var2='var2', var3='var3', var4=['var', '4']): print(var1) print(var2) print(var3) print(var4)method(111)method(var1='asd')method(var1=123, var2='zxc')method(111, '123', 123, '123')method(111, var2='123')method(var1='asd',var2=123)
需要注意的是:
- 没有默认值的参数必须提供,有默认值的参数可以不提供
- 如果不使用keyword,参数会按顺序读取
- 如果使用keyword,keyword必须和形参名相匹配,此时可以不按顺序
- 如果混合使用,非keyword参数必须在keyword之前
- 任何参数都不可重复赋值
3.2.1、强制关键字参数
我们也能将函数的参数标记为只允许使用关键字参数。用户调用函数时将只能对每一个参数使用相应的关键字参数。
设定方法是将方法的第一个参数设置为*,后续全部使用关键字,比如:
def method(*,name="User",password="asdf"): print(name,password)# 这样可以正常调用method(name='alibaba')# 这样则会报错method('alibaba','asadf')
3.3、可变参数列表
在python中也像Java一样可以设置可变的参数列表。
- 在Java中使用"参数类型... 参数名"表示可变的参数列表,并以“数组”的形式接受传入的数量不定的参数:
/** * @param strings 以数组的形式接受可变数量的参数 */public void method(String... strings){ for (int i = 0; i < strings.length; i++) { System.out.println(strings); }}
- 在python中,同样可以用“数组”(python中称为元组)来接收数量不定的参数,形式为“*args”
- 在python中,还可以使用“字典”(dictionary)来接收,形式为“**args”,但是不可以用在"*args"之前
def method(arg, *args, **keywords): print(arg) for arg in args: print(arg) for keyword in keywords: print(keyword, ":", keywords[keyword])method("arg", "arg1", "arg2", "arg3", keyword1='arg4', keyword2='arg5')
注意:
- 如果要定义可变列表,**args不能用在*args之前,普通参数不能在*args或者**args之后。
- 可以在*args或者**args之后使用关键字参数
3.4、参数列表的分拆
和3.3中的相反,如果要传递的参数已经是元组或者字典,但是调用的函数却要求把这些拆开来传入,这是同样可以用“*”和“**”把元组和字典拆开:
# 正常调用l = list(range(3, 6))print(l)# 拆分元组list1 = list(range(*[3, 6]))print(list1)def method(var1, var2, var3, var4): print(var1) print(var2) print(var3) print(var4)# 正常调用method(1, 2, 3, 4)# 拆分字典method(**{"var1":1,"var2":2,"var3":3,"var4":4})
3.5、闭包
所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
一般展示为下面的形式:
- 函数b嵌套在函数a内部
- 函数a返回函数b
- 函数b可以对函数a的参数进行修改
In [30]: def fun1(x): ...: def fun2(y): ...: return x*y ...: return fun2 ...: In [31]: fun = fun1(5)In [32]: funOut[32]:.fun2>In [33]: fun(4)Out[33]: 20In [34]: fun1(5)(4)Out[34]: 20In [35]: fun2(4)---------------------------------------------------------------------------NameError Traceback (most recent call last) in ()----> 1 fun2(4)NameError: name 'fun2' is not defined
- 这样实现了前两点,但是如果要实现内部函数对外部函数的参数进行修改的话,还是需要引入nonlocal,如:
In [40]: def fun1(x): ...: def fun2(y): ...: x += 5 ...: print("+=实际上相当于先访问,后赋值") ...: return x*y ...: return fun2 ...: In [41]: fun1(5)(4)---------------------------------------------------------------------------UnboundLocalError Traceback (most recent call last)in ()----> 1 fun1(5)(4) in fun2(y) 1 def fun1(x): 2 def fun2(y):----> 3 x += 5 4 print("+=实际上相当于先访问,后赋值") 5 return x*yUnboundLocalError: local variable 'x' referenced before assignmentIn [42]: def fun1(x): ...: def fun2(y): ...: nonlocal x ...: x += 5 ...: print("+=实际上相当于先访问,后赋值") ...: return x*y ...: return fun2 ...: In [43]: fun1(5)(4)+=实际上相当于先访问,后赋值Out[43]: 40
3.6、Lambda形式
和Java8中新增的Lambda特性相同,lambda表达式实际上是一个函数指针,常用于:
- 替代只需要调用一两次的函数
- 代表短小的匿名函数
Java中Lambda表达式的语法:
变量列表 -> 函数体
对应的python中的Lambda表达式的语法:
lambda 变量列表: 函数体
例如:
def add(n): return lambda x: x+nf1 = add(42)def f2(x): return x+42# f1和f2相同print(f1(0))print(f2(0))
In [12]: fun = lambda x,y: x+yIn [13]: fun(1,2)Out[13]: 3
lambda表达式还可以作为参数传递:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]# 意为根据元组对的第一个元素排序,即1234pairs.sort(key=lambda pair: pair[0])print(pairs)pairs.sort(key=lambda pair: pair[1])
注意:
- Java中Lambda表达式的参数列表需要用括号括起来,而python中不用
3.7、函数注解
函数注解以字典的形式存储在函数的__annotations__属性中,对函数的其他部分没有任何影响。
函数注解有三种:
- 参数注解:定义在参数的":"后面
- 返回注解:定义在参数列表的"->"后面
def method(var1: "参数1注解", var2: int='参数2初始值',var3:'参数3注解'='参数3初始值') -> "方法注解": print(method.__annotations__) print(var1) print(var2) print(var3)method(1)
注意:
- 如果参数有初始值,注解形式为(:后面跟注解,=后面跟初始值)
参数名:注解=初始值
- 注解不只可以用字符串,还可以用int等,表示类型注解
和注解不同的是,函数字符串保存在__doc__属性中:
def method1(): """这是一个文档字符串""" pass# 直接通过调用__doc__属性print(method1.__doc__)# 通过help方法help(method1)