有效的函数(二)

简介: 有效的函数(二)

函数可以作为参数传递给其他函数
因为函数也是对象,所以你可以将它们作为参数传递给其他函数。这里有一个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
相关文章
|
12天前
|
存储 Python
有效的函数(一)
有效的函数(一)
14 0
|
4月前
|
程序员 C语言
函数(1)
函数(1)
32 0
|
5月前
函数(二)
函数(二)
19 0
|
10月前
|
存储 C语言
对函数的剖析二
对函数的剖析二
47 0
|
编译器
函函函函函函函函函函函数——two
函函函函函函函函函函函数——two
89 0
函函函函函函函函函函函数——two
|
存储 编译器 C语言
C语言知识点之 函数
C语言知识点之 函数
50 0
|
算法 程序员 编译器
最简单的函数,看看就会了
最简单的函数,看看就会了
|
算法 编译器
函数(2)
函数(2)
|
算法 编译器 API
8.函数
8.函数
70 0