022、函数进阶
本文最后更新于 68 天前,其中的信息可能已经过时,如有错误请发送邮件到wuxianglongblog@163.com

函数进阶

函数的参数传递

Python的函数采用了引用传递的方法,即传递参数时,并不复制一份参数的内容,而是将参数的引用传递给函数。

例如:

def f(x):
    return id(x)
a = 1.2
id(a)
4485901776
f(a)
4485901776

函数f(a)的返回值与a的内存地址是一致的。这表示,当函数f()被调用时,Python并没有将a的值复制一份传给参数x,而是让参数x与a共享了同一块内存。所以a和x是同一个对象。

对于列表有类似的结论:

b = [1, 2, 3]
f(b)
4486278400
id(b)
4486278400

共享同一个对象的机制意味着,可以在函数中修改传入参数的值。例如:

def mod_f(x):
    x[0] = 999
    return x
b
[1, 2, 3]
mod_f(b)
[999, 2, 3]
b
[999, 2, 3]

不过,如果在函数中给参数x赋了一个新值,如另一个列表,根据赋值机制,虽然x指向一个新的内存位置,但原来的变量不会改变:

def no_mod_f(x):
    x = [4, 5, 6]
    return x
no_mod_f(b)
[4, 5, 6]
b
[999, 2, 3]

默认参数的传递

有默认参数的情况下,Python会在函数定义时,预先为默认参数分配内存,以避免每次生成一个额外的默认参数。这样做能够节约一定的空间,不过也可能会得到一些与直觉不符的结果。例如:

def f(x = []):
    x.append(1)
    return x

理论上说,调用 f() 时返回的是 [1], 但事实上:

f()
[1]
f()
[1, 1]
f()
[1, 1, 1]

随着函数的调用,默认参数会被一直改变。这并不是Python的bug,且这种特性可以用于缓存。如果不想要这样的特性,可以这样定义函数:

def f(x = None):
    if not x:
        x = []    
    x.append(1)
    return x
f()
[1]
f()
[1]

高阶函数

以函数作为参数,或者返回一个函数的函数都是高阶函数。在Python中,函数也是一种基本类型的对象,例如:

max
type(max)
builtin_function_or_method

对象性意味着可以对函数进行以下操作:

  • 将函数作为参数传给另一个函数。
  • 将函数名赋值给另一个变量。
  • 将函数作为另一个函数的返回值。

例如:

def square(x):
    """Square of x."""
    return x*x

def cube(x):
    """Cube of x."""
    return x*x*x

可以将它们作为字典的值:

funcs = {
    'square': square,
    'cube': cube,
}

调用这些函数:

funcs['square'](3)
9

常见的高阶内置函数有 map()filter()

map(f, sq)

将函数f作用在序列sq的每个元素上,得到结果组成的新序列。例如

map(square, range(5))

返回的结果是一个map迭代器,可以将其转换为列表:

list(map(square, range(5)))
[0, 1, 4, 9, 16]
filter(f, sq)

将函数f作用在序列sq的每个元素上,保留所有结果为True的元素。例如:

def is_even(x):
    return x % 2 == 0
filter(is_even, range(5))
list(filter(is_even, range(5)))
[0, 2, 4]

函数的返回值也可以是个函数。例如,定义一个返回函数的函数:

def power_func(num):
    def func(x):
        return x ** num
    return func

平方和立方函数可以使用该函数定义出来:

square2 = power_func(2)
cube2 = power_func(3)
type(square2)
function
square2(10)
100
cube2(3)
27

Lambda表达式

Python提供了Lambda表达式,简化函数的定义,来定义一些匿名函数。

lambda <variables>: <expression>

Lambda表达式返回的是一个函数对象,接受<variables>作为参数,返回<expression>

square3 = lambda x: x ** 2
cube3 = lambda x: x ** 3
type(square3)
function
square3(10)
100
cube3(3)
27

关键字global

在Python中,函数可以直接使用外部已定义好的变量值:

x = 15

def print_x():
    print(x)
print_x()
15

如果想在函数中直接改x的值,需要加上关键字global

x = 15

def print_newx():
    global x
    x = 18
    print(x)
print_newx()
18
x
18

如果不加,x的值不会改变:

x = 15

def print_newx():
    x = 18
    print(x)
print_newx()
18
x
15

递归

递归是指函数在执行的过程中调用了本身,一般用于分治法。例如,阶乘函数:

def fact(n):
    return 1 if n == 0 else n * fact(n-1)
fact(6)
720

递归可以更快地实现代码,不过在效率上可能会有一定的损失。例如,斐波那契数列:

def fib1(n):
    """Fib with recursion."""
    return 1 if n in (0, 1) else fib1(n-1) + fib1(n-2)
list(map(fib1, range(10)))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

非递归的方式的实现:

def fib2(n):
    """Fib without recursion."""
    a, b = 1, 1
    for i in range(1, n+1):
        a, b = b, a+b
    return a
list(map(fib2, range(10)))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

对这两个函数的运行时间进行比较:

%timeit -n 100 fib1(20)
2.98 ms ± 30.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit -n 100 fib2(20)
1.5 µs ± 20.1 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)

可以看到,两者的效率有很大的差别,非递归版本比递归版本要快很多,原因是递归版本中存在大量的重复计算。

为了减少递归中的重复,可以利用默认参数实现缓存机制:

def fib3(n, cache={0: 1, 1: 1}):
    """Fib with recursion and caching."""
    try:
        return cache[n]
    except KeyError:
        cache[n] = fib3(n-1) + fib3(n-2)
        return cache[n]
list(map(fib3, range(10)))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
%timeit -n 100 fib3(20)
158 ns ± 49.9 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)
谨此笔记,记录过往。凭君阅览,如能收益,莫大奢望。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