如何从函数中获取(“返回”)结果(输出)?我稍后如何使用结果?

2024-11-19 08:38:00
admin
原创
8
摘要:问题描述:假设我有一个如下函数:def foo(): x = 'hello world' 如何让函数返回x,以便我可以将其用作另一个函数的输入或在程序主体内使用该变量?我尝试使用return然后x在另一个函数中使用该变量,但我得到了NameError这样的结果。对于在同一个类中的方法之间传递信息的特定...

问题描述:

假设我有一个如下函数:

def foo():
    x = 'hello world'

如何让函数返回x,以便我可以将其用作另一个函数的输入或在程序主体内使用该变量?我尝试使用return然后x在另一个函数中使用该变量,但我得到了NameError这样的结果。


对于在同一个类中的方法之间传递信息的特定情况,通常最好将信息存储在中self。有关详细信息,请参阅在 Python 中的方法之间传递变量?


解决方案 1:

您可以使用return语句从函数返回一个值,但这并不会使返回的变量(值)在调用方的范围内可用。要在调用方中使用该值,您可以直接在语句中使用它,也可以在变量中捕获该值。

以下代码演示了这一点:

def foo():
    x = 'hello world'
    return x  # return 'hello world' would do, too

foo()
print(x)   # NameError - x is not defined outside the function

y = foo()
print(y)   # this works

x = foo()
print(x)   # this also works, and it's a completely different x than that inside
           # foo()

z = bar(x) # of course, now you can use x as you want

z = bar(foo()) # but you don't have to

解决方案 2:

有效的方式有两种:直接方式和间接方式。

直接的方式是return从函数中获取一个值,就像您尝试的那样,然后让调用代码使用该值。这通常是您想要的。从函数中获取信息的自然、简单、直接、明确的方法是这样做return。广义上讲,函数的目的return是计算一个值,并表示“这是我们计算的值;我们在这里完成了”。

直接使用return

这里的主要技巧是return返回一个值,而不是一个变量。因此,在调用函数后,return x 不会启用调用代码,也不会修改调用上下文中的任何现有值。(这大概就是为什么你得到一个。)x`x`NameError

return在函数中使用后:

def example():
    x = 'hello world'
    return x

我们需要编写调用代码来使用返回值:

result = example()
print(result)

这里的另一个关键点是,对函数的调用是一个表达式,因此我们可以像使用加法结果一样使用它。正如我们可以说 一样result = 'hello ' + 'world',我们可以说result = foo()。之后,result是我们自己的该字符串的本地名称,我们可以用它做任何我们想做的事情。

如果需要,我们可以使用相同的名称x。或者我们可以使用不同的名称。调用代码不必知道函数的编写方式,也不必知道它使用什么名称。1

我们可以直接使用该值来调用另一个函数:例如print(foo())。2我们可以直接返回该值:只需返回,而无需分配给。(再次强调:我们返回的是一个值,而不是一个变量。)return 'hello world'`x`

return每次调用该函数时,该函数只能执行一次。return终止该函数 - 再次说明,我们刚刚确定了计算结果,因此没有理由进行进一步计算。因此,如果我们想要返回多条信息,我们将需要提出一个对象(在 Python 中,“值”和“对象”实际上是同义词;对于其他一些语言来说,这并不那么好用。)

我们可以在返回行上创建一个元组;或者我们可以使用字典、namedtuple(Python 2.6+)、types.simpleNamespace(Python 3.3+)、dataclass(Python 3.7+)或其他类(甚至可能是我们自己编写的)将名称与要返回的值关联起来;或者我们可以从列表中的循环中累积值;等等。可能性无穷无尽。

另一方面,return无论你是否喜欢,函数都会执行(除非引发异常)。如果它到达末尾,它将隐式地执行return特殊值None。你可能想要或不想显式地执行它。


间接方法

除了return直接将结果返回给调用者之外,我们还可以通过修改调用者知道的某些现有对象来传达结果。有很多方法可以做到这一点,但它们都是同一主题的变体。

