Python高阶函数

高阶函数是在Python中一个非常有用的功能函数,所谓高阶函数就是一个函数可以用来接收另一个函数作为参数,这样的函数叫做高阶函数。高阶函数是函数式编程的基本前提。

函数在 Python 是一等公民(First-Class Object),函数也是对象,是可调用对象,函数可以作为普通变量,也可以作为函数的参数、返回值,这也是python高阶函数的语法基础。

通常我们说的Python高阶函数指的是函数的参数类型为函数,或者函数的返回值类型为函数,Python中常用的高阶函数有map、filter、reduce、partial。

高阶函数

概念

在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数:

  • 接受一个或多个函数作为参数
  • 输出一个函数

数学概念 y=f(g(x))

# 高阶函数,例1:
def counter(base):
    def inc(step=1):             # 局部变量
        nonlocal base
        base += step
        return base
    return inc                   # 局部变量 inc函数对象,每次赋值即重新定义

c1 = counter(5)
c2 = counter(5)
print(c1, c2)     #c1、c2引用的是不同的函数对象
print(c1(), c2())                # 6 6
print(c1() == c2())              # True,函数对象的返回值相等
print(c1== c2)  # False,函数每次调用都不一样

‘’'
.inc at 0x106ed0540> .inc at 0x106ed0b80>
6 6
True
False
‘''
def inc(step=1):
    return step

def counter():
    return inc     # 返回全局变量 inc

c1 = counter()
c2 = counter()
print(c1, c2) #c1和c2引用的是同一个函数对象
print(c1 == c2)    # True,因为全局变量 inc 不消亡

‘’'
 
True
‘''

 高阶函数特点

Python 高阶函数的特点:

  • 函数是对象类型(Object type)的实例
  • 可以将函数存储在变量中
  • 可以将函数作为参数传递给另一个函数
  • 您可以从函数返回函数
  • 您可以将它们存储在数据结构中,如哈希表、列表等…

函数作为对象

在 Python中,可以将函数分配给变量,此赋值不调用函数,而是创建对该函数的引用。考虑下面的例子,以便更好地理解。

def shout(text):
    return text.upper()

print(shout('Hello'))
# HELLO

# 将函数赋值给变量
yell = shout

print(yell('Hello'))
# HELLO

在上面的示例中,一个函数对象被 shout 引用,并创建指向它的第二个名称 yell。

将函数作为参数传递给其他函数类似于 Python 中的对象,因此,它们可以作为参数传递给其他函数。请考虑下面的示例,在这里我们创建了一个函数 greet,它将函数作为参数。

def shout(text):
    return text.upper()

def whisper(text):
    return text.lower()

def greet(func):
    # 将函数存储在变量中
    greeting = func("Hi, I am created by a function \
    passed as an argument.")
    print(greeting)

greet(shout)
# HI, I AM CREATED BY A FUNCTION PASSED AS AN ARGUMENT.
greet(whisper)
# hi, i am created by a function passed as an argument.

返回函数

由于函数是对象,我们也可以从另一个函数返回一个函数。在下面的示例中,create_adder 函数返回 adder 函数。

# 来说明函数可以返回另一个函数
def create_adder(x):
    def adder(y):
        return x + y

    return adder

add_15 = create_adder(15)

print(add_15(10))
# 25

这个特性正好可以运用到下边的装饰器的思想中。

装饰器

装饰器是 Python 中最常用的高阶函数。它允许程序员修改函数或类的行为。装饰器允许我们包装另一个函数,以扩展包装函数的行为,而无需永久修改它。在 Decorators 中,函数作为参数被放入另一个函数中,然后在包装器函数中调用。

装饰器的编写方法为:

@gfg_decorator
def hello_decorator():
    ...

# 上述代码相当于:

def hello_decorator():
    ...

hello_decorator = gfg_decorator(hello_decorator)

