函数可以作为参数传递给其他函数
因为函数也是对象,所以你可以将它们作为参数传递给其他函数。这里有一个greet函数,它使用传递给它的函数对象来格式化问候字符串,并打印输出。
def greet(func):
greeting = func('Hi, I am a Python program.')
print(greeting)
你可以通过传递不同的函数来影响最终问候的内容。例如,如果你将bark函数传递给greet函数,会发生什么?
>>> greet(bark)
HI, I AM A PYTHON PROGRAM.!
当然,你也可以定义一个新的函数来生成不同风格的问候。例如,以下的whisper函数可能更适合于不希望Python程序听起来像“Optimus Prime”的场景。
def whisper(text):
return text.lower() + '...'
>>> greet(whisper)
hi, i am a python program....
将函数对象作为参数传递给其他函数的能力非常强大。这允许你抽象行为,并在你的程序中传递这些行为。例如,在这个例子中,greet函数保持不变,但你可以通过传递不同的问候行为来影响其输出。这种灵活性和可扩展性使得代码更加模块化和易于维护。
能够接受其他函数作为参数的函数也被称为高阶函数。在函数式编程(Functional Programming, FP)风格中,高阶函数是必不可少的一部分。这种函数不仅执行基本操作,还允许你通过传递其他函数来实现更复杂的行为。这种灵活性使得函数式编程可以更好地处理数据和逻辑,并且代码更加模块化和易于维护。
在Python中,高阶函数的经典例子是内置的map函数。它接受一个函数对象和一个可迭代对象(如列表、元组或集合),然后对可迭代对象中的每个元素应用这个函数,并将结果逐个返回。这种模式使得你可以将复杂的数据处理逻辑抽象为一个函数,然后通过map这样的高阶函数来遍历并处理整个数据集。
以下是如何通过映射bark函数到它们的方式,一次性格式化一系列的问候语:
>>> list(map(bark, ['hello', 'hey' 'hi']))
['HELLO!', 'HEYHI!']
正如你所看到的,map函数遍历了整个列表,并对每个元素应用了bark函数。结果,我们得到了一个新的列表对象,其中包含的是经过修改的问候语字符串。
函数可以嵌套
出人意料的是,Python确实允许在一个函数内部定义另一个函数。这些经常被称为嵌套函数或内层函数。如下例子:
def speak(text):
def whisper(t):
return t.lower() + '...'
return whisper(text)
>>> speak('Hello, World')
'hello, world...'
这里发生了什么呢?每次你调用 speak,它都会定义一个新的内部函数 whisper,然后立即在其后调用这个函数。我感觉我的大脑开始有点小痒了,但是总体来说,这还算是相对简单的事情。
不过,关键在于—whisper 这个函数不存在于 speak 这个外部作用域中,它并不能在其他地方被访问或定义。
>>> whisper('Yo')
NameError: name 'whisper' is not defined
>>> speak.whisper
AttributeError: 'function' object has no attribute 'whisper'
但是,如果你真的想从speak外部访问到那个嵌套的whisper函数呢?别担心,函数本质上就是对象——你可以将内部函数作为对象返回给调用你父级函数(即定义两个内层函数的那个函数)的客户端。
举个例子,这里有一个函数,它定义了两个内部函数。根据传给最顶层函数(也就是这个包含两个内层函数的函数)的参数,它会选择并返回其中一个内部函数给调用者。
def get_speak_func(volume):
def whisper(text):
return text.lower() + '...'
def yell(text):
return text.upper() + '!'
if volume > 0.5:
return yell
else:
return whisper
注意这里get_speak_func并没有实际调用它的任何内层函数。它只是根据传入的音量参数,选择合适的内层函数,并返回该函数对象。这是通过设计和编程实现的,目的是在需要时,从外部访问到特定功能的内部实现。
>>> get_speak_func(0.3)
<function get_speak_func.<locals>.whisper at 0x11779e7a0>
>>> get_speak_func(0.7)
<function get_speak_func.<locals>.yell at 0x11779e8e0>
当然可以,然后你可以调用返回的函数,这可能是直接调用,或者先通过赋值给一个变量来操作。例如,你可能会这样做:
>>> speak_func = get_speak_func(0.7)
>>> speak_func('Hello')
'HELLO!'
让我花点时间消化一下这个信息……这意味着函数不仅可以通过参数接受行为,而且还能返回行为。这多么酷啊!你知道吗?我现在有点思维混乱了,我需要一个小憩,喝杯咖啡再继续写作(我建议你这样做)。
函数能够捕捉局部状态
你刚刚见识了函数如何包含内部函数,而且甚至有可能从父函数中返回这些隐藏的内部函数。这就像系上安全带,因为接下来我们将进入更深的函数式编程领域。(咖啡休息时间应该结束了,对吧?)
这意味着函数不仅可以返回其他函数,这些内部函数甚至能够在捕获和携带部分父函数状态的同时运行。简单来说,假设我们有一个get_speak_func函数,它能生成一个可以发声的函数。新版本的这个函数在接收volume(音量)和text(要读的文字)这两个参数时,就让返回的函数立即可用。这样做的好处在于提高了代码的灵活性,并且能够更好地管理和控制父函数的状态。
def get_speak_func(text, volume):
def whisper():
return text.lower() + '...'
def yell():
return text.upper() + '!'
if volume > 0.5:
return yell
else:
return whisper
>>> get_speak_func('Hello, World', 0.7)()
'HELLO, WORLD!'
仔细观察内部函数whisper和yell,你会发现它们不再需要text参数。然而,它们似乎能访问父函数中定义的text参数,并且能够捕获并‘记住’这个参数的值。
这样的函数被称为词法闭包(或简称为闭包), 它们的特点是即使程序执行流程离开了原来的词法作用域,闭包依然能够保留和使用其中存储的值。
从实际操作的角度来说,这意味着函数不仅可以返回行为,还可以预先配置这些行为。下面是一个简单示例来说明这个概念:
def make_adder(n):
def add(x):
return x + n
return add
>>> plus_3 = make_adder(3)
>>> plus_5 = make_adder(5)
>>> plus_3(4)
7
>>> plus_5(4)
9
在这个例子中,make_adder充当了一个工厂,用来创建和配置名为add的函数。请注意,这些add函数能够访问make_adder工厂方法中定义的n参数(即它们所在作用域的父级)。
对象可以行为类似于函数
尽管在Python中所有函数都是对象,但反过来并不成立。对象并不是函数,它们可以被赋予可调性(Callable),这使得在许多情况下我们能将它们当作函数来使用。
如果一个对象是可调的,这意味着你可以使用圆括号函数语法调用它,并且甚至可以传递函数调用参数。这一切都依赖于call方法,它是底层实现的关键。
这里有一个类定义的可调对象的例子:
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
>>> plus_3 = Adder(3)
>>> plus_3(4)
7
在幕后,当我们把一个对象实例当作函数来调用时,实际上是试图执行这个对象的call方法。
当然,并不是所有对象都能够被调用。这就是为什么Python提供了一个内置的可调用函数,用于检查一个对象是否看起来像是可以被调用的对象:
>>> callable(plus_3)
True
>>> callable(yell)
True
>>> callable('hello')
False