如果您希望代码以这种方式传递信息,请让它返回None-不要将返回值用于有意义的目的。这就是内置功能的工作原理。

当然,为了修改该对象,被调用的函数也必须知道它。这意味着,有一个可以在当前作用域中查找的对象名称。那么,让我们按顺序介绍一下这些内容:

本地范围:修改传入的参数

如果我们的某个参数是可变的,我们可以直接改变它,并依靠调用者来检查更改。这通常不是一个好主意,因为很难推断代码。它看起来像:

def called(mutable):
    mutable.append('world')

def caller():
    my_value = ['hello'] # a list with just that string
    called(my_value)
    # now it contains both strings

如果该值是我们自己的类的一个实例,我们也可以分配给一个属性:

class Test:
    def __init__(self, value):
        self.value = value

def called(mutable):
    mutable.value = 'world'

def caller():
    test = Test('hello')
    called(test)
    # now test.value has changed

分配给属性不适用于内置类型,包括object;并且它可能不适用于某些明确阻止您执行此操作的类。

局部范围:self在方法中修改

上面已经有了一个例子:在代码self.value中设置Test.__init__。这是修改传入参数的一个特殊情况;但它是 Python 中类的工作方式的一部分,也是我们应该做的事情。通常,当我们这样做时,调用实际上不会检查更改self- 它只会在逻辑的下一步中使用修改后的对象。这就是以这种方式编写代码的合适之处:我们仍然呈现一个接口,因此调用者不必担心细节。

class Example:
    def __init__(self):
        self._words = ['hello']
    def add_word(self):
        self._words.append('world')
    def display(self):
        print(*self.words)

x = Example()
x.add_word()
x.display()

在示例中,调用将信息返回add_word给顶层代码 - 但我们不需要查找信息,而是直接调用display。3

另请参阅:在 Python 中的方法之间传递变量?

封闭范围

这是使用嵌套函数时罕见的特殊情况。这里没什么好说的——它的工作方式与全局作用域相同,只是使用关键字nonlocal而不是global。4

全局范围:修改全局

一般来说,在设置全局范围后再更改其中的任何内容都是一个坏主意。这会使代码更难推理,因为使用该全局范围的任何内容(除了负责更改的内容)现在都有一个“隐藏”的输入源。

如果您仍想这样做,语法很简单:

words = ['hello']
def add_global_word():
    words.append('world')

add_global_word() # `words` is changed

全局范围:分配给新的或现有的全局

实际上是修改全局变量的一个特例。我并不是说赋值是一种修改(它不是)。我的意思是,当你分配一个全局名称时,Python 会自动更新一个代表全局命名空间的字典。你可以用 获取该字典globals(),并且可以修改该字典,它实际上会影响存在的全局变量。(即,返回的globals()是字典本身,而不是副本。)5

但请不要。这比前一个想法更糟糕。如果你真的需要通过分配给全局变量来从函数中获取结果,请使用global关键字告诉 Python 应该在全局范围内查找该名称:

words = ['hello']

def replace_global_words():
    global words
    words = ['hello', 'world']

replace_global_words() # `words` is a new list with both words

全局作用域:赋值或者修改函数本身的属性

这是一个罕见的特殊情况,但现在你已经看到了其他示例,理论应该很清楚了。在 Python 中,函数是可变的(即你可以在它们上设置属性);如果我们在顶层定义一个函数,它就在全局命名空间中。所以这实际上只是修改一个全局变量:

def set_own_words():
    set_own_words.words = ['hello', 'world']

set_own_words()
print(*set_own_words.words)

我们不应该真的用它向调用者发送信息。它有全局变量的所有常见问题,而且更难理解。但是,在函数内部设置函数的属性很有用,这样函数就可以在调用之间记住一些东西。(这类似于方法通过修改来记住调用之间的事情self。)functools标准库就是这样做的,例如在cache实现中。

内置范围