在上面的代码中,gfg_decorator 是一个可调用函数,将在另一个可调用函数 hello_decorator 函数的顶部添加一些代码,并返回包装器函数(wrapper function)。

# 定义一个装饰器
def hello_decorator(func): 

    # inner1 is a Wrapper function in  
    # which the argument is called 

    # inner function can access the outer local 
    # functions like in this case "func" 
    def inner1(): 
        print("Hello, this is before function execution") 

        # calling the actual function now 
        # inside the wrapper function. 
        func() 

        print("This is after function execution") 

    return inner1 


# defining a function, to be called inside wrapper 
def function_to_be_used(): 
    print("This is inside the function !!") 


# passing 'function_to_be_used' inside the 
# decorator to control its behavior 
function_to_be_used = hello_decorator(function_to_be_used) 


# calling the function 
function_to_be_used() 

# 输出
'''
Hello, this is before function execution
This is inside the function !!
This is after function execution
'''

 

闭包

说到高阶函数,就不得不提闭包,先来看一下Python中闭包的定义:

如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

def closure():
    x = 5

    def sub():
        return x * x

    return sub

如上,在内部函数sub中包含了对函数closure中局部变量x的引用,这就是闭包。

filter()

基本定义

filter(function or None, iterable) --> filter object
  • function or None
    # 过滤操作执行的函数
  • iterable
    # 需要过滤的序列

作用:过渡序列中不符合条件的元素。

filter有两个参数,第1参数可以是函数,也可以是None.

当第1个参数是函数的时候,将第2个参数中每个元素进行计算。

当第1个参数是None时,直接将第二个参数中为True的值筛选出来。

filter() 方法返回一个迭代器(filter 对象),该迭代器通过了 iterable 中每个元素的函数检查,返回的是原序列中的值,非布尔值。

函数使用

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 得到列表中的偶数
def func(x):
    return x % 2 == 0


result = filter(func, lst)
print(list(result))

# 输出:
[2, 4, 6, 8, 10]

使用匿名函数

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(filter(lambda x: x % 2 == 0, lst)))

如果未定义函数时,相当于操作if <元素>,即:

filter(function, iterable) 相当于一个生成器表达式,当 function 不是 None 的时候为 (item for item in iterable if function(item));function 是 None 的时候为 (item for item in iterable if item) 。

[*filter(None,[1,False,3])]
# [1, 3]

filter 对象

以值为偶数(除以2时余数为0)时返回 True 的 lambda(匿名函数)为例:

l = [-2, -1, 0, 1, 2]
print(filter(lambda x: x % 2 == 0, l))
# 

print(type(filter(lambda x: x % 2 == 0, l)))
# 

 返回 filter 型的对象,即使直接 print(),也不输出内容的值,可以用 for 循环等方式取出来。

for i in filter(lambda x: x % 2 == 0, l):
    print(i)
# -2
# 0
# 2
# 筛选大于 5 的值
f = filter(lambda x: x>5, [2,3,5,7,9])
f # 
list(f)
# [7, 9]

# 函数为 None
f = filter(None, [2,False,5,None,9])
list(f)
# [2, 5, 9]
# list of letters
letters = ['a', 'b', 'd', 'e', 'i', 'j', 'o']

# function that filters vowels
def filter_vowels(letter):
    vowels = ['a', 'e', 'i', 'o', 'u']

    if(letter in vowels):
        return True
    else:
        return False

filtered_vowels = filter(filter_vowels, letters)

print('The filtered vowels are:')
for vowel in filtered_vowels:
    print(vowel)
'''
The filtered vowels are:
a
e
i
o
'''

列表推导式替代

 可以用列表表达式实现它的功能:

l = [-2, -1, 0, 1, 2]
[x for x in l if x % 2 == 0]
# [-2, 0, 2]

[x for x in l if x % 2 != 0]
# [-1, 1]

l_s = ['apple', 'orange', 'strawberry']
[x for x in l_s if x.endswith('e')]
# ['apple', 'orange']

