目录
函数与函数式编程
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。Python提供了许多内建函数,比如print()。我们也可以自己创建函数,这被叫做用户自定义函数。
函数的定义
我们可以定义一个由自己想要功能的函数,以下是简单的规则:
函数代码块以def关键词开头,后接函数标识符名称和圆括号()。
任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号起始,并且缩进。
return[表达式]结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回None。
以创建一个输出任意范围内 Fibonacci 数列的函数为例:
def fib(n):
a,b=0,1
while a<n:
print(a,end=" ")
a,b=b,a+b
print()
fib(2000)
关键字def引入一个函数定义。它必须后跟函数名称和带括号的形式参数列表。构成函数体的语句从下一行开始,并且必须缩进。
函数体的第一个语句可以(可选的)是字符串文字;这个字符串文字是函数的文档字符串或docstring。
函数的执行会引入一个用于函数局部变量的新符号表。更确切地说,函数中所有的变量赋值都将存储在局部符号表中;而变量引用会首先在局部符号表中查找,然后是外层函数的局部符号表,再然后是全局符号表,最后是内置名称的符号表(变量作用域问题,之后会详细介绍)。因此,全局变量和外层函数的变量不能在函数内部直接赋值(除非是在global语句中定义的全局变量,或者是在nonlocal语句中定义的外层函数的变量),尽管它们可以被引用。
在函数被调用时,实际参数(实参)会被引入被调用函数的本地符号表中;因此,实参是通过按值调用传递的(其中值始终是对象引用而不是对象的值)。当一个函数调用另外一个函数时,将会为该调用创建一个新的本地符号表。
函数定义会把函数名引入当前的符号表中。函数名称的值具有解释器将其识别为用户定义函数的类型。这个值可以分配给另一个名称,该名称也可以作为一个函数使用。这用作一般的重命名机制:
print(fib)
f=fib
f(100)
上面这个函数是无返回值的函数,即没有return语句,默认返回None。一般来说解释器不会打印出单独的返回值None,如果真想看到它,可以使用print()
print(fib(0))
print(fib(1))
写一个返回斐波那契数列的列表(而不是把它打印出来)的函数,非常简单:
def fib2(n):
result=[]
a,b=0,1;
while a<n:
result.append(a)
a,b=b,a+b
return result
f100=fib2(100)
print(f100)
函数与过程
函数与过程都是可以被调用的实体,一般来说,函数有一定的返回值,过程是简单、特殊、没有返回值的函数。
返回值与返回值类型
当没有返回值时,Python的实际返回对象是None。
当返回对象的数目为1时,Python的返回值类型是该对象的类型。
当返回对象的数目大于1时,Python的返回值类型是元组。
def fun1():
pass
print(fun1())
def fun2():
return '123'
print(fun2())
def fun_list():
return ['123','xyz',456]
print(fun_list())
def fun_tuple1():
return 1,2,3
print(fun_tuple1())
def fun_tuple2():
return (1,2,3)
print(fun_tuple2())
运行结果:
None
123
['123', 'xyz', 456]
(1, 2, 3)
(1, 2, 3)
fun1()返回None,fun2()的返回值类型是字符串,fun_list()、fun_tuple1()、fun_tuple2()的返回值大于1个,fun_list()返回的是一个列表,fun_tuple1()、fun_tuple2()等价,都是一个元组。
所以在我们看来。返回一个容器对象时,好像是返回了多个对象(元组在语法上不需要带括号),事实上,他们仍返回的是一个,一个容器罢了。
保存元组返回值的三种方法:
def fun_tuple():
return 'abc',5,['k','p']
aTuple=fun_tuple()
x,y,z=fun_tuple()
(a,b,c)=fun_tuple()
print(aTuple)
print(x,y,z)
print((a,b,c))
运行结果:
('abc', 5, ['k', 'p'])
abc 5 ['k', 'p']
('abc', 5, ['k', 'p'])
在对x、y、z和a、b、c的赋值中,根据返回值的顺序,每个变量会接收到与之对应的返回值。而aTuple直接获得函数隐式返回的整个元组。
前向引用
def foo():
print('1')
bar()
def bar():
print('2')
foo()
运行结果:
1
2
可见上例中foo()先调用的bar(),然后bar()才声明,系统不会报错。但是不推荐这么写,从感觉上来说,有悖常识,自己尽量让需要用的放在前面吧。
内部/内嵌函数
在函数体内部创建另外一个函数是完全合法的,这种函数叫内嵌函数。
如:
def foo():
print('1')
def bar():
print('2')
bar()
foo()
运行结果:
1
2
内部函数的整个函数体都在外部函数的作用域里。如果没有任何对bar()的外部引用,那么除了在外部函数体foo()内,任何其他地方都不能对其进行调用。
另外一个函数体内创建函数对象的方式是使用lambda语句。这个之后的学习会涉及。
函数装饰器
暂时看不懂,先不写。
传递函数
传递函数的方式大致有三种:
- 被引用(访问或者以其他变量作为其别名)
- 作为参数传入函数
- 作为字典、列表等容器对象的元素
下面详细介绍一下用其他的变量作为函数的别名。
def foo():
print('1')
bar=foo
bar()
运行结果:
1
当我们把foo赋给bar时,bar和foo引用了同一个函数对象,所以能以调用foo()相同的方式调用bar()。
故:foo是函数对象的引用;foo()是函数对象的调用。
再如:
def foo():
print('1')
def bar(argfunc):
argfunc()
bar(foo)
运行结果:
1
参数的种类
位置参数
位置参数必须以在被调用函数中的定义的准确顺序来传递。若没有任何默认参数的话,传入函数的参数的精确的数目必须和声明中的数字一致。
当然,我们也可以不按位置地将关键字参数传入函数,给出关键字来匹配其在参数列表中的合适位置,但是位置参数的数字一定要对。
def info(name,sex,grades,school='abc'):
print(name)
print(sex)
print(grades)
print(school)
info('Jennifer','female','3')
print()
info(sex='female',name='Jennifer',school='lalala',grades='1')
print()
info(grades='1',sex='male',name='frank')
运行结果:
Jennifer
female
3
abc
Jennifer
female
1
lalala
frank
male
1
abc
参数默认值
最有用的形式是对一个或多个参数指定一个默认值。这样创建的函数,可以用比定义时允许的更少的参数调用,设置默认参数,必选参数在前,默认参数在后。比如:
def ask_ok(prompt,retries=4,reminder='Please try again!'):
while True:
ok=input(prompt)
if ok in ('y','ye','yes'):
return True
if ok in ('n','no','nop','nope'):
return False
retries=retries-1
if retries<0:
raise ValueError('invalid user response')
print(reminder)
这个函数可以通过几种方式调用:
只给出必需的参数:ask_ok('Do you really want to quit?')
给出一个可选的参数:ask_ok('OK to overwrite the file?', 2)
或者给出所有的参数:ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
这个示例还介绍了 in 关键字。它可以测试一个序列是否包含某个值。
默认值是在定义过程中在函数定义处计算的,所以下例会打印5。
i=5
def f(arg=i):
print(arg)
i=6
f()
重要警告: 默认值只会执行一次。这条规则在默认值为可变对象(列表、字典以及大多数类实例)时很重要。比如,下面的函数会存储在后续调用中传递给它的参数:
def f(a,L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
打印结果如下:
如果你不想要在后续调用之间共享默认值,你可以这样写这个函数:
def f(a,L=None):
if L is None:
L=[]
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
命名关键字参数
函数调用时,指定参数名称,称为关键字参数(别和默认参数混淆,这里是函数调用)
def temp(a,b,c):
print(a)
print(b)
print(c)
temp(1,3,c=99)
temp(a=1,b=2,c=3)
temp(1,b=2,c=100)
顺序要求:函数调用时,关键字参数必须在普通参数(位置参数)的后面
temp(100,b=200,c=300)
temp(a=100,32,100) #错误语法,关键字参数不能在普通参数的前面
使用字典解包方式传入关键字参数(后面小节中会详细讲到)
hi = {"a":100,"b":99,"c":1000}
temp(**hi) #等同于下面#
temp(a=100,b=99,c=1000)
函数调用时,当剩余的参数全部为关键字参数时,可随意定义顺序
当全部为关键字参数时,调用函数时的参数顺序可随意书写,因为你指定了参数名,但是参数的数量不能少于函数定义时要求的位置参数的数量
temp(c=100,b=38,a=10)
也可以使用形如kwarg=value的关键字参数来调用函数。例如下面的函数:
def parrot(voltage,state='a stiff',action='voom',type='Norwegian Blue'):
print("--This parrot wouldn't",action,end=' ')
print("if you put",voltage,"volts though it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
接受一个必需的参数(voltage)和三个可选的参数(state, action,和 type)。这个函数可以通过下面的任何一种方式调用:
- 一个位置参数:parrot(1000)
- 一个关键字参数:parrot(voltage=1000)
- 两个关键字参数;parrot(voltage=1000000, action='VOOOOOM')
- 两个关键字参数;parrot(action='VOOOOOM', voltage=1000000)
- 三个位置参数:parrot('a million', 'bereft of life', 'jump')
- 一个位置参数,一个关键字参数:parrot('a thousand', state='pushing up the daisies')
但下面的函数调用都是无效的:
- 丢失必要参数:parrot()
- 在关键字参数之后的非关键字参数 :parrot(voltage=5.0, 'dead')
- 同一参数的重复值 :parrot(110, voltage=220)
- 未知的关键字参数:parrot(actor='John Cleese')
在函数调用中,关键字参数必须跟随在位置参数的后面。传递的所有关键字参数必须与函数接受的其中一个参数匹配(比如 actor 不是函数 parrot 的有效参数),它们的顺序并不重要。这也包括非可选参数,(比如 parrot(voltage=1000) 也是有效的)。不能对同一个参数多次赋值。下面是一个因为此限制而失败的例子:
def function(a):
pass
function(0,a=0)
关键字参数:**others,便于函数功能的扩展
当存在一个形式为**name的最后一个形参时,它会接收一个字典,其中包含除了与已有形参相对应的关键字参数以外的所有关键字参数。这可以与一个形式为*name,接收一个包含除了已有形参列表以外的位置参数的元组的形参组合使用(*name必须出现在**name之前,下一小节会讲到)。例如,如果我们这样定义一个函数:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
它可以像这样调用:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
注意打印时关键字参数的顺序保证与调用函数时提供它们的顺序是相匹配的。
任意的参数列表 *others
最后,最不常用的选项是可以使用任意数量的参数调用函数。这些参数会被包含在一个元组里。在可变数量的参数之前,可能会出现零个或多个普通参数。:
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
一般来说,这些可变参数将在形式参数列表的末尾,因为它们收集传递给函数的所有剩余输入参数。出现在*args参数之后的任何形式参数都是‘仅关键字参数’,也就是说它们只能作为关键字参数而不能是位置参数。
def concat(*args, sep="/"):
return sep.join(args)
print(concat("earth", "mars", "venus"))
print(concat("earth", "mars", "venus", sep="."))
解包参数列表
当参数已经在列表或元组中但需要为需要单独位置参数的函数调用解包时,会发生相反的情况。例如,内置的 range() 函数需要单独的 start 和 stop 参数。如果它们不能单独使用,请使用 * 运算符编写函数调用以从列表或元组中解包参数,*args输出结果是3 6:
list(range(3,6))
print(list(range(3,6)))
args=[3,6]
print(list(range(*args)))
以同样的方式,字典可以使用 ** 运算符来提供关键字参数:
def parrot(voltage, state='a stiff', action='voom'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.", end=' ')
print("E's", state, "!")
d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)
更多Python框架梳理,请参考: 【Python学习】Python最全总结
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。