这不起作用。内置命名空间不包含任何可变对象,并且您无法分配新的内置名称(它们将进入全局命名空间)。


一些在 Python 中不起作用的方法

只是在函数结束前计算一些东西

在其他一些编程语言中,存在某种隐藏变量,每次计算时都会自动获取最后一次计算的结果;如果您到达函数末尾而没有return执行任何操作,则会返回该结果。这在 Python 中不起作用。如果您到达函数末尾而没有return执行任何操作,则函数返回None

分配给函数名称

在其他一些编程语言中,允许(或期望)将 赋值给与函数同名的变量;并在函数结束时返回该值。这在 Python 中仍然行不通。如果您到达末尾时没有执行return任何操作,您的函数仍会返回None

def broken():
    broken = 1

broken()
print(broken + 1) # causes a `TypeError`

如果您使用以下关键字,似乎至少可以通过这种方式使用该值global

def subtly_broken():
    global subtly_broken
    subtly_broken = 1

subtly_broken()
print(subtly_broken + 1) # 2

但这当然只是分配给全局变量的一个特例。它有一个大问题——同一个名字不能同时指代两个东西。通过这样做,该函数替换了自己的名字。所以下次它会失败:

def subtly_broken():
    global subtly_broken
    subtly_broken = 1

subtly_broken()
subtly_broken() # causes a `TypeError`

分配给参数

有时人们希望能够将函数的一个参数赋值给它,并让它影响用于相应参数的变量。然而,这行不通:

def broken(words):
    words = ['hello', 'world']

data = ['hello']
broken(data) # `data` does not change

就像 Python 返回值而不是变量一样,它也传递值而不是变量。words是一个本地名称;根据定义,调用代码对该命名空间一无所知。

我们看到的一种有效方法是修改传入的列表。这样做之所以有效,是因为如果列表本身发生变化,那么它就会发生变化——无论它使用什么名称,或者代码的哪一部分使用该名称。但是,分配一个新列表words 不会导致现有列表发生变化。它只是使列表words开始成为不同列表的名称。

有关更多信息,请参阅如何通过引用传递变量?。


1至少,不是为了取回值。如果你想使用关键字参数,你需要知道关键字名称是什么。但通常,函数的意义在于它们是一种抽象;你只需要知道它们的接口,而不需要考虑它们在内部做什么。

2在 2.x 中,print是一个语句而不是一个函数,因此这并不是直接调用另一个函数的示例。但是,print foo()仍然可以与 2.x 的 print 语句一起使用,所以也是如此print(foo())(在这种情况下,额外的括号只是普通的分组括号)。除此之外,2.7(最后一个 2.x 版本)自 2020 年初以来一直不受支持- 这几乎是正常时间表的 5 年延长。但是,这个问题最初是在 2010 年提出的。

3再次强调:如果方法的目的是更新对象,则不要同时更新return值。有些人喜欢使用 return ,self以便可以“链式”调用方法;​​但在 Python 中,这被认为是糟糕的风格。如果您想要那种“流畅”的界面,那么不要编写更新的方法self,而要编写创建类的新修改实例的方法。

4当然,如果我们修改值而不是分配,则我们不需要任何一个关键字。

5还有一个可以为locals()您提供局部变量字典的函数。但是,这不能用于创建新的局部变量 - 在 2.x 中,此行为未定义,而在 3.x 中,字典是动态创建的,并且赋值给它没有任何效果。Python 的一些优化依赖于提前知道函数的局部变量。

解决方案 3:

>>> def foo():
    return 'hello world'

>>> x = foo()
>>> x
'hello world'

解决方案 4:

您可以使用global语句,然后实现您想要的功能,而无需从函数返回值。例如,您可以执行以下操作:

def foo():
    global x 
    x = "hello world"

foo()
print x

上述代码将打印“hello world”。

但请注意,“全局”的使用根本不是一个好主意,最好避免在我的示例中显示的用法。

另请查看关于Python 中全局语句的用法的相关讨论。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用