[x for x in l_s if not x.endswith('e')]
# ['strawberry']

l = [-2, -1, 0, 1, 2]
[x for x in l if x]
# [-2, -1, 1, 2]

l_2d = [[0, 1, 2], [], [3, 4, 5]]
[x for x in l_2d if x]
# [[0, 1, 2], [3, 4, 5]]

filterfalse()

itertools.filterfalse() ,只有 function 返回 false 时才选取 iterable 中元素的补充函数。和filter()函数的筛选机制相反。

import itertools

foo = itertools.filterfalse(lambda x: x%2==0, [1,3,1,5,6,8,1])
[*foo]
# [1, 3, 1, 5, 1]

map()

基本定义

map(func, *iterables) --> map object
  • function
    # 序列中的每个元素需要执行的操作, 可以是匿名函数
  • *iterables
    # 一个或多个序列

作用:对可迭代对象的每一个元素作为函数的参数进行运算,然后将其添加到一个新的对象中返回。

返回一个将 function 应用于 iterable 中每一项并输出其结果的迭代器。 如果传入了额外的 iterable 参数,function 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。 当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。

将给定的函数应用于可迭代对象的每一项,并返回结果列表,返回的结果是一个 map object(map 对象),可以将 map object 传递给 list()(创建列表)、set()(创建集合)等函数以显示和应用结果。

单个迭代对象

# 对可迭代对象进行2次方运算
lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

result = map(lambda i: i ** 2, lst)
print(list(result))
#输出
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

多个迭代对象

map支持多个迭代对象,也需要前面的函数支持多个输入变量,如果 iterable 的数量不一样,则取短板,其余的放弃,如把两个列表对应的元素求和:

r = map(lambda x, y: x + y, [1, 2, 3, 4], [5, 6, 7, 8])
print(list(r)) # 打印结果:[6, 8, 10, 12]

# 对象不同长度
def add_num(x, y):
    return x + y

m = map(add_num, [1,2,3,4], [1, 2])
list(m)
# [2, 4]

startmap() 

对于函数的输入已经是参数元组的情况,可以 使用 itertools.starmap() 操作。它创建一个迭代器,使用从可迭代对象中获取的参数来计算该函数。当参数对应的形参已从一个单独可迭代对象组合为元组时(数据已被“预组对”)可用此函数代替 map()。map() 与 starmap() 之间的区别可以类比 function(a,b) 与 function(*c) 的区别。

import itertools

t = [(2,5), (3,4)]
# 元组内操作(相乘)
sm = itertools.starmap(lambda x,y: x*y, t)
list(sm)
# [10, 12]

  列表推导式替代

map 的功能可以用列表表达式来代替:

l = [-2, -1, 0]
[abs(x) for x in l]
# [2, 1, 0]

[x**2 for x in l]
# [4, 1, 0]

l_1 = [1, 2, 3]
l_2 = [10, 20, 30]
[x * y for x, y in zip(l_1, l_2)]
# [10, 40, 90]

在大多数情况下,与 map 相比,使用列表生成器式更简洁明了,但也有人认为 map 作为高阶函数,能更加突出函数,弱化了循环的表达,让处理逻辑看起来更加明显。 

NumPy 代替

在数据科学中,不需要按 map 模式的计算,两个序列之间的操作被认为是一个矩阵计算,NumPy 可以非常好地完成这些,比 map() 和列表表示更为明确。

import numpy as np

a = np.array([-2, -1, 0])
print(np.abs(a))
# [2 1 0]

print(a**2)
# [4 1 0]

a_1 = np.array([1, 2, 3])
a_2 = np.array([10, 20, 30])
print(a_1 * a_2)
# [10 40 90]

对于大规模的列表的处理和复杂的处理NumPy更快。NumPy还提供各种函数,所以在进行以数值排列为对象的处理的情况下可以尝试一下。

reduce()

