函数类似于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语句时,函数返回None
print(fibonacci(0))
2、全局变量、局部变量和参数调用
python中的函数和其他语言的函数/方法一样:
- 函数内局部变量在每次函数调用时生成,函数结束时销毁
- 由于python中没有Java中类似的“无引用的基本类型”,所以一直传递进函数的都是“引用”:形参在初始时,指向和传入的实参相同的地址。“更改形参指向的地址”并不会影响到实参,但是“更改形参指向的地址的内容”则会影响实参。
def method(num,str,tuple,list):
# "="实际上意味着更改了类型的指向
num += 1
str = 'new str'
tuple = ('a', 'b')
list[0] = 1
n = 0
s = '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 = 0
def method():
# python解释器会认为是在方法中重新声明了一个“同名的局部变量”
n = 1
- 同样由于赋值语句和变量声明语句没有任何差别,python中有一种“类型可以改变”的假象:
# 看起来是类型改变了,实际上丢弃了原有的对象,新声明了一个对象
num = 123
num = '123'
# 如果Number和String进行运算,则会报错
num = 123+'123'
2.1、全局变量、局部变量详解
需要注意的有:
- 方法内部只能访问全局变量,无法修改全局变量。同样,嵌套的方法也只能访问其外层方法的变量,而无法修改。
- “无法修改”仅限于无法修改值,实际上对于“引用”,仍然可以修改。例如:
如上面所说,修改无效:
In [16]: num = 1
In [17]: def fun1():
...: num = 2
...: print(num)
...:
In [18]: fun1()
2
In [19]: num
Out[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]: list
Out[23]: ['a new number', 2, 3, 4, 5]
- 如果在方法内对全局变量(或上级局部变量)进行修改,或造成“shadowing”,导致无法对全局比变量进行访问,同时抛出错误“UnboundLocalError”。即如果下文中要对某个全局变量进行修改,就不能在这之前访问他。比如:
In [1]: num = 1
In [2]: def fun1():
...: print(num)
...:
In [3]: fun1()
1
In [4]: def fun1():
...: print(num)
...: num = 2
...:
In [5]: fun1()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-5-ed0619e33a22> in <module>()
----> 1 fun1()
<ipython-input-4-c7d39297e45b> 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)
<ipython-input-28-32d138681f85> in <module>()
----> 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 = 5
def 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]: fun
Out[32]: <function __main__.fun1.<locals>.fun2>
In [33]: fun(4)
Out[33]: 20
In [34]: fun1(5)(4)
Out[34]: 20
In [35]: fun2(4)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-35-95b8bef3aee7> in <module>()
----> 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)
<ipython-input-41-a8dcf2873369> in <module>()
----> 1 fun1(5)(4)
<ipython-input-40-23b40a634958> in fun2(y)
1 def fun1(x):
2 def fun2(y):
----> 3 x += 5
4 print("+=实际上相当于先访问,后赋值")
5 return x*y
UnboundLocalError: local variable 'x' referenced before assignment
In [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+n
f1 = add(42)
def f2(x):
return x+42
# f1和f2相同
print(f1(0))
print(f2(0))
In [12]: fun = lambda x,y: x+y
In [13]: fun(1,2)
Out[13]: 3
lambda表达式还可以作为参数传递:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
# 意为根据元组对的第一个元素排序,即1234
pairs.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)