delishcomcn

Python 函数

函数


当编写的代码出现有规律的重复时,这个时候就要考虑定义函数,将这些代码提取定义成一个函数,方便调用。

Python 提供许多内置函数,可以根据需要调用相应的函数实现想要的功能。同样 Python 也能够灵活地自定义函数。

调用函数


介绍如何定义函数前,先讲下如何调用函数。

Python 提供许多内置函数,这些函数都是可以直接调用的。包括前面的篇幅,其实也有调用函数实现一些功能。这里大致讲下函数的调用。

举个例子,求绝对值的函数 abs(),这个函数只接受一个参数。函数的使用可以查看官方文档:

也可以在交互式命令行下通过 help(abs) 来查看 abs 函数的帮助信息。

现在尝试调用 abs() 函数:

>>> abs(-1)
1
>>> abs(1)
1

因为 abs 只接受一个参数,若是传入的参数个数不为 1,会报 TypeError 的错误,信息中也会给出错误的提示:

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

这里表示,abs() 函数只接受一个参数,但是却提供了两个参数。

abs() 的参数接收的是数值,若是传入的类型错误,同样也会报错:

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

这里给出的错误提示表示 str 不是可接受的类型。

这就是函数调用,同时也需要注意的一些地方。要了解函数调用需要的几个参数,以及参数的类型。

定义函数


现在讲一下函数的定义,函数定义要使用 def 语句,后面紧跟的是函数名,括号,包含在括号内的参数以及冒号,然后在缩进块编写函数体,如果有返回值的话,则使用 return 语句返回。

还是以求绝对值为例,这里自定义函数实现:

def my_abs(num):
    if num >= 0:
        return num
    else:
        return -num

当执行 return 时,函数即为执行完毕,直接返回结果。例子中,简单使用了条件判断,函数内部可以通过使用条件判断以及循环,实现非常复杂的逻辑。

有些函数并没有 return 语句。但是函数执行完毕后同样会返回结果,只是结果为 None。所以 return None,也可以写为 return

现在我们尝试调用自己编写的函数 my_abs:

