我有一个非常基本的问题.
假设我调用一个函数,例如,
def foo():
x = 'hello world'
Run Code Online (Sandbox Code Playgroud)
如何让函数以这样的方式返回x,我可以将它用作另一个函数的输入或者在程序体内使用变量?
当我使用return并在另一个函数中调用该变量时,我得到一个NameError.
Tim*_*ker 21
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
Run Code Online (Sandbox Code Playgroud)
直接的方法是return从函数中获取一个值,正如您所尝试的那样,然后让调用代码使用该值。这通常是您想要的。从函数获取信息的自然、简单、直接、显式的方法就是返回函数return。从广义上讲,函数的目的是计算一个值,并return表示“这是我们计算的值;我们已经完成了”。
return这里的主要技巧是return返回一个值,而不是一个变量。因此return x ,不会x启用调用代码在调用函数后使用,并且不会x修改调用上下文中的任何现有值。(这大概就是你得到一个的原因NameError。)
return我们在函数中使用后:
def example():
x = 'hello world'
return x
Run Code Online (Sandbox Code Playgroud)
我们需要编写调用代码来使用返回值:
result = example()
print(result)
Run Code Online (Sandbox Code Playgroud)
这里的另一个关键点是对函数的调用是一个表达式,因此我们可以像使用加法结果一样使用它。正如我们可能会说的那样result = 'hello ' + 'world',我们可能会说result = foo()。之后,result是我们自己的该字符串的本地名称,我们可以用它做任何我们想做的事情。
x如果需要,我们可以使用相同的名称。或者我们可以使用不同的名称。调用代码不必知道函数的编写方式或函数使用的名称。1
我们可以直接使用该值来调用另一个函数:例如,print(foo()). 2我们可以直接返回值:简单地返回return 'hello world',而不分配给x。(再次强调:我们返回的是一个值,而不是一个变量。)
该函数每次只能return调用一次。return终止函数 - 同样,我们刚刚确定了计算结果,因此没有理由进一步计算。因此,如果我们想要返回多条信息,我们将需要提供一个对象(在 Python 中,“值”和“对象”实际上是同义词;这对于其他一些语言来说效果不太好。 )
我们可以在返回行上创建一个元组;或者我们可以使用字典、a namedtuple(Python 2.6+)、a types.simpleNamespace(Python 3.3+)、a 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
Run Code Online (Sandbox Code Playgroud)
如果该值是我们自己的类的实例,我们也可以分配给一个属性:
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
Run Code Online (Sandbox Code Playgroud)
分配给属性不适用于内置类型,包括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()
Run Code Online (Sandbox Code Playgroud)
在示例中,调用add_word将信息返回给顶层代码 - 但我们没有寻找它,而是继续调用display。3
另请参阅:在 Python 中的方法之间传递变量?
这是使用嵌套函数时罕见的特殊情况。这里没什么可说的 - 它的工作方式与全局作用域相同,只是使用关键字nonlocal而不是global. 4
一般来说,在首先设置之后再更改全局范围内的任何内容都是一个坏主意。它使代码更难以推理,因为任何使用该全局的东西(除了负责更改的东西)现在都有一个“隐藏”的输入源。
如果您仍然想这样做,语法很简单:
words = ['hello']
def add_global_word():
words.append('world')
add_global_word() # `words` is changed
Run Code Online (Sandbox Code Playgroud)
这实际上是修改全局的一个特例。我并不是说赋值是一种修改(它不是)。我的意思是,当您分配全局名称时,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
Run Code Online (Sandbox Code Playgroud)
这是一种罕见的特殊情况,但现在您已经看到了其他示例,理论应该很清楚了。在Python中,函数是可变的(即你可以在它们上设置属性);如果我们在顶层定义一个函数,那么它就在全局命名空间中。所以这实际上只是修改全局:
def set_own_words():
set_own_words.words = ['hello', 'world']
set_own_words()
print(*set_own_words.words)
Run Code Online (Sandbox Code Playgroud)
我们不应该真正使用它来向调用者发送信息。它具有全局变量的所有常见问题,而且更难理解。但从函数内部设置函数的属性可能很有用,以便函数在调用之间记住一些内容。(这类似于方法如何通过修改来记住调用之间的内容self。)functools标准库就是这样做的,例如在cache实现中。
这是行不通的。内置命名空间不包含任何可变对象,并且您无法分配新的内置名称(它们将进入全局命名空间)。
在其他一些编程语言中,有某种隐藏变量,每次计算时都会自动获取上次计算的结果;如果您到达函数末尾而没有return执行任何操作,则会返回该函数。这在 Python 中行不通。如果您到达末尾时没有return执行任何操作,则函数将返回None。
在其他一些编程语言中,允许(或期望)分配给与函数同名的变量;在函数结束时,返回该值。这在 Python 中仍然行不通。如果您没有执行任何操作就到达末尾return,您的函数仍会返回None。
def broken():
broken = 1
broken()
print(broken + 1) # causes a `TypeError`
Run Code Online (Sandbox Code Playgroud)
如果您使用关键字,您似乎至少可以这样使用该值global:
def subtly_broken():
global subtly_broken
subtly_broken = 1
subtly_broken()
print(subtly_broken + 1) # 2
Run Code Online (Sandbox Code Playgroud)
但这当然只是分配给全局的一个特例。但它有一个大问题——同一个名字不能同时指代两个事物。通过这样做,该函数替换了它自己的名称。所以下次会失败:
def subtly_broken():
global subtly_broken
subtly_broken = 1
subtly_broken()
subtly_broken() # causes a `TypeError`
Run Code Online (Sandbox Code Playgroud)
有时,人们期望能够分配给函数的参数之一,并使其影响用于相应参数的变量。但是,这不起作用:
def broken(words):
words = ['hello', 'world']
data = ['hello']
broken(data) # `data` does not change
Run Code Online (Sandbox Code Playgroud)
就像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提供值。有些人喜欢返回self,以便可以“链接”方法调用;但在 Python 中,这被认为是糟糕的风格。如果您想要那种“流畅”的界面,那么不要编写 update 方法self,而是编写创建新的、修改后的类实例的方法。
4当然,如果我们要修改值而不是赋值,则不需要任何一个关键字。
5还有一个locals()可以为您提供局部变量的字典。但是,这不能用于创建新的局部变量 - 该行为在 2.x 中未定义,而在 3.x 中,字典是动态创建的,分配给它没有任何效果。Python 的一些优化依赖于提前知道函数的局部变量。
| 归档时间: |
|
| 查看次数: |
74604 次 |
| 最近记录: |