如何从函数中获取(“返回”)结果(输出)?我稍后如何使用结果?
- 2024-11-19 08:38:00
- admin 原创
- 8
问题描述:
假设我有一个如下函数:
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 中全局语句的用法的相关讨论。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件