基本定义

reduce(function, sequence[, initial]) -> value
  • function
    # 函数, 序列中的每个元素需要执行的操作, 可以是匿名函数
  • sequence
    # 需要执行操作的序列
  • initial
    # 可选,初始参数

作用:reduce把一个函数作用在一个序列[x1, x2, x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

每次函数计算的结果继续和序列的下一个元素做累积计算。

可以理解为 reduce 中的第一个参数 function 是一个函数,它有两个变量(function 的变量),iterable 是一个序列,function 第一次执行时,按顺序先取两个传入执行,得到一个结果,然后再将这个结果与 iterable 中的下一个值(还是两个变量)传入 function 执行,如此反复直到 iterable 里的值取完为止,最终就能得到一个终极的返回值。

例如,reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) 是计算 ((((1+2)+3)+4)+5) 的值。 左边的参数 x 是积累值而右边的参数 y 则是来自 iterable 的更新值。

简单使用

要想使用,必需导入functools模块。

# 求列表元素之和
import functools

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def func(a, b):
    return a + b


result = functools.reduce(func, lst)
print(result)
输出:
55

 使用匿名函数:

import functools

lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(functools.reduce(lambda x, y: x + y, lst))

如果存在可选项 initializer,它会被放在参与计算的可迭代对象的条目之前,并在可迭代对象为空时作为默认值。 如果没有给出 initializer 并且 iterable 仅包含一个条目,则将返回第一项。

累积相除:

from functools import reduce
from operator import truediv

reduce(truediv, [4, 3, 2, 1])
reduce(lambda x,y: x/y, [4, 3, 2, 1])
# 0.6666666666666666

设定初始值

指定 initializer 参数时,第一次执行时,函数的第一个参数传入此值,第二个参数为序列的第一个值:

from functools import reduce
from operator import truediv

reduce(truediv, [10, 5, 2], 50)
# 0.5

 还可以把初始值当做输出的提示部分: 

# 设定初始参数:
s = reduce(lambda x, y: x + y, ['1', '2', '3', '4', '5'], "数字 = ")

print(s)
# print out: 数字 = 12345

如果 initializer 没有指定,序列也会空,则会报错:

#当序列为空时,则返回就是 initializer 的值
reduce(truediv, [], 50)
# 50

reduce(truediv, [])
# TypeError: reduce() of empty iterable with no initial value

性能和可读性

Python的 reduce() 的性能可能非常差,因为它通过多次调用函数来工作。这会使您的代码变得缓慢和低效。当您将 reduce() 用于复杂的用户定义函数或 lambda 函数时,使用 reduce() 还会影响代码的可读性。

Python 提供了一系列工具,可以优雅地替换 reduce(),至少在其主要用例中是如此。主要有:

  • 尽可能使用专用函数解决 Python 的 reduce() 的用例。函数(如 sum、all、any、max、min、len、math.prod等)将使您的代码更快、更可读、更易于维护,并且更具 Python 风格。
  • 使用 reduce() 时避免使用复杂的用户定义函数。这些类型的函数会使代码难以阅读和理解。您可以改为使用显式且可读的 for 循环。
  • 使用reduce() 时避免使用复杂的 lambda 函数,它们还可能使您的代码无法阅读和混淆。

第二点和第三点是 Guido (Python 之父,专门撰文讨论过 reduce 在 Python 3 的去留问题)自己的担忧,他说:

