Python中"一等公民"——函数

ⅰ亾dé卋堺 提交于 2020-08-12 03:16:30

Python中"一等公民"——函数

Python的函数是“一等公民”。 你可以将它们分配给变量,将它们存储在数据结构中,将它们作为参数传递给其他函数,甚至将它们作为其他函数的值返回。

直观地探究这些概念将使理解Lambda和装饰器等Python的高级功能变得更加容易。 它还使您走上了函数式编程技术的道路。

在本教程中,我将指导您完成许多示例,以帮助您发展这种直观的理解。 这些示例将彼此叠加,因此您可能需要按顺序阅读它们,甚至在继续学习时都可以在Python解释器会话中尝试其中的一些示例。 绕开我们将在此处讨论的概念的时间可能比预期要长一些。 不用担心,那是完全正常的。 我去过那儿。 您可能会感觉好像是在将头撞在墙上,然后当准备就绪时,事物突然“咔嗒”一声落入适当的位置。

在整个教程中,我将使用此yell函数进行演示。 这是一个简单的玩具示例,具有易于识别的输出:

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

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

 

函数就是对象

Python程序中的所有数据都由对象或对象之间的关系表示。 诸如字符串,列表,模块和函数之类的东西都是对象。 Python中的函数没有什么特别的。

因为yell函数是Python中的一个对象,所以您可以将其分配给另一个变量,就像其他任何对象一样:

>>> bark = yell

该行不会调用该函数。 它采用yell引用的功能对象,并创建指向它的第二个名称,即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”,这不会影响您如何从代码中访问它。 该标识符仅仅是调试辅助。 指向函数的变量和函数本身是两个单独的关注点。 (从Python 3.3开始,还有__qualname__,其作用类似,并提供合格的名称字符串来消除函数和类名的歧义。)

 

函数可以存储在数据结构中

由于函数是一等公民,因此您可以将它们存储在数据结构中,就像处理其他对象一样。 例如,您可以将函数添加到列表中:

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

访问存储在列表中的功能对象的工作方式与使用任何其他类型的对象相同:

>>> for f in funcs:
...     print(f, f('hey there'))
<function yell at 0x10ff96510> 'HEY THERE!'
<method 'lower' of 'str' objects> 'hey there'
<method 'capitalize' of 'str' objects> 'Hey there'

您甚至可以调用存储在列表中的功能对象,而无需先将其分配给变量。 您可以进行查找,然后立即在单个表达式中调用生成的“无实体”函数对象:

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

函数传递给其他函数

因为函数是对象,所以您可以将它们作为参数传递给其他函数。 这是一个问候函数,它使用传递给它的函数对象格式化问候字符串,然后打印出来:

def greet(func):
    greeting = func('Hi, I am a Python program')
    print(greeting)

您可以通过传入不同的函数来影响产生的问候。 如果您通过yell函数打招呼,将会发生以下情况:

>>> greet(yell)
'HI, I AM A PYTHON PROGRAM!'

当然,您还可以定义一个新函数来生成不同的问候语。 例如,如果您不希望Python程序听起来像Optimus Prime,则以下耳语功能可能会更好:

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

>>> greet(whisper)
'hi, i am a python program...'

将功能对象作为参数传递给其他功能的功能非常强大。 它允许您抽象化并传递程序中的行为。 在此示例中,greet函数保持不变,但是您可以通过传入不同的问候行为来影响其输出。

可以接受其他函数作为参数的函数也称为高阶函数。 它们是功能编程风格的必要条件。

Python中高阶函数的经典示例是内置的map函数。 它接受一个函数和一个可迭代对象,并在可迭代对象中的每个元素上调用该函数,并随着迭代的进行而产生结果。

通过将yell函数映射到问候语,您可以立即格式化所有问候语的方式:

>>> list(map(yell, ['hello', 'hey', 'hi']))
['HELLO!', 'HEY!', 'HI!']

map遍历了整个列表,并将yell函数应用于每个元素。

 

嵌套函数

Python允许在其他函数中定义函数。 这些通常称为嵌套函数或内部函数。 这是一个例子:

def speak(text):
    def whisper(t):
        return t.lower() + '...'
    return whisper(text)

>>> speak('Hello, World')
'hello, world...'

现在,这是怎么回事? 每次您通话时,它都会定义一个新的内部函数耳语,然后调用它。

而且这里的内部函数对外面不可见

>>> whisper('Yo')
NameError: "name 'whisper' is not defined"

>>> speak.whisper
AttributeError: "'function' object has no attribute '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实际上并没有调用其内部函数之一,它只是根据volume参数选择适当的函数,然后返回该函数对象:

>>> get_speak_func(0.3)
<function get_speak_func.<locals>.whisper at 0x10ae18>

>>> get_speak_func(0.7)
<function get_speak_func.<locals>.yell at 0x1008c8>

当然,您可以直接调用返回的函数,也可以先将其分配给变量名:

>>> 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!'

现在,仔细看看内部函数的whisperyell。 请注意,它们不再具有text参数了吗? 但是他们仍然可以通过某种方式访问父函数中定义的text参数。 实际上,他们似乎抓住并“记住”了该论点的价值。

执行此操作的函数称为词法闭包 lexical closures(或简称闭包)。 即使程序流不再在该范围内,闭包也会从其所在的词法范围中记住这些值。

实际上,这意味着函数不仅可以返回行为,而且还可以预先配置这些行为。 这是另一个简单的例子来说明这个想法:

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用作创建和配置“ adder”功能的工厂。 注意“ adder”函数如何仍然可以访问make_adder函数的n参数(封闭范围)。

 

对象也能像个函数一样被调用

在Python中对象不是的函数, 但是可以将它们设为可调用(callable),这使您在许多情况下都可以将它们像函数一样对待。

如果一个对象是可调用的,则意味着您可以在其上使用圆括号()并将函数调用参数传递给该对象。 这是可调用对象的示例:

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__方法。

当然,并非所有对象都是可调用的。 这就是为什么有一个内置的可调用函数来检查对象是否可调用的原因:

>>> callable(plus_3)
True
>>> callable(yell)
True
>>> callable(False)
False

总结

  • Python中的所有内容都是一个对象,包括函数。 您可以将它们分配给变量,将它们存储在数据结构中,以及在其他函数(一流函数)之间传递或返回它们。

  • “一等公民”函数使您可以抽象化并传递程序中的行为。

  • 函数可以嵌套,并且可以捕获并携带一些父函数的状态。 执行此操作的函数称为闭包。

  • 可以使对象可调用,这使您在许多情况下都可以像对待函数一样对待它们

     

参考:

Python’s Functions Are First-Class

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!