Python学习 - 函数
目录
函数是组织好的, 可重复使用的, 用来实现单一, 或相关联功能的代码段.
函数能提高应用的模块性, 和代码的重复利用率. 你已经知道Python提供了许多内建函数, 比如print(). 但你也可以自己创建函数, 这被叫做用户自定义函数.
你可以定义一个由自己想要功能的函数, 以下是简单的规则:
- 函数代码块以 def 关键词开头, 后接函数标识符名称和圆括号().
- 任何传入参数和自变量必须放在圆括号中间. 圆括号之间可以用于定义参数.
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明.
- 函数内容以冒号起始, 并且缩进.
- return [表达式] 结束函数, 选择性地返回一个值给调用方. 不带表达式的return相当于返回 None.
以下为一个简单的Python函数, 它将一个字符串作为传入参数, 再打印到标准显示设备上.
1 # Python 3.x 2 3 def funcname(args): 4 """函数描述信息""" 5 函数体 6 return 返回值
定义空函数:
空函数, 从名字上可以看出来就是一个没有实际作用的函数, 有人会问, 既然没有作用那为什么还用定义它呢. 空函数主要用于程序设计初期, 先进行程序结构设计, 定义的功能函数先不实现功能, 等程序结构设计好后, 更具需要逐步添加具体功能代码.
1 # Python 3.x 2 3 # 定义一个空函数名为func 4 def func(): 5 pass 6 7 # 调用func这个函数 8 func()
函数调用方式:
- 语句形式: 直接在函数名后面加小括号(), 进行调用.
- 表达式形式: 将函数的返回值作为表达式的一部分.
- 高阶函数: 将一个函数作为返回值或参数使用.
1 # Python 3.x 2 3 def printme( str ): 4 """打印任何传入的字符串""" 5 print(str) 6 return 1 7 8 # 语句形式 9 printme("我要调用用户自定义函数!") 10 11 # 表达式形式 12 res = 10 * printme("我要调用用户自定义函数!") 13 14 # 高阶函数 15 def my_max(n1, n2, func): 16 """返回func(n1,n2)的值""" 17 return func(n1, n2) 18 19 print(my_max(10,20,max)) 20 21 # 执行结果 22 我要调用用户自定义函数! 23 我要调用用户自定义函数! 24 20
- 空返回值: 如果不写return, 默认返回None
- 单返回值
- 多返回值: 多个返回值, 会以元组形式返回 (相当于将你所有的返回值按顺序加入到一个元组中, 返回一个元组)
- 形参: 只有在被调用时才分配内存单元, 在调用结束时, 即刻释放所分配的内存单元. 因此, 形参只在函数内部有效. 函数调用结束返回主调用函数后则不能再使用该形参变量.
- 实参: 是常量, 变量, 表达式, 函数等, 无论实参是何种类型的量, 在进行函数调用时, 它们都必须有确定的值, 以便把这些值传送给形参. 因此应预先用赋值, 输入等办法使参数获得确定值
参数的分类
- 以形参的角度讲: 参数分为位置参数, 默认参数与不定长参数
- 位置参数: 必传参数, 须以正确的顺序传入函数
- 默认参数: 非必传参数, 默认参数的值如果没有传入, 则被认为是默认值, 定义的时候默认参数必须放在位置参数后面
- 不定长参数: 非必传参数, 可以接受任何长度的参数, 定义的时候必须放在最后面
- *args: 以元组形式传入
- **kwargs: 以字典的形式传入
- 以实参的角度讲: 参数分为位置参数, 关键参数
- 位置参数: 必须以正确的顺序依次传给函数的形参
- 关键参数: 不必按照顺序给函数传值, 关键参数必须放在位置参数后面
实例:
1 # Python 3.x 2 3 # 形参 - 位置参数 4 def foo(x, y, z): 5 return x, y, z 6 print(foo(1, 2, 3)) # 按照顺序依次把实参赋值给对应的形参 7 print(foo(4, z = 5, y = 6)) # 通过关键参数传值, 可以不按照位置的限制, 但必须放在位置参数后面 8 # 执行结果 9 1 2 3 10 4 6 5 11 12 # 形参 - 默认参数 13 def foo(x, y=1): # 默认参数必须放在位置参数后面定义 14 return x, y 15 print(foo(1, 2)) # 给默认参数传值, 默认参数则等于传入的值 16 print(foo(1)) # 不给默认参数传值, 则为默认值 17 # 执行结果 18 1 2 19 1 1 20 21 # 形参 - 不定量变参 22 def foo(x, y=1, *args, **wkargs): 23 print(x, y, args, wkargs) 24 print(foo(1, 2, 3, 4, 5, name = "kys", age = "18")) 25 # 执行结果 26 1 2 (3, 4, 5) {"name" = "kys", "age" = "18"} 27 # 注: 这里的args与kwargs是可以换做其他合法变量名的. 并不是语法的一部分. 28 29 # *args作为参数传入函数的说明 30 # 应该看过别人把*args与**kwargs当作参数传入函数吧, 这是什么意思呢, 难道调用的时候也能设置不定量变参? 31 # 当然不是, 我们上面已经说过, 实参在使用的时候一定有个确定的值. 32 # 那么我们来说一下到底是怎么回事吧. 33 34 a = [1, 2, 3, 4] 35 def foo(a, b, c, d): 36 return a, b, c, d 37 # 这里我想把a列表的四个值, 作为foo函数的参数 怎么写呢. 38 foo(a[0], a[1], a[2], a[3]) # 肯定有人说这样写. 但是我告诉你太low了. 39 # 我们可以这样来写 40 foo(*a) 41 # 这里的 *a 是什么意思呢, 相当于把a的元素遍历出来, 并按照索引的位置依次传给函数 42 # 执行结果 43 1 2 3 4 44 1 2 3 4 45 46 # 同理, 我有个字典, 也想传入foo函数可以这样写. 47 dic = {'a' = 4, 'b' = 3, 'c' = 2, 'd' = 1} 48 foo(**dic) # 等价于foo(a=4, b=3, c=2, d=1) 49 # 执行结果 50 4 3 2 1
限制参数与返回值的类型
了解过java等强类型语言的朋友们可能知道, 在定义变量的时候会明确指定这个变量的类型, 当你定义了一个int类型的变量, 而给这个变量赋值一个字符串类型的数据时, 会抛出异常, 在Python中, 变量的类型是交由python解释器自行处理的, 也就是说在Python中, 你定义一个变量a, 这时候你可以给它赋值成一个int型, 也可以给他赋值成一个str型.
Python这样的机制, 给我们带来了便利, 同时也给我们带来了很多不便之处. 例如:
- 优点: 对于函数的调用者来说, 不用考虑我这个参数应该传入什么类型的值.
- 缺点: 对于函数的编写者来说, 考虑到别人传入的参数类型我处理不了, 需要额外增加判断传入的参数类型.
1 # Python 3.x 2 3 # 定义一个处理加法的函数 4 def sum(n1, n2): 5 return n1 + n2 6 7 a, b = 10, 20 8 print(sum(a, b)) 9 # 执行结果 10 30 11 12 # 这里是写死了a, b的值为两个int类型的数据. 13 # 但正常开发过程中, 我们定义了一个函数, 调用的可能是别人. 14 # 我们无法确定别人传入的参数类型是不是int. 15 a, b = 10, "20" # 这里给a赋值一个int类型, 给b赋值一个str类型 16 print(sum(a, b)) 17 # 执行结果 18 Traceback (most recent call last): 19 File "test.py", line 16, in <module> 20 print(sum(a, b)) 21 File "test.py", line 5, in sum 22 return n1 + n2 23 TypeError: unsupported operand type(s) for +: 'int' and 'str' 24 # 报错很明显, int和str类型的数据没办法做加法操作. 直接抛出异常
对于上面实例来讲, 很明显我们编写的sum()方法并不完善. 如何解决这个问题呢. 我们有三种办法:
- 函数内部判断参数类型
- 为形参显性设置类型
- 编写函数文档
1 # Python 3.x 2 3 # 内部判断 4 def sum(n1, n2): 5 if isinstance(n1, int) and isinstance(n2, int): 6 return n1 + n2 7 else: 8 return "Type Error" 9 10 # 为形参显性设置类型 11 def sum(n1:int, n2:int)->int: 12 return n1 + n2 13 # 这里显性的指出 n1, n2的形参类型为int 14 # "->int" 则显性的指出返回值的类型为int 15 16 # 内部判断的方式 解决了问题, 但增加了函数编写者的工作了, 要判断每个参数的类型, 并不是完美的解决办法 17 # 显性设置类型 虽然解决了问题, 但它违背了 python 简洁的理念. 18 19 # 推荐使用第三种方法 20 # 给你的函数写上函数文档 21 def sum(n1, n2): 22 """ 23 返回n1, n2的相加结果 24 :param n1: int 25 :param n2: int 26 :return: int 27 """ 28 return n1 + n2 29 # 这样在调用者时候你的函数时, 会通过文档看到, 你需要两个参数, 都是int类型的, 你函数的返回值也是int类型.
参数的传递
在 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 函数的参数传递:
-
不可变类型: 类似 c++ 的值传递, 如 整数, 字符串, 元组. 如fun(a), 传递的只是a的值, 没有影响a对象本身. 比如在 fun(a)内部修改 a 的值, 只是修改另一个复制的对象, 不会影响 a 本身
-
可变类型: 类似 c++ 的引用传递, 如 列表, 字典. 如 fun()la), 则是将 la 真正的传过去, 修改后fun外部的la也会受影响
python 中一切都是对象, 严格意义我们不能说值传递还是引用传递, 我们应该说传不可变对象和传可变对象
python 传不可变对象实例
1 # Python 3.x
2
3 def ChangeInt( a ):
4 a = 10
5
6 b = 2
7 ChangeInt(b)
8 print(b)
9 # 执行结果
10 2
实例中有 int 对象 2,指向它的变量是 b,在传递给 ChangeInt 函数时,按传值的方式复制了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
传可变对象实例
1 #Python 3.x 2 3 def changeme(mylist): 4 "修改传入的列表" 5 mylist.append([1,2,3,4]) 6 print("函数内取值: ", mylist) 7 8 # 调用changeme函数 9 mylist = [10,20,30] 10 changeme(mylist) 11 print("函数外取值: ", mylist) 12 # 执行结果 13 函数内取值: [10, 20, 30, [1, 2, 3, 4]] 14 函数外取值: [10, 20, 30, [1, 2, 3, 4]]
实例中传入函数的和在末尾添加新内容的对象用的是同一个引用.
一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。两种最基本的变量作用域如下:
- 全局变量
- 局部变量
全局与局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
1 # Python 3.x 2 3 total = 0 # 这是一个全局变量 4 5 def sum( arg1, arg2 ): 6 """返回2个参数的和""" 7 total = arg1 + arg2; # total在这里是局部变量. 8 print("函数内是局部变量 : ", total) 9 return total 10 11 #调用sum函数 12 sum( 10, 20 ) 13 print("函数外是全局变量 : ", total) 14 # 以上实例输出结果: 15 函数内是局部变量 : 30 16 函数外是全局变量 : 0
python 使用 lambda 来创建匿名函数。
- lambda只是一个表达式,函数体比def简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
- 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
语法
lambda函数的语法只包含一个语句, lambda [arg1 [,arg2,.....argn]]:expression
1 # Python 3.x 2 3 sum = lambda arg1, arg2: arg1 + arg2 4 print("相加后的值为 : ", sum( 10, 20 )) 5 print("相加后的值为 : ", sum( 20, 20 )) 6 # 以上实例输出结果: 7 相加后的值为 : 30 8 相加后的值为 : 40
闭包的定义:
在计算机科学中, 闭包(Closure)是词法闭包(Lexical Closure)的简称, 是引用了自由变量的函数. 这个被引用的自由变量将和这个函数一同存在, 即使已经离开了创造它的环境也不例外. 所以, 有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体. 闭包在运行时可以有多个实例, 不同的引用环境和相同的函数组合可以产生不同的实例.
闭包的特点:
- 隐藏状态
- 延迟计算
闭包实例:
1 # Python 3.x 2 3 def f1(): 4 x = 1 # 在外层函数中定义一个变量x 值为1 5 6 def f2(t): # 定义内部函数, 接受一个变量t 7 print("t: " % t) 8 print("x: " % x) 9 10 return f2 # 外层函数直接返回内部函数的内存地址 11 12 f=f1() # 这时候 f 指向的是 f2 的内存地址 13 14 f(10) 15 # 执行结果 16 # t: 10 17 # x: 1 18 19 # 在这里我们称 f2 为闭包函数, 20 # 他保留了外层函数 x 变量的值和它本身函数的处理过程. 21 # 并且在下次调用才执行这个处理过程.
闭包的具体应用实例:
# Python 3.x import urllib.request def getpage(url): # 定义一个获取页面的函数 def sendget(): # 定义一个内层函数, 让他来发送http请求获取页面数据 return urllib.request.urlopen(url).read() # 内存函数将页面返回 return sendget # 外出函数返回内层函数 baidu = getpage("http://www.baidu.com") # 定义一个变量保存 sendget 这个闭包函数 # 这个时候 baidu 这个变量中包含了 url 与 sendget 函数中定义的操作 # 这样我就可以在程序初期初始化我需要访问的页面, 而到了需要使用的时候直接执行 baidu() 来获取baidu的页面内容, 当然我如果还有其他的页面要访问可以都先定义好. biying = getpage("http://cn.bing.com") print(baidu()) print(biying()) # 执行结果过长 这里不再展示了
内置函数具体说明请参考: http://www.cnblogs.com/sesshoumaru/p/6140987.html