1.2.4. 定义函数

1.2.4.1. 函数定义

In [1]: def test():
...: print('in test function')
...:
...:
In [2]: test()
in test function

警告

函数块必须像其他控制流块一样缩进。

1.2.4.2. 返回值语句

函数可以可选地返回值。

In [3]: def disk_area(radius):
...: return 3.14 * radius * radius
...:
In [4]: disk_area(1.5)
Out[4]: 7.0649999999999995

注意

默认情况下,函数返回 None

注意

注意定义函数的语法

  • def 关键字;

  • 后面跟着函数的**名称**,然后

  • 函数的参数用括号括起来,后面跟着一个冒号。

  • 函数体;

  • 以及 return object 用于可选地返回值。

1.2.4.3. 参数

必填参数(位置参数)

In [5]: def double_it(x):
...: return x * 2
...:
In [6]: double_it(3)
Out[6]: 6
In [7]: double_it()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[7], line 1
----> 1 double_it()
TypeError: double_it() missing 1 required positional argument: 'x'

可选参数(关键字或命名参数)

In [8]: def double_it(x=2):
...: return x * 2
...:
In [9]: double_it()
Out[9]: 4
In [10]: double_it(3)
Out[10]: 6

关键字参数允许您指定默认值

警告

默认值在定义函数时计算,而不是在调用函数时计算。当使用可变类型(例如字典或列表)并在函数体中修改它们时,这可能会出现问题,因为修改将在函数的多次调用中保持不变。

在关键字参数中使用不可变类型

In [11]: bigx = 10
In [12]: def double_it(x=bigx):
....: return x * 2
....:
In [13]: bigx = 1e9 # Now really big
In [14]: double_it()
Out[14]: 20

在关键字参数中使用可变类型(并在函数体中修改它)

In [15]: def add_to_dict(args={'a': 1, 'b': 2}):
....: for i in args.keys():
....: args[i] += 1
....: print(args)
....:
In [16]: add_to_dict
Out[16]: <function __main__.add_to_dict(args={'a': 1, 'b': 2})>
In [17]: add_to_dict()
{'a': 2, 'b': 3}
In [18]: add_to_dict()
{'a': 3, 'b': 4}
In [19]: add_to_dict()
{'a': 4, 'b': 5}

提示

更复杂的示例实现了 Python 的切片

In [20]: def slicer(seq, start=None, stop=None, step=None):
....: """Implement basic python slicing."""
....: return seq[start:stop:step]
....:
In [21]: rhyme = 'one fish, two fish, red fish, blue fish'.split()
In [22]: rhyme
Out[22]: ['one', 'fish,', 'two', 'fish,', 'red', 'fish,', 'blue', 'fish']
In [23]: slicer(rhyme)
Out[23]: ['one', 'fish,', 'two', 'fish,', 'red', 'fish,', 'blue', 'fish']
In [24]: slicer(rhyme, step=2)
Out[24]: ['one', 'two', 'red', 'blue']
In [25]: slicer(rhyme, 1, step=2)
Out[25]: ['fish,', 'fish,', 'fish,', 'fish']
In [26]: slicer(rhyme, start=1, stop=4, step=2)
Out[26]: ['fish,', 'fish,']

关键字参数的顺序无关紧要

In [27]: slicer(rhyme, step=2, start=1, stop=4)
Out[27]: ['fish,', 'fish,']

但最好使用与函数定义相同的顺序。

关键字参数是定义具有可变数量参数的函数的一个非常方便的功能,尤其是在大多数对函数的调用中都使用默认值时。

1.2.4.4. 按值传递

提示

您可以在函数内部修改变量的值吗?大多数语言(C、Java 等)区分“按值传递”和“按引用传递”。在 Python 中,这种区别有点人为,并且变量是否会被修改有点微妙。幸运的是,存在明确的规则。

函数的参数是对对象的引用,这些引用是按值传递的。当您将变量传递给函数时,python 会将变量引用的对象的引用(**值**)传递给函数。而不是变量本身。

如果传递给函数的**值**是不可变的,则函数不会修改调用者的变量。如果**值**是可变的,则函数可能会就地修改调用者的变量

>>> def try_to_modify(x, y, z):
... x = 23
... y.append(42)
... z = [99] # new reference
... print(x)
... print(y)
... print(z)
...
>>> a = 77 # immutable variable
>>> b = [99] # mutable variable
>>> c = [28]
>>> try_to_modify(a, b, c)
23
[99, 42]
[99]
>>> print(a)
77
>>> print(b)
[99, 42]
>>> print(c)
[28]

函数有一个名为局部命名空间的局部变量表。

变量 x 仅在函数 try_to_modify 内存在。

1.2.4.5. 全局变量

在函数外部声明的变量可以在函数内部引用

In [28]: x = 5
In [29]: def addx(y):
....: return x + y
....:
In [30]: addx(10)
Out[30]: 15

但是这些“全局”变量不能在函数内部修改,除非在函数中声明为**全局**。

这不起作用

In [31]: def setx(y):
....: x = y
....: print('x is %d' % x)
....:
....:
In [32]: setx(10)
x is 10
In [33]: x
Out[33]: 5

这有效

In [34]: def setx(y):
....: global x
....: x = y
....: print('x is %d' % x)
....:
....:
In [35]: setx(10)
x is 10
In [36]: x
Out[36]: 10

1.2.4.6. 可变数量的参数

参数的特殊形式
  • *args:打包到元组中的任意数量的位置参数

  • **kwargs:打包到字典中的任意数量的关键字参数

In [37]: def variable_args(*args, **kwargs):
....: print('args is', args)
....: print('kwargs is', kwargs)
....:
In [38]: variable_args('one', 'two', x=1, y=2, z=3)
args is ('one', 'two')
kwargs is {'x': 1, 'y': 2, 'z': 3}

1.2.4.7. 文档字符串

关于函数的功能及其参数的文档。一般约定

In [39]: def funcname(params):
....: """Concise one-line sentence describing the function.
....:
....: Extended summary which can contain multiple paragraphs.
....: """
....: # function body
....: pass
....:
In [40]: funcname?
Signature: funcname(params)
Docstring:
Concise one-line sentence describing the function.
Extended summary which can contain multiple paragraphs.
File: ~/src/scientific-python-lectures/<ipython-input-13-64e466df6d64>
Type: function

注意

文档字符串指南

为了标准化,文档字符串约定网页记录了与 Python 文档字符串相关的语义和约定。

此外,NumPy 和 SciPy 模块已为科学函数的文档定义了一个精确的标准,您可能希望将其应用于您自己的函数,包括 Parameters 部分、Examples 部分等。请参阅 https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard

1.2.4.8. 函数是对象

函数是一等公民,这意味着它们可以
  • 赋值给变量

  • 列表(或任何集合)中的一个项目

  • 作为参数传递给另一个函数。

In [41]: va = variable_args
In [42]: va('three', x=1, y=2)
args is ('three',)
kwargs is {'x': 1, 'y': 2}

1.2.4.9. 方法

方法是附加到对象上的函数。您已经在我们关于列表字典字符串等的示例中看到了这些。

1.2.4.10. 练习

function quicksort(array)
var list less, greater
if length(array) < 2
return array
select and remove a pivot value pivot from array
for each x in array
if x < pivot + 1 then append x to less
else append x to greater
return concatenate(quicksort(less), pivot, quicksort(greater))