所以现在 reduce() 这实际上是我最讨厌的一个,因为除了一些涉及 + 或 * 的示例外,几乎每次我看到带有非平凡函数参数的 reduce() 调用时,我都需要抓起纸笔来绘制实际输入到该函数中的内容,然后才能理解 reduce() 应该做什么。所以在我看来,reduce() 的适用性非常局限于关联运算符,在所有其他情况下,最好显式写出积累循环。(来源:https://www.artima.com/weblogs/viewpost.jsp?thread=98196)

 以下是用内置函数与reduce() 方法的性能对比:

from timeit import timeit

print('sum()', timeit('sum(range(100))'))
print('reduce()', timeit('reduce(lambda x,y: x+y, range(100))',
                         setup='from functools import reduce'))

‘’'
sum() 1.5165458140254486
reduce() 8.190408975002356
‘''

如果您打算使用 reduce() 来解决问题,那么与使用专用内置函数的代码相比,您的代码将慢得多,内置函数比如求和问题(sum)最具可读性和 python 风格的解决方案。

sorted()

 基本定义

sorted(iterable,key,reverse)
  • iterable
    # 序列
  • key
    # 可以用来计算的排序函数。
  • reverse
    # 排序规则,reverse = True 降序,reverse = False 升序(默认)。

其中,iterable是可迭代对象。

key可选参数,可以接收函数来实现自定义的排序,默认为None(直接比较)。

reverse:排序规则,为一个布尔值,reverse = True 降序 , reverse = False 升序(默认)。

# 默认情况下,对字符串排序,是按照ASCII的大小比较的
lst = ['bb', 'aaaa', 'c', 'ddddddddd', 'fff']
print(sorted(lst))
输出:
['aaaa', 'bb', 'c', 'ddddddddd', 'fff']

#对列表按照int值排序
lst = [2, 5, '1', 3, '6', '4']
print(sorted(lst, key=int))
输出:
['1', 2, 3, '4', 5, '6']

key 函数参数

对于更复杂的自定义排序,sorted() 采用可选的 key 参数指定一个函数,该函数在比较之前转换每个元素。key 函数接受 1 个值并返回1个值,返回的函数计算值(proxy)值用于排序中的比较。

例如,对于字符串列表,指定 key=len(内置的 len() 函数)按长度对字符串进行排序,从最短到最长。排序对每个字符串调用 len() ,以获取代理长度值列表,然后使用这些代理值进行排序。

strs = ['ccc', 'aaaa', 'd', 'bb']
sorted(strs, key=len)
# ['d', 'bb', 'ccc', 'aaaa']

可以自定义一个函数,该函数接受一个值(这个值是序列中的每个元素),并返回代理值以指导排序。因此,key 传入的是一个可调用对象,这个对接接受序列中的元素。

# 不区分大小写的字符串比较
sorted("This is a test string from Andrew".split(), key=str.lower)
# ['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
# 使用第二个元素进行排序
def take_second(elem):
    return elem[1]


# 随便给一个列表
random = [(2, 2), (3, 4), (4, 1), (1, 3)]

# 带关键字的排序列表
sorted_list = sorted(random, key=take_second)

# 打印 list
sorted_list
# [(4, 1), (2, 2), (1, 3), (3, 4)]

多健排序

假设我们有以下列表:

# 科学奥林匹克竞赛中学生信息的嵌套列表
# 列表元素:(学生姓名,满分100分,年龄)

participant_list = [
    ('Alison', 50, 18),
    ('Terence', 75, 12),
    ('David', 75, 20),
    ('Jimmy', 90, 22),
    ('John', 45, 12)
]

 

我们希望对列表进行排序,使得分最高的学生位于开始位置,如果学生的分数相等,则必须对他们进行排序,以便年轻的参与者排在第一位。

我们可以通过返回元组而不是数字来实现这种多键排序。

两个元组可以通过从第一个元组开始比较它们的元素来进行比较。如果存在联系(元素相等),则比较第二个元素,依此类推。

>>> (1,3) > (1, 4)
False
>>> (1, 4) < (2,2)
True
>>> (1, 4, 1) < (2, 1)
True

让我们使用这个逻辑来构建排序逻辑。

# Nested list of student's info in a Science Olympiad
# List elements: (Student's Name, Marks out of 100 , Age)
participant_list = [
    ('Alison', 50, 18),
    ('Terence', 75, 12),
    ('David', 75, 20),
    ('Jimmy', 90, 22),
    ('John', 45, 12)
]


def sorter(item):
    # 因为最高分在先,所以最小错误=最高分
    error = 100 - item[1]
    age = item[2]
    return (error, age)


sorted(participant_list, key=sorter)
# [('Jimmy', 90, 22), ('Terence', 75, 12), ('David', 75, 20), ('Alison', 50, 18), ('John', 45, 12)]

 由于排序逻辑函数很小,可以放在一行中,所以lambda函数在键内使用,而不是传递单独的函数名。上述程序可通过以下方式使用lambda函数编写:

# Nested list of student's info in a Science Olympiad
# List elements: (Student's Name, Marks out of 100 , Age)
participant_list = [
    ('Alison', 50, 18),
    ('Terence', 75, 12),
    ('David', 75, 20),
    ('Jimmy', 90, 22),
    ('John', 45, 12)
]

sorted(participant_list, key=lambda item: (100-item[1], item[2]))
# [('Jimmy', 90, 22), ('Terence', 75, 12), ('David', 75, 20), ('Alison', 50, 18), ('John', 45, 12)]

排序原理

对字符的排序是按照 unicode 的码位顺序进行排序的,但有一些细节实现比较复杂。排序算法只使用项目之间的 < 比较。虽然定义一个__lt__() (小于)方法就足以进行排序,但 PEP 8 建议实现所有六个比较功能("<" | ">" | "==" | ">=" | "<=" | "!=")的特殊方法,这将有助于避免将相同的数据与依赖于不同底层方法的其他排序工具(如 max() )一起使用时出现错误。实现所有六个比较功能的特殊方法也有助于避免混合类型比较的混淆,混合类型比较可以调用 __gt__() 方法。

# vowels list
py_list = ['e', 'a', 'u', 'o', 'i']
sorted(py_list)
# ['a', 'e', 'i', 'o', 'u']

# string
py_string = 'Python'
sorted(py_string)
# ['P', 'h', 'n', 'o', 't', 'y']

# vowels tuple
py_tuple = ('e', 'a', 'u', 'o', 'i')
sorted(py_tuple)
# ['a', 'e', 'i', 'o', 'u']

请注意,在所有情况都会返回排序后的列表,不认原数据是什么结构。

按降序排序

sorted() 函数接受一个 reverse 参数作为可选参数,设置 reverse=True 将按降序对 iterable 进行排序。

# set
py_set = {'e', 'a', 'u', 'o', 'i'}
print(sorted(py_set, reverse=True))
# ['u', 'o', 'i', 'e', 'a']

# dictionary
py_dict = {'e': 1, 'a': 2, 'u': 3, 'o': 4, 'i': 5}
sorted(py_dict, reverse=True)
# ['u', 'o', 'i', 'e', 'a']

# frozen set
frozen_set = frozenset(('e', 'a', 'u', 'o', 'i'))
sorted(frozen_set, reverse=True)
# ['u', 'o', 'i', 'e', 'a']

 

sort()与sorted的区别

  • sort() 函数只适用于列表排序,而sorted()函数适用于任意可以迭代的对象排序。
  • sort() 函数排序会改变原有的待排序列表,而sorted()函数则不会改变。所以在使用列表进行排序时,需要考虑是否需要保存原列表,如果无需保存原列表,则优先使用sort() 节省内存空间,提高效率。

partial()

柯里化基本概念

partial 函数允许我们复刻函数的某些参数并生成新函数。函数在执行时,要带上所有必要的参数进行调用。但是,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。简单说就是局部套用一个函数,让广泛功能的函数简单化、单一化。这是一个 柯里化 过程。在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

简单说 partial 把某个函数的某个参数固定,从而构造出一个新的函数来。返回的这个新函数对象是 partial 对象,下文有介绍。

在一些情况下, 我们在设计 Python 函数的时候, 会给它设定非常丰富的功能, 这样我们就可以定义一个非常强大的函数。 与此同时带来的问题是使用上的不方便, 因为有可能我们需要传递非常多的参数才能换成我们想要的功能。这时候 partial 函数就可以让我们在这个强大的函数中派生出对应的具体功能。

在计算机科学中,柯里化(英语:Currying),又译为科里化、卡瑞化、或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。这个技术由克里斯托弗·斯特雷奇以逻辑学家哈斯凯尔·加里命名的,尽管它是Moses Schönfinkel和戈特洛布·弗雷格发明的。

例如,传入百、十、个位数字生成数字:

from functools import partial

# 常规函数
def add(a, b, c):
    return 100 * a + 10 * b + c

# b=1,c=2 的部分函数
add_part = partial(add, c = 2, b = 1)

# 调用 partial 函数
add_part(3)
# 312

例如,我们有加法函数(实际上我们有 sum,不需要它),我们可以将它派生出不同的加法函数:

from functools import partial

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

def add2number(x,y,z):
    return x + y + z


# a 固定值为 2
add2 = partial(add,2)
add2(1)
# 3

# 将 x 固定为 1,y 值固定为 2
add3 = partial(partial(add2number,1), 2)
add3(1)
# 4

语法定义

完整语法为:functools.partial(func, /, *args, **keywords),返回一个新的 partial 对象(部分对象,见下文),又称偏函数,主要用途是减少可调用对象的参数个数,当被调用时其行为类似于 func 附带位置参数 args 和关键字参数 keywords 被调用。 如果为调用提供了更多的参数,它们会被附加到 args。 如果提供了额外的关键字参数,它们会扩展并重载 keywords。 这个函数是使用 C 而不是 Python 实现的,大致等价于:

def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

partial() 会被“冻结了”一部分函数参数和/或关键字的部分函数应用所使用,从而得到一个具有简化签名的新对象。 例如,partial() 可用来创建一个行为类似于 int() 函数的可调用对象,其中 base 参数默认为二:

from functools import partial

basetwo = partial(int, base=2)
basetwo.__doc__ = 'Convert base 2 string to an int.'

basetwo('10010')
# 18

在类中使用

在类中如果想实现 partial 类似的派生方法的话,可以使用 functools.partialmethod 方法,如:

import functools

class RGB(object):
    def __init__(self, red, blue, green):
        super(RGB, self).__init__()
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):
        return getattr(self, type)

    red = functools.partialmethod(_color, type='_red')
    blue = functools.partialmethod(_color, type='_blue')
    green = functools.partialmethod(_color, type='_green')

 partial 对象

