Python; 为什么传递函数参数不是必须的?

4

在Python中,我认为将要在函数内使用的变量传递给该函数并不是强制性的。

以下是示例代码:

def functionWithoutArgs():
    print(theList)
    theList.append('asdf')
    print(theList)
    theNewList = theList + ['qwer']
    print(theNewList)
    return theNewList

theList = ['this', 'is', 'a', 'list']
theLastList = functionWithoutArgs()
print(theLastList)

程序运行结果如下(无错误或警告):

['this', 'is', 'a', 'list']
['this', 'is', 'a', 'list', 'asdf']
['this', 'is', 'a', 'list', 'asdf', 'qwer']
['this', 'is', 'a', 'list', 'asdf', 'qwer']

我很惊讶没有出现任何异常。因为(在我看来),允许函数使用未传递给它们的变量会严重混淆该函数与其他代码的交互方式。
据我所知,函数的整个目的就是将程序拆分为小部分,并清楚地定义它们与其他代码的交互方式。
问题:
为什么Python允许在函数中使用未传递给该函数的变量?
附加信息:
示例中存在过多代码的原因是这个线程 Use a Variable in a Function Without Passing as an Argument 其中被接受的答案声称,仅当被调用的函数不修改或重命名变量时才允许使用未传递的变量(至少我是这样理解的),而上述代码反例。
我知道这个问题有点主观或基于意见,因为它是一个“为什么…”问题,但我认为这是如此基础,以至于必须有某种共识论证。此外,对于许多新的Python程序员来说,这将是有用的知识。

2
“...不会修改...变量”。我认为您误解了链接的答案。列表是可变的,因此对它们进行更改不会导致它们重新绑定到名称,它仍然是同一个对象。如果您尝试增加全局整数(不可变),而没有使用“global”,那么它将抛出错误。 - roganjosh
2
全局变量的作用是它们在模块范围内(几乎)可以被访问,这有时很有用,而且不是 Python 的独特功能,例如 R 也类似。 - Chris_Rands
2
全局变量是一个广泛使用的结构。在您的情况下,一个变量被使用,但并没有令人信服的必要性。我认为期望Python解释器(或任何其他动态语言)能够检测到这种情况有些值得商榷。对于这种严格性,其他语言更加适合。 - guidot
你可能需要重新阅读Python作用域和命名空间以获取更多背景信息。 - guidot
2个回答

3
完整的答案需要深入解释整个语言的运作方式,这已经有文档记录 - 官方关于作用域和命名空间的文档可能是最好的起点。
简而言之,在Python中,所有东西都是对象 - 包括函数、类和模块 - 并且没有明确的函数、模块和类的命名空间。也就是说,在以下代码中:
FOO_FACTOR = 2

def foo(arg):
    return bar(arg) * FOO_FACTOR

def bar(arg):
    return arg + 3

foo中,bar实际上就像FOO_FACTOR一样是全局变量。如果不允许函数访问全局作用域中的名称,那么您必须显式传递barFOO_FACTOR。如果需要特殊声明才能访问全局变量,则每次函数使用另一个函数、类或模块时都必须编写大量样板代码,这将非常不实用。
允许函数使用未传递给它们的变量会严重混淆该函数与其他代码的交互方式。重新绑定(甚至只是改变)非局部名称确实可能会导致很多问题,涉及可读性(对代码进行推理的能力)、可测试性等方面,也可能涉及并发性和可重入性,应尽可能避免。
但从实际角度来看,你几乎无法避免至少具有“只读”全局变量(符号常量、其他函数、类、模块等),有时你还需要至少一些函数确实会改变/重新绑定全局名称(例如考虑应用程序设置)- 这里的重点是这些函数应该仅在应用程序启动时调用以定义您的符号常量等。

1
这种做法使用了在作用域之外定义的“影子名称”,通常被认为是非常糟糕的做法。许多IDE和代码检查工具都会在此处报告错误或警告。
实际上,许多动态类型语言都允许这种行为。我认为这是因为,在编写函数时,我们希望能够按任意顺序编写函数。对于像C这样将声明和定义分开的语言,我们可以确保所有内容都在定义部分中声明。
然而,对于Python这样不为名称声明类型的语言来说,情况就有所不同了。如果我们不允许来自作用域之外的名称,那么我们将无法进行相互递归和许多编程结构!

实际上,许多(如果不是全部)动态类型语言都允许这种行为。这与类型关系很少有关 - 在C、C++、Pascal等几乎所有过程式语言中,您都可以使用全局变量。 - bruno desthuilliers
但在那些语言中,如果您在函数中使用变量/函数而未声明它,则会收到错误/警告。(在我看来,这更接近于OP所寻求的行为)在动态类型语言中,解释器在函数定义时不需要知道名称实际上是什么。 - Qing
这确实是我理解你的答案的方式,但你写的方式相当不清楚,可能会误导编程新手,因此我发表了评论。 - bruno desthuilliers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接