Python-学习笔记:函数进阶篇

Python教程 - 廖雪峰的官方网站

变量与函数

变量可以指向函数:

abs # 这个变量其实是 abs()
print(type(abs))

<class ‘builtin_function_or_method’>

因此,函数名可以被当做参数传递,比如定义一个函数 computed(x, y, f),使得 xy 作为参数来调用 f 变量指向的函数。

  • 函数对象有一个__name__属性,可以拿到函数的名字
def computed(x, y, f):
    return f(x) + f(y)

print(computed(-2, -8, abs)) # 计算-2、-8的绝对值之和

10

例如 computed() 这种接受函数为变量的函数,称之为高阶函数。

高阶函数

map() 高阶函数

将一个函数与一个可迭代对象的所有元素创建联系,并作为一个 Iterator (迭代器)返回,等待执行。

  • 此时并没有执行这个函数,只有对迭代器进行迭代的时候才会执行函数。
result = map(abs, [-1, -2, 3]) # 得到 <class 'map'> 对象
print(list(result)) # 转为列表

[1, 2, 3]

  • result 是一个惰性对象,使用 list() 计算了所有的值并转为列表。

reduce() 高阶函数

它将一个函数作用于一个可迭代对象的所有元素,这个函数需要接收两个参数reduce() 会这样执行:取两个元素执行获得的结果,与下一个元素作为参数代入继续执行。获得结果再继续与下一个元素代入执行直至结束。

  • 此时已经执行函数了。

例如对一个序列合并组成数字:

from functools import reduce # 需要导入模块
def fn(x, y):
    return x *  10  + y

r = reduce(fn, [1, 2, 3])
print(r)

123

map()reduce() 使用范例:将字符串转为数字

from functools import reduce

DIGITS = {
    
    '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

filter() 高阶函数

map() 类似,但 filter() 是创建将函数用于过滤元素的联系,根据这个函数返回的布尔值选择是否保留元素。返回一个迭代器对象。

  • 返回惰性对象,并没有执行
# 判断一个数字反过来还是不是它
def is_palindrome(n):
    temp = n
    m =  0
    while temp >  0: # 有大于十位数的位数存在
        m = m *  10  + temp %  10  # 取个位数;下一次循环时,将个位数升级,
        temp = temp //  10  # 得到个位数以上的数字
    return n == m

print(list(filter(is_palindrome, range(1, 100)))) # 打印1~99的回数

sorted() 高阶函数

sorted(lista) 是排序一个列表,还可以传入关键字参数 key,它是一个函数,可以对应用了该函数后的元素来排序列表。

  • 比如 sorted(lista, key=abs),按照绝对值大小来排序列表
  • Python 对元素使用了 abs(),然后获得绝对值,内部继续按照原来的排序进行,但是排序这个元素时,看的是使用了 abs() 后的元素。
  • 这个函数要返回一个让 Python 来排序的值。

字符串列表原来的排序是根据 ASCII 码进行排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

使用 key 参数,使得它排序时忽略大小写:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
# 排序看的是:'bob', 'about', 'zoo', 'credit'
['about', 'bob', 'Credit', 'Zoo']

反向排序,可以传入第三个参数 reverse=True

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
  • 因此,我们可以自己写一个函数传入,比如对元素为元组的列表进行排序:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return t[0] # 返回名字,让 Python 内部根据名字排序

L2 = sorted(L, key=by_name)
# 按照 'Bob', 'Adam', 'Bart', 'Lisa' 进行排序
print(L2)

返回函数

高阶函数可以接收函数作为变量,也可以将函数作为返回值返回。

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1, 3, 5, 7, 9)
# 它是:<function lazy_sum.<locals>.sum at 0x101c6ed90>,是一个函数
f() # 执行函数
  • 这里 sum() 函数可以访问外部的 args,并且当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
  • 每次调用 lazy_sum() 都会返回一个新的函数,即使传入相同的参数它们也是不 == 的(不是说运行结果不等,是说函数不等)
  • 内部函数 sum() 引用的外部函数的变量称之为自由变量
    • 闭包中的引用的自由变量只和具体的闭包有关联,闭包的每个实例引用的自由变量互不干扰。
    • 一个闭包实例对其自由变量的修改会被传递到下一次该闭包实例的调用。
  • 闭包陷阱:
