跳转至

函数

对于变量,赋值完成后,修改赋值语句中其他变量的值,这个变量的值不会因为别的变量改变而自动变化;对于函数,同一个函数 f() ,因为它内部用到了外部变量 a、b 等等,改变这些变量后,再调用函数,返回值就可能变。

评估(Evaluation)

Python 解释器读取一段代码,进行计算,并最终得出一个“结果对象”的过程。内存中制造出了一个“函数对象”,但代码没有运行。

内部名称

当定义 def square(x): ... 时,Python 实际上做了两件事:在内存中创建一个函数对象,它的名字叫 square;在当前的环境中创建一个变量名 square,并指向这个函数对象。可以通过函数的 .__name__ 属性看到这个内部名称:

print(square.__name__)  # 输出: "square"

重名

有重名时,先检查本地(局部)帧(框架),再检查全局帧(框架)。例如:

def square(square):
    return square * square

x=square(3)  # 返回 9,这里把3传给了局部变量square,再调用square()函数

当一个内置函数名称与变量名称冲突时,优先使用变量。例如:

def max(x, y):
    if x > y:
        return x
    return y

max = 42
print(max(1, 2))  # 输出 42

内置函数可以作为另一个内置函数的变量名。例如:max=min,调用max(1,2)时会调用min函数。

当后面定义了一个与前面函数同名的函数时,优先使用后定义的函数。

多重返回值

return语句可以返回多个值。例如:

>>> def divide_exact(n, d):
...    return n // d, n % d
>>> quotient, remainder = divide_exact(2013, 10)

高阶函数:接受另一个函数作为参数的函数

函数作为形参

在具有多个形参的函数A中,某些形参可能是函数B。在调用函数A时,一定会自动执行函数B。例如:

def if_(c, t, f):
    if c:
        return t
    else:
        return f

from math import sqrt

def real_sqrt(x):
    """Return the real part of the square root of x."""
    return if_(x >= 0, sqrt(x), 0)

在执行return if_(x >= 0, sqrt(x), 0)时,函数if_会被调用,并且参数t会被求值为sqrt(x)。这意味着如果x是负数,sqrt(x)仍然会被计算,从而导致报错。

函数作为返回值

def make_adder(n):
    """Return a function that takes one argument k and returns k + n."""
    def adder(k):
        return k + n
    return adder

想计算4 + 3,可以调用make_adder(3)(4)

adder的父级是make_addermake_adder的父级是全局帧。

闭包

通常情况下,一个函数运行结束,它内部的局部变量就会被销毁。但在闭包下,即使外部函数已经执行完了,内部函数依然能记住并访问外部函数里的变量。

要形成一个闭包,必须满足以下三点:

  1. 必须有嵌套函数(函数里面定义函数)。
  2. 内部函数必须引用外部函数的变量
  3. 外部函数必须返回内部函数

例如:

def delay(arg):
    def g():
        return arg  # g 引用了外部的 arg,形成了闭包
    return g

当执行 f = delay(6) 时:

  1. delay 执行完毕。
  2. 返回的函数 f(也就是 g)其实随身携带了参数 6
  3. 所以当执行 f() 时,返回 6

环境图

Frame:

func <name> (param) [parent= <parent>]
一些变量
return <value>

Object:

<type> object at <address>

Frame 代表函数调用时的环境,Object 代表内存中的对象。Frame中的变量会指向Object。

调用时,从子级向上,例如上面的代码从adder向上找到make_adder,再从make_adder向上找到全局帧。

当函数被调用时,会创建一个新的局部帧,绑定它的父级为定义该函数时所在的帧,传递参数(当前帧的变量),执行函数。

Lambda 函数

lambda函数是匿名函数,可以看作是高阶函数的特例。例如:square = lambda x: x * x,此时square被绑定为了x的平方这样一个函数。也可以直接用(lambda x:x * x)(3),直接调用lambda函数。

图片里写的是 Function Currying(函数柯里化)。这是函数式编程里的一个重要概念,我用直观 + 例子给你解释。

函数柯里化(单参数函数链)

把一个“接收多个参数的函数”,变成一连串每次只接收一个参数的函数

def add(a):
    def add(b):
        return a + b
    return add

add(2)(3)

装饰器

Python 装饰器(Decorator) 允许在不修改原有函数代码的情况下,给函数添加新的功能。

装饰器本质上是一个闭包,它接收一个函数作为参数,并返回一个新的函数。使用 @ 符号来使用装饰器。例如:

def my_decorator(func):
    def wrapper():
        print("--- 执行函数前:准备工作 ---")
        func()
        print("--- 执行函数后:清理工作 ---")
    return wrapper
@my_decorator
def say_hello():
    print("你好,世界!")
say_hello()

当使用 @my_decorator 作用在 say_hello 函数上时,Python 实际上执行了以下操作:say_hello = my_decorator(say_hello),原有的 say_hello 函数被替换成了 wrapper 函数。当调用 say_hello() 时,实际上是在执行 wrapper()

递归

交叉递归

交叉递归是指两个或多个函数相互调用对方来实现递归的过程。例如,计算一个数是否为偶数或奇数:

def is_even(n):
    if n == 0:
        return True
    else:
        return is_odd(n - 1)
def is_odd(n):
    if n == 0:
        return False
    else:
        return is_even(n - 1)

树形递归

树形递归是指一个函数在执行过程中会多次调用自身,从而形成树状的调用结构。例如,计算斐波那契数列:

def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

匿名函数递归

lambda f: (lambda x: f(f, x))

这个表达式是一个函数lambda_f,这个函数会返回一个把自己的参数作为其中一个参数的函数,它相当于:

def lambda_f(f):
    def lambda_x(x):
        return f(f, x)
    return lambda_x

这个技巧可以用来实现匿名函数的递归调用。例如,计算阶乘:

(lambda x: f(f, x))(lambda f, n: 1 if n == 1 else (n*f(f, n-1)))

后半段相当于:

def factorial(f, n):
    if n == 1:
        return 1
    else:
        return n * f(f, n - 1)

当使用lambda_f(factorial)(n)时,它会先返回lambda_x,然后运行lambda_x(n),即factorial(factorial, n)从而使得参数f始终为factorial函数本身,实现递归调用。