【问题标题】:Python; Why passing function arguments not mandatoryPython;为什么传递函数参数不是强制性的
【发布时间】:2018-11-29 08:46:40
【问题描述】:

在我看来,将要在函数中使用的变量传递给 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程序员来说,这将是有用的知识。

【问题讨论】:

  • "...不修改...变量"。我认为您误解了链接的答案。列表是可变的,因此更改它们不会导致它们重新绑定到名称,它仍然是同一个对象。如果你试图增加一个全局整数(不可变),没有global,那么它会抛出一个错误。
  • 全局变量的要点是它们在模块范围内(几乎)可以访问,这有时很有用,而不是 Python 的独特功能,例如 R 类似
  • 全局变量是一种广泛使用的结构。在您的情况下,使用一个没有令人信服的需要。我发现这个期望有点值得商榷,这应该由 Python 解释器(或任何其他动态语言)检测到。对于这种严格性,其他语言更适合。
  • 您可能需要重新阅读Python Scopes and Namespaces 了解更多背景信息。

标签: python arguments


【解决方案1】:

这种做法是使用在范围之外定义的“影子名称”,通常是this is a very bad practice。许多 IDE 和代码检查工具会在此处报告错误或警告。

实际上,如果不是全部的话,很多动态类型语言都允许这种行为。 IMO,这是因为,在编写函数时,我们希望能够以我们喜欢的任何顺序编写函数。对于像 C 这样分离声明和定义的语言,我们可以确保在定义部分声明所有内容。

但是,对于没有为名称声明类型的python,情况就不同了。如果我们不允许来自外部范围的名称,我们突然之间就不能有相互递归和许多编程结构!

【讨论】:

  • "实际上,如果不是所有动态类型语言都允许这种行为" => 这与类型几乎没有关系 - 您可以在 C、C++、Pascal 等中使用全局变量,实际上几乎所有程序语言。
  • 但在那些语言中,如果在声明之前在函数中使用变量/函数,则会收到错误/警告。 (IMO,这更接近于 OP 寻求的行为)在动态类型语言中,解释器不需要在函数定义时知道名称实际上是什么。
  • 这确实是 理解你的答案的方式,但是你写它的方式很不清楚,可能会误导编程新手,因此我的评论。
【解决方案2】:

完整的答案需要深入解释整个语言的工作原理,这已经记录在案 - the official doc about scoping and namespaces 可能是这里最好的起点。

长话短说,在 Python 中,一切都是对象——包括函数、类和模块——并且函数、模块和类没有不同的命名空间。 IOW,在以下代码中:

FOO_FACTOR = 2

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

def bar(arg):
    return arg + 3

foo 中,bar 实际上是一个全局变量,就像FOO_FACTOR 一样。如果不允许函数访问全局范围内的名称,则必须显式传递barFOO_FACTOR,如果需要特殊声明来访问全局变量,则必须编写大量样板代码每次一个函数使用另一个函数、类或模块时,这至少可以说是非常不切实际的。

允许函数使用未传递给它们的变量 混淆了该函数如何与其他代码交互 严重。

好吧,重新绑定(甚至只是改变)非本地名称确实会导致很多问题 - wrt/可读性(推理代码的能力)、可测试性等,还有 wrt/并发和reentrancy,应该尽量避免。

但是从实际的 POV 来看,您几乎无法避免至少“只读”全局变量(符号常量、其他函数、类、模块等),有时您还需要至少一个几个确实会改变/重新绑定全局名称的函数(例如考虑应用程序设置) - 这里的重点是这些函数应该只在应用程序启动时调用来定义你的符号常量等。

【讨论】:

    猜你喜欢
    • 2018-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-27
    • 1970-01-01
    • 2014-01-26
    相关资源
    最近更新 更多