# 函数方法

2023年8月03日11分钟阅读

- FormalSciences/ComputerScience
- ProgrammingLanguage/Python
- Python/Basics

## 函数

函数是通过白盒/黑盒封装多行代码的实现方式，通常具有输入和输出，目的是为了简化代码、重复调用和模块化编程。

在 Python 中，`def` 关键字用于定义函数，每个函数都具有一个唯一的名称，其命名规则与变量命名规则相同。函数体的第一条语句可以是一个字符串，该字符串被称为文档字符串或 docstring，用于提供关于函数的简要描述。

    # 语法
    def 函数名(参数列表):
        函数体

    # 实例
    def fib(n):
        """输出限定数值内的斐波那契数列函数"""
        a, b = 0, 1
        while a < n:
            print(a, end=' ')
            a, b = b, a+b
        print()

### 参数传递

python 中类型属于对象，变量是没有类型的。**python 中一切都是对象，严格意义我们不能说值传递还是引用传递，我们应该说传不可变对象和传可变对象。**而不可变对象和可变对象的区别在于：不可变对象的值不可以改变，而可变对象的值可以改变。

#### 可更改与不可更改对象

在 python 中 strings,、tuples 和 numbers 是不可更改的对象，而 list、dict 等则是可以修改的对象。

- **不可变类型：**变量赋值 a=5 后再赋值 a=10，这里实际是新生成一个 int 值对象 10，再让 a 指向它，而 5 被丢弃，不是改变 a 的值，相当于新生成了 a。
- **可变类型：**变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改，本身 la 没有动，只是其内部的一部分值被修改了。

#### 参数传递

- **不可变类型：**类似 C++ 的值传递，如整数、字符串、元组。如 fun(a)，传递的只是 a 的值，没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值，则是新生成一个 a 的对象。
- **可变类型：**类似 C++ 的引用传递，如 列表，字典。如 fun(la)，则是将 la 真正的传过去，修改后 fun 外部的 la 也会受影响

### 默认值参数

在 Python 中，函数的参数可以有默认值，也支持使用可变参数，所以 Python 并不需要像其他语言一样支持函数的重载，因为我们在定义一个函数的时候可以让它有多种不同的使用方式。

    def add(a=0, b=0, c=0):
        """三个数相加"""
        return a + b + c
    add(1,2,3)
    # 6

### 键值参数

`kwarg=value` 形式的 关键字参数 也可以用于调用函数。函数示例如下：

该函数接受一个必选参数（`voltage`）和三个可选参数（`state`, `action` 和 `type`）。

    def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
        print("-- This parrot wouldn't", action, end=' ')
        print("if you put", voltage, "volts through it.")
        print("-- Lovely plumage, the", type)
        print("-- It's", state, "!")

    parrot("halo",type="test")

    # -- This parrot wouldn't voom if you put halo volts through it.
    # -- Lovely plumage, the test
    # -- It's a stiff !

### 特殊参数

#### 可变参数 *

在参数名前面的 * 表示 args 是一个可变参数，可以输入多个参数。

    def add2(*args):
        total = 0
        for val in args:
            total += val
        print(total)

    add2(1,2,3)
    # 6

#### 键值参数 **

在参数名前面的 ** 表示 args 是一个可变参数，可以输入键值对。

    def add2(**arg):
        print(arg)

    add2(name="halo")
    # {'name': 'halo'}

#### 限位置参数 /

`/` 必须放在形参后面表示限制位置参数，实参必须按照形参位置输入。

    def pos_only_arg(arg, /):
        print(arg)

#### 限关键字参数 *

`*` 必须放在形参前面表示限关键字参数，实参必须按键值参数输入。

    def kwd_only_arg(*, arg):
        print(arg)