def my_func(*args):
    fs = []
    for i in range(03):
        def func():
            return i * i
        fs.append(func)
    return fs

fs1, fs2, fs3 = my_func()
print fs1()
print fs2()
print fs3()

程序的结果并不是我们想象的结果 0,1,4。实际结果全部是 4。
因为还没 return 返回函数i 还只是外部函数的一个普通变量(不是闭包函数的自由变量),在返回函数前 i 已经被赋值为 2 了,而 returni 才变成了自由变量。这时候已经都是 2 了。

  • 注意:返回函数不要引用任何循环变量,或者后续会发生变化的变量(在 return 前)。
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

闭包例子:

# 返回一个计数器函数,每次调用它返回递增整数,多个计数器函数之间互不干扰
def createCounter():
    n = 0
    def counter():
        nonlocal n
        n += 1
        return n
    return counter
  • 也就是说闭包就是返回一个函数,它独立拥有外部函数的变量实例。(类似于一个小对象)

装饰器

在代码运行阶段,动态的为某个已有函数添加功能。这个功能可以被许多函数选择使用。(java 装饰者模式)

  • 本质上是一个返回函数的高阶函数。

在面向对象(OOP)的设计模式中,decorator 被称为装饰模式。OOP 的装饰模式需要通过继承和组合来实现,而 Python 除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。Python 的 decorator 可以用函数实现,也可以用类实现。

以为 now() 函数添加打印日志功能 log() 为例:

  1. 在原函数前添加 @log
@log
def now():
    print("2020-11-23")

这个语法相当于:now = log(now)

  • 原函数变为执行装饰器函数,是函数变量的替换,所以后续装饰器函数要返回一个函数。
  1. now() 函数的上面书写定义装饰器函数:
def log(func): # func 为 now 函数!
    def wrapper(*args, **kw): # *args, **kw 接收任意类型的参数
        print(f'call {func.__name__}():')
        return func(*args, **kw)
    return wrapper

装饰器函数是要返回一个函数的,所以需要再内部定义一个函数用来返回。
实现调用前打印日志:在这个内部函数里完成我们需要添加的功能,然后返回时调用原函数 func


如果装饰器函数需要传入参数的话,需要嵌套返回一个 decorator 的高阶函数(也可以是其他名字):

# 装饰器函数
def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print(f'{text} {func.__name__}():')
            return func(*args, **kw)
        return wrapper
    return decorator

# 原函数
@log('execute')
def now():
    print('2020-11-23')

重要:要理解 @log('execute') 相当于:

now = log('execute')(now)
  • 首先执行 log('execute'),执行 log() 返回的是 decorator() 函数,再带参数 (now) 调用 decorator() 返回的是 wrapper() 函数。
  • 直观的看是,两个小括号从外到内执行了两个函数。

装饰完函数后,因为我们是通过 @ 语法将本要执行的函数换成了我们的装饰器函数,然后再在装饰器函数里调用我们的原函数,now = log(now) 已经被赋值,__name__ 变成了 wrapper

  • 功能上已经满足需求了,但是有些依赖函数签名的代码执行会出错。
  • import functools,在定义 wrapper() 上添加:@functools.wraps(func)
  • 这段代码相当于运行了:wrapper.__name__ = func.__name__,这样就完美了

偏函数

在局部区域,为函数设置默认值。
int(x, base=10):将字符串转为数字,通过 base 可以修改数字的进制。
比如我们需要转很多二进制数字,每次都设置 base 极为不便:

import functools
# 创建函数变量int2,赋值为:【默认的参数base设置为2】的int函数 
int2 = functools.partial(int, base=2)
print(int2('1000000')) #输出: 64
  • 这里的参数:第一个是要设置的函数,后面的便会作为函数的参数代入(遵循普通调用函数的规则)
    • 如果没有使用关键字参数,那么就是为位置参数

partial 英 [ˈpɑːʃl] 美 [ˈpɑːrʃl] adj. 局部的;偏爱的;不公平的

猜你喜欢

转载自blog.csdn.net/zsq8187/article/details/110003643