有效的函数

简介: 有效的函数

有效的函数
Python的函数是第一等级
在Python中,函数是被视为“第一等级”对象的。你可以将它们赋值给变量,存储在数据结构中,作为参数传递给其他函数,并且甚至可以将它们作为其他函数返回值来使用。Python中的函数具有很高的灵活性和可操作性,使得它们在编写代码和实现功能时扮演着至关重要的角色。

深入理解这些概念的直觉方式将使你更容易掌握Python中高级特性,如lambda表达式和装饰器。这也将引导你走向函数式编程技术的道路。接下来,我将为你提供一系列示例,帮助你逐步培养这种直觉理解。这些示例会逐个建立在前一个的基础上,因此建议你按照阅读顺序阅读它们,并且在过程中尝试在Python解释器中运行一些示例来加深理解。

理解和掌握我们将讨论的概念可能需要比你预期的更多时间。别担心,这完全正常。我自己也经历过类似的情况。你可能会觉得自己像是撞到了墙,然后突然间一切就绪了,当你准备好时, 我会使用这个“吼叫”函数来演示目标。这是一个简单的玩具示例,具有易于识别的输出:

>>> def yell(text):
...   return text.upper() + '!'

>>> yell('hello')
'HELLO!'

函数是对象
在Python程序中,所有数据都由对象或对象之间的关系表示。对象如字符串、列表、模块和函数等都是对象的实例。 Python中的函数虽有特殊性,但从本质上说它们也是对象。

由于"吼叫"函数是Python中的一个对象,因此你可以像操作其他对象一样将它赋值给另一个变量:

>>> bark = yell

这条代码并没有调用函数。它实际上是获取了被引用为"吼叫"的函数对象,并创建了一个新的名称"bark",这个"bark"实际上是指向原始的函数对象。所以你现在也可以通过调用"bark"来执行与原函数相同底层功能的对象:

>>> bark('woof')
'WOOF!'

函数对象和它们的名字确实是两个独立的概念。这里有一个更明确的例子来证明这一点:正如例子所示,即使删除了原始函数名"yell",因为另一个名字"bark"仍然可以调用这个函数。

>>> del yell
>>> yell('hello?')
NameError: name 'yell' is not defined
>>> bark('hey')
'HEY!'

顺便说一下,Python在每个函数创建时都会附加一个字符串标识符,这是出于调试目的。你可以通过name属性来访问这个内部标识:

>>> bark.__name__
'yell'

现在,尽管函数的name仍然是’yell’,但这并不影响你从代码中访问该函数对象的方式。名字标识符只是一个调试辅助工具。一个指向函数的变量和函数本身实际上是两个独立的关注点。

函数可以存储在数据结构中
既然函数是第一类公民,你可以像存储其他对象一样将它们存储在数据结构中。举个例子,你可以在一个列表中添加函数:

>>> funcs = [bark, str.lower, str.capitalize]
>>> funcs
[<function yell at 0x1174676a0>, <method 'lower' of 'str' objects>, <method 'capitalize' of 'str' objects>]

在列表内部存储的函数对象,其访问方式与其他类型的对象并无区别。

>>> for f in funcs:
...    print(f, f('hey there'))

<function yell at 0x1174676a0> HEY THERE!
<method 'lower' of 'str' objects> hey there
<method 'capitalize' of 'str' objects> Hey there

你甚至可以在不先将其赋值给变量的情况下,直接调用存储在列表中的函数对象。你可以进行查找操作,然后立即在一个表达式中调用得到的“独立”函数对象

>>> funcs[0]('heyho')
'HEYHO!'

函数可以作为参数传递给其他函数
因为函数也是对象,所以你可以将它们作为参数传递给其他函数。这里有一个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
相关文章
|
3月前
|
Shell PHP
escapeshellarg() 函数
escapeshellarg() 函数
|
7月前
|
存储 编译器 C++
|
6月前
函数\judgeprime
函数\judgeprime
60 5
|
6月前
|
算法 程序员 编译器
函数(2)
函数(2)
25 0
|
7月前
|
数据库
什么是纯函数
纯函数是指在相同的输入下,总是返回相同的输出,且没有副作用的函数。具体来说,纯函数不会改变任何传入的参数,也不会在函数外部改变全局变量、文件系统、数据库等状态,它只是接收输入并返回输出,不会产生任何可观察的副作用。
76 0
|
7月前
|
开发工具 Windows
GetMessage()函数使用时的注意
GetMessage()函数使用时的注意
90 0
基本初等函数 对数函数
基本初等函数 对数函数
140 0
|
算法 程序员 C++
C/C++ 中的 strrchr() 函数
strrchr() 函数 在 C++ 中,strrchr() 是用于字符串处理的预定义函数。cstring 是字符串函数所需的头文件
322 0
|
自然语言处理 C++
C/C++ 中的 atol()、atoll() 和 atof() 函数
1.atol(): 此函数将作为参数传递给函数调用的 C 类型字符串转换为长整数。它解析 C 字符串 str 并将其内容解释为整数,该整数作为 long int 类型的值返回。该函数会丢弃字符串开头的空白字符,直到找到非空白字符。如果 C 字符串 str 中的非空白字符序列不是有效的整数,或者如果因为 str 为空或仅包含空白字符而不存在这样的序列,则不执行任何转换并返回零。
256 0
下一篇
DataWorks