#### 特殊参数组合

    def combined_example(pos_only, /, standard, *, kwd_only):
        print(pos_only, standard, kwd_only)

    """
    运行示例
    """
    >>> combined_example(1, 2, 3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: combined_example() takes 2 positional arguments but 3 were given

    >>> combined_example(1, 2, kwd_only=3)
    1 2 3

    >>> combined_example(1, standard=2, kwd_only=3)
    1 2 3

    >>> combined_example(pos_only=1, standard=2, kwd_only=3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

### Return

return [表达式] 语句用于退出函数，选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。

    def sum( arg1, arg2 ):
       # 返回2个参数的和."
       total = arg1 + arg2
       print ("函数内 : ", total)
       return total

    # 调用sum函数
    total = sum( 10, 20 )
    print ("函数外 : ", total)

## 全局、局部变量

在 Python 中，变量的作用范围可分为全局变量和局部变量。

- **全局变量：**在函数体外部声明的变量被称为全局变量。全局变量在程序的整个生命周期内都是可访问的。
- **局部变量：**在函数体内部声明的变量被称为局部变量。它们只能在声明它们的函数内部被访问。

    x = 10  # 这是一个全局变量

    def func():
        y = 5  # 这是一个局部变量
        print(y)  # 输出：5

    func()
    print(x)  # 输出：10
    print(y)  # 将会引发一个错误，因为 y 在这个作用域内不存在

## global、nonlocal

当需要在函数或其他作用域内部改变全局变量或嵌套作用域中的变量时，我们需要使用 `global` 或 `nonlocal` 关键字。

- global 关键字： 允许你在函数或其他局部作用域内部修改全局变量。
- nonlocal 关键字： 允许你在嵌套的函数（即闭包）中修改上一层非全局作用域的变量。

    # 使用 global 关键字：
    x = 10

    def func():
        global x
        x = 20  # 修改全局变量 x

    func()
    print(x)  # 输出：20

    # 使用 nonlocal 关键字：
    def outer():
        x = 10
        def inner():
            nonlocal x
            x = 20  # 修改上一层函数中的 x

        inner()
        print(x)  # 输出：20

    outer()

## Yield

`yield` 是 Python 中用于创建生成器（generator）的关键字。一个包含 `yield` 表达式的函数被称为一个生成器函数。这种函数在被调用时不会立即执行，而是返回一个迭代器，这个迭代器可以在其元素需要被处理时生成它们。这种延迟生成元素的方式使得生成器在处理大数据集或无限序列时非常有用，因为它们不需要一次性生成所有元素，从而节省内存。

生成器函数在执行到 `yield` 表达式时会暂停并保存当前所有的状态信息（包括局部变量等），在下次从该函数获取下一个元素（即进行下一次迭代）时，它会从保存的状态和位置继续执行。

    # yield a 语句会暂停函数的执行并返回当前的 a 值作为序列的下一个元素。
    # 在下次迭代时，函数会从 yield a 语句后的语句继续执行，计算出序列的下一个值。
    def fibonacci():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b

    # zip(range(10), fibonacci()) 部分会生成一个迭代器
    # 这个迭代器在每次迭代时都会返回斐波那契数列的下一个数字，直到生成了前 10 个数字为止。
    for _, val in zip(range(10), fibonacci()):
        print(val)

## Lambda

lambda 关键字用于创建小巧的匿名函数。Lambda 函数可用于任何需要函数对象的地方。在语法上，匿名函数只能是单个表达式。在语义上，它只是常规函数定义的语法糖。与嵌套函数定义一样，lambda 函数可以引用包含作用域中的变量：

- lambda 只是一个表达式，函数体比 def 简单很多。
- lambda 的主体是一个表达式，而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间，且不能访问自己参数列表之外或全局命名空间里的参数。
- 虽然 lambda 函数看起来只能写一行，却不等同于 C 或 C++ 的内联函数，后者的目的是调用小函数时不占用栈内存从而增加运行效率。

    # 语法
    lambda arguments: expression

    # lambda 实例
    double = lambda x: x * 2

    # 常规函数
    def double(x):
        return x * 2

    print(double(5))  # 输出：10