partial 对象是由 partial() 创建的可调用对象。 它们具有三个只读属性:

  • partial.func:一个可调用对象或函数。 对 partial 对象的调用将被转发给 func 并附带新的参数和关键字。
  • partial.args:最左边的位置参数将放置在提供给 partial 对象调用的位置参数之前。
  • partial.keywords:当调用 partial 对象时将要提供的关键字参数。

partial 对象与 function 对象的类似之处在于它们都是可调用、可弱引用的对象并可拥有属性。 但两者也存在一些重要的区别。 例如前者不会自动创建 __name__ 和 __doc__ 属性。 而且,在类中定义的 partial 对象的行为类似于静态方法,并且不会在实例属性查找期间转换为绑定方法。

如上例 basetwo 就是一个 partial 对象:

from functools import partial

basetwo = partial(int, base=2)
basetwo
# functools.partial(, base=2)

basetwo.args
# ()
basetwo.func
# int
basetwo.keywords
# {'base': 2}

偏函数可用于从常规函数派生专用函数,从而帮助我们重用代码。

这个特性类似于C++中的绑定。

Currying 是将一个具有n个参数的函数划分为 n 个具有一个参数的连续函数。部分应用程序是使用一些参数“预填充”函数( 'pre-filling' a function),然后返回参数数量较少的函数。

