函数

重复进行运算时,通过调用固定的函数,可以避免不必要的麻烦。要用一个函数,需要知道函数的名称和参数,诸如求绝对值abs(在官网查询),也可以通过help(abs)来了解相关信息

#调用abs函数
>>> abs(100)
100
>>>abs(-20)
20
>>>abs(12.34)
12.34

注:调用函数时,输入参数数量错误时,TypeError报错,会明确指出abs()有且只有1个参数。

>>> abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)

注:如果个数正确,参数类型不被接受,也会报TypeError的错误。

>>> abs('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'

max()

可接受多个参数,并返回最大的那一个

数据类型转换

int():把其他数据转化为整数

>>> int('123')
123
>>> int(12.34)
12
>>> folat('12.34')
12.34
str(1.23)
'1.23'
>>>str(100)
'100'
>>>bool(1)
True
>>>bool('')
False

函数名:是只想函数对象的应用,可以当做函数名赋给一个变量,相当于给函数取了个别名

>>> a = abs # 变量a指向abs函数
>>> a(-1)

定义函数

def语句:依次写出函数名、括号、参数、冒号,在缩进块中编写函数体,返回时用return语句返回

#求绝对值的函数
def my_abs(x):
    if x >= 0:
        return x
    else:
    return -x

空函数

定义:什么事也不做,可以用pass语句

def nop():
    pass

pass 语句什么都不做,实际上是用来作为占位符。
pass 还可以用在其他语句里:

if age >= 18:
   pass

如果缺少pass,代码运行就会有语法错误。

参数错误

如果参数个数、类型不对,解释器会自动检查出来。

返回多个值

感性理解:游戏中需要从一个点移动到另一个点,给出坐标、位移、角度,就可以计算出新的坐标。

import math

def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny

import math语句表示导入math包,允许代码引用math里的sin、cos等函数
同时获得返回值:

>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0

Python函数返回的仍然是单一值

>>> r = move(100, 100, 60, math.pi / 6)
>>> print(r)
(151.96152422706632, 70.0)

此处克制返回值是一个tuple(元祖),多个变量可以同时接受一个tuple,按位置赋给对应的值。Python的函数返回值就是返回一个tuple

函数的参数

位置参数

调用者需要知道如何传递正确参数、返回何值
power()函数,参数 x 就是一个位置参数
计算平方:

def power(x):
    return x * x

如果要计算多次幂,相对较麻烦,这时如果将power(x) 修改为power(x,n)

def power(x, n):
    s = 1
    while n > 0:
        n = n - 1 
        s = s * x
    return s 

修改后就可以计算任意的幂函数

>>> power(5, 2)
25
>>> power(5, 3)
125

在power(x, n)函数中有两个参数:x 和 n ,这两个参数便是位置参数,调用函数是,传入的两个值按照位置顺序依次赋值给参数 x 和 n

默认参数

定义:当旧代码因为缺少一个参数而无法正常调用,这时候默认参数就派上用场:

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

这样调用power(5)时,相当于调用power(5, 2):

>>>power(5)
25
>>> power(5,2)
25

对于n > 2的情况,要明确输入n 的值
注意事项:
(1) 必选参数在前,默认参数在后
(2) 默认参数能降低调用函数的难度

def enrool(name, gender):
    print('name:', name)
    print('gender:',gender)

这样输入注册函数时,只需要传入name和gender两个参数,调用时也只需要传入两个参数

>>>enroll('Sarah', 'F')
name: Sarah
gender: F

如果需要传入的信息增多,诸如还需年龄、城市、日期,那么调用函数的复杂度将增加,可以把年龄、城市等设置为默认参数:

def enroll(name, gender, age=6, city='Beijing'):
    print('name', name)
    print('gender:', gender)
    print('age:',age )
    print('city',city)

这样注册时可以默认城市和年龄是固定值,只需要提供需要的两个参数:

>>>enroll('Sarah','F')
name: Sarah
gender: F
age: 6
city: Beijing

只有与默认参数不符合时学生才需要提供额外信息:

enrool('Bob','M', 7)
enrool('Amda', 'M', city='Tianjin')
]

默认参数需正确使用,否则也会出错

def add_ed(L=[]):
    L.append('END')
    return L

正常调用时,结果似乎不错:

>>>add_end([1, 2, 3])
[1, 2, 3, 'END']
>>>add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

使用默认参数调用时,一开始结果也是正确的:

>>>add_end()
['END']

再次调用时就不对了

>>>add_end()
['END','END']
>>> add_end()
['END','END','END']

出错的原因在于函数每次都记住了上次添加的'END'后的list,此时默认的参数L是个变了,它的指向对象[],每次调用该函数时,改变了L的内容,下次调用时默认参数的内容就变了,不再是函数定义的[]了
为解决这个问题,可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

之后再调用就不会出问题:

add_end()
['END']
>>> add.end()
['END']

回到之前的疑问,为什么要设计str、None这样的不变对象,是因为它们一旦创建就不能修改,减少了由于修改数据导致的错误。能设计不变对象,就尽量设计成不变对象。

可变参数

定义:输入的参数是可变的,可以从1个,2个到任意。

def calc(number):
    sum = 0
    for n in number:
        sum = sum + n * n 
    return sum

调用时需要先组装一个list或tuple:
比如想要计算平方和,编写以下代码:

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

如果利用可变参数,调用函数可以简化为:

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

把函数的参数改为可变参数:

def calc(*number):
    sum = 0
    for n in numbers:
        sum = sum + n * n 
    return sum

与定义一个list或tuple相比,定义可变参数致使在参数number前加了一个*号。在函数内部,参数numbers接收的事一个tuple,因此函数代码完全不变,在调用的时候,可以传入任意个参数,包括0个参数:

>>> calc(1, 2)
5
>>> calc()
0

如果已经有一个list或tuple,调用一个可变参数也可以如此。但是做法过于繁琐,Python允许在list或tuple前面加一个*号,把list和tuple元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

关键字参数

定义:关键字参数允许传入0个或任意个参数名的参数,并且在函数内部自动组装为dict
(此处对比可变参数,是将可变参数在函数调用时自动组装为一个tuple)

def person(name, age , **kw):
    print('name:', name, 'age', 'other:', kw)

person 除了必选参数name 和 age外,还接受关键字参数kw,在调用该函数时,可以只传入必选参数:

>>> person('Michael', 30)
name: Michael age: 30 other: {}

也可以传入任意个数的关键字参数:

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数的意义在于扩展函数功能,在person函数中,可以保证接收到name和age两个参数,但是如果调用者愿意提供更多的参数,也可以接收到。
先组装一个dict,然后把dict转换成关键字参数传进去:

>>> extra = {'city:' 'Beijing', 'job:' 'Engineer'}
>>> person('Jack', 24 city=extra['city'], job= extra['job'])
name: Jack age: 24 other: {'city': 'Berijing', 'job': 'Engineer'}

可以简化的写法:

>>> extra = {'city': 'Beijing', 'job:' 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other:{'city': 'Beijing', 'job':'Engineer'}

注:'extra'表示extra这个dict的所有key-value用关键字参数传入函数的'kw',kw将获得一个dict,此时获得的dict是extra的一个拷贝份,对kw的改动不会影响到函数外的extra

命名关键字参数

作用:函数调用者可以传入不受限制的关键字参数,但是到底传入了那些,就需要在函数内部通过kw检查。
以person()函数为例,希望检查是否有city和job两个参数:

def person(name, age, **kw):
    if 'city' in kw:
       # 有city参数
       pass
    if 'job' in kw:
       # 有job参数
       pass
    print('name:', name, 'age:', age, 'other:', kw)

调用者可以传入不收限制的关键字参数:

>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)

如果要限制关键字参数的名字,就可以用命名关键词参数,如只接收city和job,可以定义如下的函数:

def person(name, age, *, city, job):
    print(name, age, city, job)

与关键字参数*kw不同,命名关键参数只需要一个特殊的分隔符后面的参数被视为命名关键字参数。
调用方式如下:

>>>person('Jack', 24, city='Beijing', job='Enginner')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不要特殊分隔符。

def person(name, age, *args, city, job):
    print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同,如果没有传入参数名,调用会出错。

>>> 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个位置参数。
命名关键字参数可以缺省值,从而简化调用:

def person(name, age, *, city='Berijing', job):
    print(name, age, city, job)

由于命名关键字参数city具有默认值,调用时,可不传入city参数:

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

注:使用命名关键字参数时,如果没有可变参数,必须加一个*作为特殊分隔符,如果缺少分隔符,Python解释器将无法是被位置参数和命名关键字参数。

参数组合

参数定义的顺序:必选参数、默认参数、可变参数、命名关键字参数、关键字参数
比如定义一个函数,包括上述若干参数:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b=', 'c=', c,'args =',args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
    print('a=', a,'b=', b, 'c =', c, 'd =', d, 'kw =', kw)

调用时,Python解释器会自动按照参数位置和参数名把对应的参数传进去。

>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

还可以通过一个tuple和dict来调用上述函数:

>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x':'#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args =(4,) kw = {'d':99, 'x': '#'}
>>> args = (1, 2, 3)
>>> kw = {'d':88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d =88 kw = {'x':'#'}

递归函数

定义:如果一个函数在内部调用自身,这个函数就是递归函数。
计算阶乘:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)
>>> fact(1)
1
>>> fact(5)
120

解决递归调用用栈溢出的方法是通过尾递归优化,实际上与循环效果一样。
尾递归是指:在函数返回时,指调用自己。

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。