文章目录
抽象
6.3 创建函数
关于函数
- 函数是可以调用的
- 函数可能带有参数
- 函数执行某种行为并且返回一个值(不写返回则返回None)
内置callable函数可以用来判断函数是否可以调用;它在python3中不再可用,需要使用hasattr(func,__call__)
代替
创建函数
1 2 3 4 |
def functionName(param): pass # Here are statements [return return_content] |
文档化函数
- 如果需要给函数写文档,可以加入注释(以#开头)
- 还可以直接写上字符串,它会作为函数的一部分进行储存
- 可以以
functionName.__doc__
的形式访问文档字符串 - 也可以使用
help
函数查询帮助信息(包括文档字符串)
他们不是真正的函数?
似乎有些函数不返回值,或者直接没有返回语句
他们不是真正的函数?
不,他们返回None
6.4 参数魔法
在def语句后面函数名括号中的变量叫做形参,而调用函数时提供的值叫做实参
- 当在函数内部把参数重新绑定(或重新赋值)的时候,函数外的变量不会受到影响
- 参数储存在局部作用域
- 对于可变对象,由于使用它的方式是引用,会改变原有值,请复制之后再传入参数
修改参数
传入可变对象的绑定即可
如果参数不可变
- 在python中,函数只能修改参数对象本身.如果你想要修改参数,而它本身不可变?
使用将值放在列表中的技巧即可
1 2 3 4 5 6 7 |
>>> def inc(x):x[0]=x[0]+1 ... >>> foo = [10] >>> foo = inc(foo) >>> foo [11] |
6.4.3 关键字参数和默认值
位置参数
位置参数的位置十分重要,比他们的名称更为重要
位置参数需要严格按照他们的位置传入参数
关键字参数
很多时候,参数的位置难以记住.为了让事情简单些,可以提供参数的名字
1 2 3 4 5 6 7 8 |
#对于这样的函数 def hello_l(name,greeting): print '%s %s!'%(name,greeting) #可以使用如下方法 >>> hello_l(greeting='hello',name='world') hello world |
关键字参数的主要作用在于明确每个参数的意义
默认值
关键字参数最厉害在于可以给函数中的参数提供默认值:
1 2 3 |
def hello_l(greeting='hello',name='world'): print '%s %s!'%(name,greeting) |
- 如果关键字参数和位置参数联合使用,需要将位置参数放在前面
- 除非完全清楚程序的功能和参数的意义,否则应该避免混合使用
- 一般来说,只有在强制要求的参数个数比可修改的具有默认值的参数个数少的时候,才应该使用混合的方法
命名关键字
(这一段基础教程没有提到,单个人觉得廖雪峰对此的讲解很棒,遂摘录)
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw
检查。
1 2 3 4 5 6 7 8 9 |
def person(name, age, **kw): if 'city' in kw: # 有city参数 pass if 'job' in kw: # 有job参数 pass print('name:', name, 'age:', age, 'other:', kw) |
但是调用者仍可以传入不受限制的关键字参数:
1 2 |
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456) |
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
1 2 3 |
def person(name, age, *, city, job): print(name, age, city, job) |
和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符,后面的参数被视为命名关键字参数。
调用方式如下:
1 2 |
>>> person('Jack', 24, city='Beijing', job='Engineer') |
Jack 24 Beijing Engineer
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
1 2 3 |
def person(name, age, *args, city, job): print(name, age, args, city, job) |
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
1 2 3 4 5 |
>>> person('Jack', 24, 'Beijing', 'Engineer') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: person() takes 2 positional arguments but 4 were given |
由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
1 2 3 |
def person(name, age, *, city='Beijing', job): print(name, age, city, job) |
由于命名关键字参数city具有默认值,调用时,可不传入city参数:
1 2 3 |
>>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer |
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。如果缺少,Python解释器将无法识别位置参数和命名关键字参数:
1 2 3 4 |
def person(name, age, city, job): # 缺少 *,city和job被视为位置参数 pass |
6.4.4 收集参数
收集多个参数
1 2 3 |
def print_param(*param): print param |
- 参数前的*将所有值放置在同一个元组中
- 它可以收集其余的参数
- 如果不提供任何收集的元素,param就是一空元组
收集多个关键词参数
1 2 3 4 5 6 |
def print_param(**params): return params >>> print_param(x=1,y=2) {x:1,y:2} |
使用两个星号,则会收集剩余关键字-值对
6.4.5 收集参数的逆过程
1 2 3 4 5 6 |
def add(x,y):return x+y >>> params=(1,2) >>> add(*params) 3 |
- 和其他语言中的数组差不多
- 可以推广到解字典操作
- 如果传入字典和元组,使用或不使用星号没什么不同
- 星号只在定义函数(允许使用不定数目的参数)或者调用(分割字典或者序列)时才有用
6.4.6 参数组合与更多(本小节选自廖雪峰的教程)
(说明同上)
- 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。
- 但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
- 所以,对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
6.5 作用域
- python中,变量 可以被看做值的名字
- 内建的
vars
函数可以返回变量的名字-值对,这个”不可见”的”字典”
1 2 3 4 5 |
>>> x = 1 >>> scope = vars() >>> scope['x'] 1 |
警告
一般来说,vars
函数返回的字典是不应该修改的,因为他的结果是未定义的
命名空间和作用域
这类”不可见”字典,叫做命名空间或者作用域
- 除了全局作用域外,每次函数调用都会创建一个新的作用域
- 以下方法引用全局变量会引发很多问题
1 2 3 |
def combine(paramenter): print paramenter + external |
- 众所周知,就和其他语言一样,Python的变量具有覆盖(层叠)的特性,如果局部变量屏蔽了全局变量,而你想使用它,请使用以下方法(
vars
和globals
函数有相似之处)
1 2 3 |
def combine(paramenter): return paramenter + globals['paramenter'] |
嵌套作用域
- python的函数是可以嵌套的,也就是说函数可以放在函数里面,函数可以返回函数
- 外层函数返回里层函数,被返回的函数还可以访问定义它所在的定义域
- 每次调用外层函数,内部的函数都会被重新绑定
- 外部作用域的变量一般是不能重新绑定的,当python3中的
nolocal
关键字被引入,和globals
的使用方式类似,可以让用户访问外部作用域(非全局作用域)
6.6 递归
定义?定义
1 2 3 |
def recursion(): return recursion |
一些限制
每次调用函数都会消耗一些内存,当预设的这部分内存被耗尽后,会引发超过最大递归深度的错误
有用的递归函数的组成
- 当函数直接返回值时有基本实例(最小可能性问题)
- 递归实例,包括一个或多个问题较小部分的递归调用
例子
略,标准库中的bisect函数可以有效地实现二分查找
函数式编程
几乎全部用函数完成工作的语言?是的,Python部分实现了函数式编程的优秀部分
包括map
,filter
,reduce
函数都被放置在python的functools
模块中
更详细的函数式编程内容请期待规划中的haskell学习笔记
1 2 3 4 5 6 7 8 9 10 11 12 |
map(func,seq[,seq..]) #为序列中每个元素应用函数 filter(func,seq) #返回序列中其函数为真的元素的列表 reduce(func,seq[,initial]) #等同于func(func(func(seq[0],seq[1]))) apply(func[,args[,kwargs]]) #调用函数,可以提供参数 |