partialmethod() 

Python 内置模块 functools 的一个高阶函数 partialmethod 与 partial() 偏函数 类似,partialmethod 偏方法也是在对象里对已有的方法进行派生,将功能复杂的方法衍生出简单功能的方法。这是从 Python 3.4 版新功能。

偏函数基本概念

对于python 偏函数partial理解运用起来比较简单,就是对原函数某些参数设置默认值,生成一个新函数。而如果对于类方法,因为第一个参数是 self,使用 partial 就会报错了。

class functools.partialmethod(func, /, *args, **keywords)

返回一个新的 partialmethod 描述器,其行为类似 partial 但它被设计用作方法定义而非直接用作可调用对象。

func 必须是一个 descriptor 或可调用对象(同属两者的对象例如普通函数会被当作描述器来处理)。

当 func 是一个描述器(例如普通 Python 函数, classmethod(), staticmethod(), abstractmethod() 或其他 partialmethod 的实例)时, 对 __get__ 的调用会被委托给底层的描述器,并会返回一个适当的 部分对象 作为结果。

当 func 是一个非描述器类可调用对象时,则会动态创建一个适当的绑定方法。 当用作方法时其行为类似普通 Python 函数:将会插入 self 参数作为第一个位置参数,其位置甚至会处于提供给 partialmethod 构造器的 args 和 keywords 之前。

