有效的函数

简介: 有效的函数

有效的函数
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
相关文章
|
11天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
7天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2514 17
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
7天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1520 14
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
3天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
552 14
|
1月前
|
运维 Cloud Native Devops
一线实战:运维人少,我们从 0 到 1 实践 DevOps 和云原生
上海经证科技有限公司为有效推进软件项目管理和开发工作,选择了阿里云云效作为 DevOps 解决方案。通过云效,实现了从 0 开始,到现在近百个微服务、数百条流水线与应用交付的全面覆盖,有效支撑了敏捷开发流程。
19282 30
|
9天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
473 48
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
|
1月前
|
人工智能 自然语言处理 搜索推荐
阿里云Elasticsearch AI搜索实践
本文介绍了阿里云 Elasticsearch 在AI 搜索方面的技术实践与探索。
18838 20
|
1月前
|
Rust Apache 对象存储
Apache Paimon V0.9最新进展
Apache Paimon V0.9 版本即将发布,此版本带来了多项新特性并解决了关键挑战。Paimon自2022年从Flink社区诞生以来迅速成长,已成为Apache顶级项目,并广泛应用于阿里集团内外的多家企业。
17528 13
Apache Paimon V0.9最新进展
|
2天前
|
云安全 存储 运维
叮咚!您有一份六大必做安全操作清单,请查收
云安全态势管理(CSPM)开启免费试用
362 4
叮咚!您有一份六大必做安全操作清单,请查收