>>> my_abs(1)
1
>>> my_abs(-1)
1
>>> my_abs('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in my_abs
TypeError: '>=' not supported between instances of 'str' and 'int'

这里会看到当参数接受字符串的时候,出现的错误与 Python 提供的 abs 出错信息不一样。

这里我们添加对参数的检查,只允许整数和浮点数类型的参数。可以用 isinstance() 函数实现数据类型检查。

>>> def my_abs(num):
...     if not isinstance(num, (int, float)):
...         raise TypeError("bad operand type")
...     if num >= 0:
...         return num
...     else:
...         return -num
...

再次尝试调用该函数:

>>> my_abs('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in my_abs
TypeError: bad operand type

这样,看起来就更像 abs() 函数。

pass 语句

有时候,可能并没有想好要编写怎样的函数,但是想先定义,在后续补充代码。这个时候,pass 语句就派上用场。

pass 语句能够做占位符使用,可以在函数体中先将这个语句放进来,让编写的其他代码运行起来。等到后续要补充的时候再去掉 pass 语句进行修改。例如:

def no_idea():
    pass

这里如果函数体内没有这个 pass 语句,运行就会提示有语法错误。

返回多个值

上面提及的自定义函数,包括带 pass 语句的空函数(返回的是 None),返回的是一个值。

为了可以返回多个值,可以直接返回一个元组,例如:

>>> def my_func():
...     return 1, 2, 3
...
>>> a, b, c = my_func()
>>> a
1
>>> b
2
>>> c
3

虽然上面的函数看上去是返回了多个值,但实际上是先创建一个元组然后返回的。

其实我们使用的是逗号来生成一个元组,而不是用括号。例如:

>>> a = (1,2) # 带括号
>>> a
(1, 2)
>>> b = 1,2 # 不带括号
>>> b
(1, 2)

所以,这前面定义返回多值的函数,其实返回的是元组。若是赋值给单个变量,那么这个变量其实就是返回的那个元组本身:

>>> d = my_func()
>>> d
(1, 2, 3)

函数参数


位置参数

先定义一个函数:

>>> def func(a, b):
...     print('a = {},b = {}'.format(a, b))
...
>>> func(1,2)
a = 1,b = 2

在这里,就是 a 和 b,两个参数都是位置参数,调用的时候,依次将两个值按照顺序依次赋值给参数 a 和 b

默认参数

延用上面的例子,在这里,我们调用的时候只赋值给 a:

>>> func(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'b'

这里错误提示:调用参数缺少一个位置参数 b

这个时候,就可以考虑使用默认参数:

>>> def func(a, b=2):
...     print('a = {}, b = {}'.format(a, b))
...
>>> func(1)
a = 1, b = 2

可以看到当调用 func(1) 时,就相当于调用了 func(1, 2)

这里有些地方需要注意:

  • 必选的参数一定要放在默认参数前面,否则会报错。
  • 默认参数必须指向不变对象

第一点比较好理解,如果默认参数在必选参数前面,由于传入的值会按照位置给参数赋值。例如下面的错误示例(下面这种写法仅做错误示范,不建议如此编写。当然这样编写也会报错):

def func(a=1, b):
    return a, b

假设我们调用的时候,以这样的逻辑想,现在 a 有默认值,只给 b 赋值就可以了。但是赋值会按照位置赋值。比如调用 func(2),其实这里是将 2 赋值给了 a,替代了原来的 1,最终 b 还是缺少赋值。

第二点,必须指向不变对象,千万不能像下面这样编写代码:

def func(a, b=[]):
    ....

这样写的话,后面会遇到很多的麻烦。b 作为可修改的对象,这些修改会影响到下次调用这个函数的默认值。比如:

>>> def func(a, b=[]):
...     print(b)
...     return b
...
>>> x = func(1)
[]
>>> x.append(2)
>>> x.append('wow!')
>>> x
[2, 'wow!']
>>> func(1)
[2, 'wow!']

可以看到当再次调用 func(1) 的时候,结果已经变了,这个并不是原来的初衷。

如果真的需要使用列表,最好先使用 None,然后在函数里面用相应的逻辑检查。

当然测试 None 的时候要需要注意,使用 is 操作符是非常重要的。

def func(a, b=None):
    if not b: # 不建议这样写,建议用 b is None:代替
        b = []

上面不建议使用 if not b: ,如果这样的话,None 会被当成是 False,而长度为 0 的字符串,列表,元组,字典等都可能被当成 False。这一点也要注意。

可变参数

上面提及的函数都是固定数量的位置参数,为了实现一个能接受任意数量的位置参数的函数,可以使用一个 * 参数。例如:

>>> def func(*nums):
...     sum = 0
...     for num in nums:
...         sum += num
...     return sum
...

>>> func(1,2)
3
>>> func(1,2,3)
6

这样就实现了可以传入任意数量的参数的位置参数。

关键字参数

若是要实现接受任意数量的关键字参数,可以使用 ** 开头的参数。比如:

>>> def info(name, age, **kw):
...     print('name:', name, 'age:', age, 'other:', kw)
...
>>> info('大梦三千秋', 0)
name: 大梦三千秋 age: 0 other: {} 
>>> info('大梦三千秋', 0, city='Guangzhou')
name: 大梦三千秋 age: 0 other: {'city': 'Guangzhou'}
>>> info('大梦三千秋', 0, city='Guangzhou', gender='male')
name: 大梦三千秋 age: 0 other: {'city': 'Guangzhou', 'gender': 'male'}

这样就实现了传入任意个数的关键字参数。这里,关键字参数允许传入 0 个或任意个参数名的参数,函数内部会自动组成一个 dict,如上示例。

命名关键字参数

命名关键字参数,其实是将关键字参数放到某个 * 参数后面或者单个 * 后面,例如:

>>> def info(name, age, *, city):
...     print(name, age, city)
...
>>> info('大梦三千秋', 0, city='Guangzhou')
大梦三千秋 0 Guangzhou

这里注意调用方式,一定要传入参数名,若是没有传入参数名,则会报错:

>>> info('大梦三千秋', 0, 'Guangzhou')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: info() takes 2 positional arguments but 3 were given

上面的提示中说只需要 2 个位置参数但是传入的参数有 3 个这时因为缺少参数名 city,解释器将 3 个参数都视为位置参数,但 info 函数只接受 2 个位置参数。

参数顺序

这里提及到的参数有位置(必选)参数、默认参数、可变参数、关键字参数和命名关键字参数。但是需要注意参数的定义顺序:

必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

虽然上面的参数都可以组合在一起,但是不建议这样做,这样会导致可理解性变差。

posted on 2023-10-28 09:53  delish  阅读(9)  评论(0编辑  收藏  举报

导航