python学习笔记24:基础语法之函数
1. 相关概念
以如下一段程序为例
def add(a,b=0): # a和b是形式参数,形式参数可以定义默认值比如b=0
c = a+b # c是局部变量
global d, e # d和e是全局变量
d = a*b
e = a+b
x=8
y=9
add(x, y) # x和y是实际参数
1.1 形式参数/实际参数
形式参数: 函数定义时写的,系统没有为其分配内存空间,但是在定义里面可以使用的参数。例如:def(a, b=0)。
实际参数:函数调用的时候传给函数的变量。这个是系统实实在在分配了内存空间的变量。
1)形参可以定义默认值 def func(a, b=5, c=10), 但只有在形参表末尾的那些参数可以有默认参数值,例如def func(a, b=5)是有效的,def func(a=5, b)是无效的;
2)带默认值的参数要出现在参数列表后面,如果出现在无默认值参数的前面会报错;
1.2 位置参数/关键字参数
位置参数:传入的值按照位置顺序依次赋给函数的形参。比如power(x, n),
关键字参数:通过命名为参数赋值的一种参数,不用考虑位置。比如func(25, c=24);
使用关键字参数的好处:
1)不用担心参数顺序;
2)如果其它参数都有默认值,我们可以只给我们想要的参数赋值;
1.3 参数数目可以不确定
1.3.1 使用星号*
定义函数时,在参数前加一个*号,则参数接收到的是一个tuple
>>> def calc(*numbers):
... s = 0
... for n in numbers: # numbers是一个tuple,遍历它,不能使用星号
... s += n**2
... return s
...
>>> L0 = [0,1,2,3]
>>>
>>> # 调用函数时:在list/tuple前加一个*号,把list/tuple元素变成可变参数传入
>>> calc(*L0)
14
>>> # 直接传一个List,在参数解析时没问题,但List整体作为一个参数,不符合预期
>>> # 在函数内部对参数做加法时会报告list不能与int做幂运算。
>>> calc(L0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in calc
TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'int'
在普通语句中不能使用星号
>>> *L0
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> a,b,c,d = *L0
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> a,b,c,d = (*L0)
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>> (a,b,c,d) = (*L0)
File "<stdin>", line 1
SyntaxError: can't use starred expression here
>>>
注意:
- *numbers 这种写法只能出现在两种语句中:函数参数列表、调用函数时传参。普通语句(包括函数内部语句)中使用星号会报错。
- 用在函数参数列表中:表示这个地方实际上有多个参数,但在函数内部合并到一个tuple中使用。
- 用在调用函数传参时使用:表示将待传递增的List/Tuple打散后再传,因为函数的参数不是List/Tuple而是它的元素。
- *numbers写在普通语句中,会报告语法错误: can’t use starred(标星号的) expression here。
1.3.2 使用双星号**
使用**kw, 传入0个或多个含参数名的参数,这些关键字参数在函数内部组装为一个dict。
使用关键字参数可以扩展函数的功能;
>>> def person(name, age, **kw):
... print(‘name:’, name, ‘, age’, age, ‘, others:’ kw)
...
>>> person(‘Michael’, 30) #只传入关键字参数
name:Michael, age:30, others:{}
>>>
>>> person(‘Adam’, 45, gender=’M’, job=’Engineer’) # 后面两个参数会组装成一个dict。
name:Adam, age:45, others:{‘gender’:’M’, ‘job’:’Engineer’}
>>>
>>> extra = {‘gender’:’M’, ‘job’:’Engineer’}
>>> person(‘Adam’, 45, **extra) #在dict前加**,把dict变成关键字参数传入;
name:Adam, age:45, others:{‘gender’:’M’, ‘job’:’Engineer’}
1.3.3。 *与**的作用
一般*与**只用在给函数传参数时,在函数参数外使用作用不大(还可能报错)
比如:
foo(1, 2, 3) 相当于foo(*[1, 2, 3])
bar(a=1, b=2, c=3) 相当于bar(**{‘a’:1, ‘b’:2, ‘c’:3})
*L0 可以认为是把列表(元组)L0拆散后的结果 。
*的作用:将列表拆散为元素。
L0 = [0, 1, 2, 3] # L0是list,是一个整体,把L0传递给函数,函数得到的是1个参数。
*L0 # L0是4个元素,把L0传递给函数,函数得到的是4个参数。
**的作用,将dict拆散为一系列键值对。
D0 = {‘a’:0, ‘b’:1} # D0是dict,直接传递给函数, 函数得到的是1个参数。
D0 # 是两个键值对, 把D0传递给函数,函数得到的是2个参数。
1.4. 命名关键字参数
使用**kw:可以传递任意名称的关键字作为参数;
使用*, city, job:可以限制关键字参数的名字,比如只接收city和job作为关键字参数;
- 对应的参数传参时必须传入参数名(使用’city’=’Chengdu’这种方式传参),没有参数名时,会被认为是位置参数,可能导致Error;
- 如果在定义函数时给默认值,调用时就可以不传这个参数。
>> def person(name, age, *, city, job):
.. print(name, age, city, job)
..
>> person(‘Jon’, 30, city=’Chengdu’, job=’Engineer’) # 通过参数名传参数,正常运行;
on 30 Chendu, Engineer
>>
>> extra = {‘city’:’Shenzhen’, ‘job’:’Art’}
>> person(‘Bill’, 9, **extra) # 通过dict传入参数,正常运行;
ill 9 Shenzhen Art
>>>
>>> extra ={‘job’:’Art’}
>>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:缺少city参数;
TypeError: person() missing 1 required keyword-only arg: ‘city’
>>>
>>> extra = {‘length’:6, ‘job’:’Art’}
>>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:不能使用length这个参数;
TypeError: person() got a unexpected keyword arg: ‘length’
1.5. 参数组合
Python函数的5种参数可以任意组合使用, 参数定义的顺序必须为:
- 必选参数
- 默认参数
- 可变参数
- 命名关键字参数
- 关键字参数
比如:
def f1(a, b, c=0, *args, **kw):
pass
def f2(a, b, c=0, *, d, **kw):
pass
任意函数,都可以通过类似func(*args, **kw)的形式(一个元组和一个字典)调用它,无论它的参数是如何定义的。
1.6. 返回值
- 使用return从函数返回(跳出),也可以返回一个值。
- python程序默认返回None
- return可以返回多个值(实际上是返回一个元组,省略括号,直接赋值给多个变量)
def move(x0, y0, s=0, angle=0):
x1=x0+s*math.cos(angle)
y1=y0+s*math.sin(angle)
return x1,y1
x,y = move(100,100, 50, math.pi*(1/3))
1.7. DocStrings
- 可以在函数中嵌入说明语句.
- 使用三引号可以跨行写说明,不需要续行符
- 使用__doc__方法,可以调用到说明语句。
>>> def max(x,y):
... '''''Return the max of two number.
... The two number must be integer.'''
... if a>b:
... return a
... else:
... return b
...
>>> print max(1,2)
>>> print max.__doc__
1.8. 函数起别名
函数名其实是指向一个函数的引用,所以可以把函数名赋给一个变量,然后可以后这个变量调用函数:
>>> a = abs # 变量a指向abs函数
>>> print(a(-1))
1
>>> print(a)
<built-in function abs>
1.9. 函数式编程
Python对函数式编程提供部分支持;
由于Python允许使用变量,所以Python不是纯函数式编程语言;
2. 高阶函数
2.1. 变量可以指向函数
>>> x = abs(-10) # 调用函数,并获得函数调用结果;
>>> f = abs # 把函数赋值给变量
>>> print(f) # <built-in function abs>
>>> f(-10) # 变量f指向abs,f(-10)与abs(-10)完全相同
2.2. 函数名也是变量
函数名其实就是指向函数的变量。
比如abs()这个函数,abs就是一个变量,指向计算绝对值的函数;
>>> import builtins
>>> builtins.abs
<built-in function abs>
>>> builtins.abs = 10 # 可以把abs函数的指向改变
2.3. 传入函数
一个函数A可以接收另一个函数B作为参数,这种函数A就称之为高阶函数;
>>> def func_add(f, x, y):
... return f(x)+f(y)
...
>>> abs_add(abs, 5, -6)
11
map()、reduce()、都是高阶函数
2.4. 返回函数
高阶函数除了可以接受函数做为参数外,还可以把函数做为结果值返回;
3. 匿名函数lambda
- lambda 定义了一个匿名函数
- lambda 并不会带来程序运行效率的提高,只会使代码更简洁。
- lambda 只能有一个表达式,不用写return,返回值就是该表达式的值;
- 如果可以使用for...in...if来完成的,坚决不用lambda。
- 如果使用lambda,lambda内不要包含循环,如果有,宁愿定义函数来完成,使代码获得可重用性和更好的可读性。
>>> foo = [2,18,9,22]
>>> list(filter(lambda x:x%3==0, foo)) #列表中可以被3整除的数;
[18, 9]
>>> list(map(lambda x:x*2+10, foo)) #将原列表元素做*2+10;
[14, 46, 28, 54]
>>> list(map(lambda x:[x,x*2], foo)) #将列表元素映射为list;
[[2, 4], [18, 36], [9, 18], [22, 44]]
>>> functools.reduce(lambda x,y:x+y, foo) # 返回数值,将原列表相加;
51
上述filter可以改写为:[x for x in foo if x%x==0]
上述map可以改写为:[x*2+10 for x in foo]
4. 装饰器
作用:增强函数的功能,但又不修改函数定义本身;
例1:
定义一个装饰器,它可以打印当前调用函数的名称,它接受一个函数作为参数,并返回一个函数
>>> def log(func):
... def wrapper(*args, **kwargs):
... print(‘call func: {name}: ’.format(name=func.__name__))
... return func(*args, **kwargs)
... return wrapper
把装饰器(log)作用到其它函数(now)上,相当于now=log(now):
调用now(),在执行now本身之前,会先打印一行log
>>> @log
... def now():
... print(datetime.datetime.now())
...
>>> import datetime #可以在调用now之前才import它需要用到的模块
>>> now()
call func: now
2018-07-27 11:29:18.239895
例2,定义一个装饰器,打印函数调用的次数和函数执行时间
import time
def timer(func):
lst = [0, 0] # 使用一个list保存要记录的内容,在每次调用时,lst指向同一地址;
def wrapper(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs) #调用函数,并保留返回值
end = time.time()
delta = end –start
lst[0] += 1 # lst[0]记录函数执行的次数
lst[1] += delta # lst[1]记录函数执行的时间
print(f‘cnt: {lst[0]}, time: {lst[1]}, {delta}’)
return r # 返回函数的结果,这里有个局限性,只能处理返回单个值的函数;
return wrapper # 返回包了壳的函数
测试:
>>> @timer
>>> def my_sum(n): #定义一个函数
... r = 0
... for i in range(n):
... r += i
... return r
...
>>> for i in range(10):
... r = my_sum(10000000)
...
cnt 1, time 0.937***, 0.937***
cnt 2, time 1.852***, 0.914***
cnt 3, time 2.747***, 0.895***
cnt 4, time 3.681***, 0.934***
cnt 5, time 4.601***, 0.919***
cnt 6, time 5.563***, 0.962***
cnt 7, time 6.494***, 0.930***
cnt 8, time 7.403***, 0.909***
cnt 9, time 8.374***, 0.970***
cnt 10, time 9.280***, 0.906***
5. 偏函数
- 语法:functools.partial(函数对象, *args, **kwargs)
- 参数:函数对象、位置参数、命名参数
Python的functools模块提供了很多功能,其中一个是偏函数(Partial Function);
functools.partial功能:帮忙我们创建一个偏函数,但不需要我们自己定义偏函数;
例如:将2进制字符串转为10进制整数,可以定义一个函数int2
def int2(x, base=2):
return int(x, base=base)
这样默认处理2进制字符串.
但我们不需要自己定义函数int2,可以使用偏函数达到目的:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2(‘00010000’)
16
functools.partial的作用:把函数的某些参数给固定住(即设置默认值),返回一个新的函数;
但是调用int2时,仍然可以给它指定base参数为其它值:
>>> int2(‘00010000’,base=10)
10000
例1:
>>> int2 = functools.partial(int, base=2)
>>> int2(‘00010000’)
>>>
>>> #相当于:
>>> kwargs = {‘base’:2} # 将’base’:2作为kwargs的一部分
>>> int(‘00010000’, **kwargs)
例2:
>>> max_with_10 = functools.partial(max, 10)
>>> max_with_10(5, 6, 7)
>>>
>>> #相当于:
>>> args=(10, 5, 6, 7) # 将10作为*args的一部分自动加到左边
>>> max(*args)