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
>>>

注意:

  1. *numbers 这种写法只能出现在两种语句中:函数参数列表、调用函数时传参。普通语句(包括函数内部语句)中使用星号会报错。
  2. 用在函数参数列表中:表示这个地方实际上有多个参数,但在函数内部合并到一个tuple中使用。
  3. 用在调用函数传参时使用:表示将待传递增的List/Tuple打散后再传,因为函数的参数不是List/Tuple而是它的元素。
  4. *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作为关键字参数;

  1. 对应的参数传参时必须传入参数名(使用’city’=’Chengdu’这种方式传参),没有参数名时,会被认为是位置参数,可能导致Error;
  2. 如果在定义函数时给默认值,调用时就可以不传这个参数。
>> 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种参数可以任意组合使用, 参数定义的顺序必须为:

  1. 必选参数
  2. 默认参数
  3. 可变参数
  4. 命名关键字参数
  5. 关键字参数

比如:

def f1(a, b, c=0, *args, **kw):
    pass

def f2(a, b, c=0, *, d, **kw):
    pass

任意函数,都可以通过类似func(*args, **kw)的形式(一个元组和一个字典)调用它,无论它的参数是如何定义的。

1.6. 返回值

  1. 使用return从函数返回(跳出),也可以返回一个值。
  2. python程序默认返回None
  3. 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

  1. 可以在函数中嵌入说明语句.
  2. 使用三引号可以跨行写说明,不需要续行符
  3. 使用__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

  1. lambda 定义了一个匿名函数
  2. lambda 并不会带来程序运行效率的提高,只会使代码更简洁。
  3. lambda 只能有一个表达式,不用写return,返回值就是该表达式的值;
  4. 如果可以使用for...in...if来完成的,坚决不用lambda。
  5. 如果使用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. 偏函数

  1. 语法:functools.partial(函数对象, *args, **kwargs)
  2. 参数:函数对象、位置参数、命名参数

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)  
posted @ 2020-07-06 15:08  编程驴子  阅读(352)  评论(0编辑  收藏  举报