定义类方法

from functools import partialmethod

class Cell:
    def __init__(self):
        self._alive = False
    @property
    def alive(self):
        return self._alive
    def set_state(self, state):
        self._alive = bool(state)

    set_alive = partialmethod(set_state, True)
    set_dead = partialmethod(set_state, False)

    print(type(partialmethod(set_state, False)))
    # 

c = Cell()
c.alive
# False

c.set_alive()
c.alive
# True

c.set_alive() 和 c.set_dead() 作为 Cell 的方法,更加直观,省去专门的定义代码。

派生三方库方法

pandas 的 apply 方法 默认 axis=0 按行去操作,我们需要一个派生一个按列的的操作:

from functools import partialmethod
import pandas as pd
import pandas._testing as tm

df = tm.makeDataFrame()
df
'''
                   A         B         C         D
D4mjVx3GtT  1.808966 -1.199819 -0.779483 -0.566463
ylYk4vm4MZ -1.509050 -0.361186  0.486100  0.021837
tDbfPX8Eva  0.141416  0.397220  0.172930 -0.504479
...
19tYkM1qrE -0.617952  1.137066 -0.962393  0.982731
mpQIQkifPC  0.874417  0.226630  0.739977 -0.786624
'''

# 使用原函数
df.apply(sum)
'''
A    4.621611
B    8.959269
C    0.126089
D    2.828309
dtype: float64
'''

# 定义偏方法
pd.DataFrame.apply_col = partialmethod(pd.DataFrame.apply, axis=1)

# 使用偏方法
df.apply_col(sum)
'''
D4mjVx3GtT   -0.736799
ylYk4vm4MZ   -1.362299
tDbfPX8Eva    0.207087
...
19tYkM1qrE    0.539452
mpQIQkifPC    1.054401
dtype: float64
'''

partial 对象

partialmethod 派生出的新方法是一个 partial 对象,它有 args、func、keywords 三个属性,如在上例中:

pd.DataFrame.apply_col
# functools.partial(, axis=1)

pd.DataFrame.apply_col.args
# ()

pd.DataFrame.apply_col.func
# 

pd.DataFrame.apply_col.keywords
# {'axis': 1}

与 partial 偏函数的区别

 partialmethod 针对对象中的方法进行派生,partial 是对独立的函数进行派生。偏函数 partial 对原函数某些参数设置默认值,生成一个新函数。而如果对于类方法,因为第一个参数是 self,使用 partial 就会报错,因此要使用 partialmethod。

 

你可能感兴趣的:(python,开发语言)