【问题标题】:Can a function and local variable have the same name?函数和局部变量可以同名吗?
【发布时间】:2021-12-16 10:34:55
【问题描述】:

这是我的意思的一个例子:

def foo():
    foo = 5
    print(foo + 5)

foo()
# => 10

代码不会产生任何错误并且可以完美运行。这与变量和函数不应该具有相同名称的想法相矛盾,除非您覆盖它们。 为什么会起作用?当应用于实际代码时,我应该使用不同的函数/局部变量名称,还是完全可以?

【问题讨论】:

  • 补充一下khelwood所说的,由于内部foo是一个局部变量,你实际上可以将foo声明为一个全局变量,并像这样分配给它。该函数仍将正确完成,并且对foo 的任何进一步引用都将在分配的值中产生。
  • 这样做你唯一真正破坏的是递归调用foo 的能力。如果您不需要这样做,我真的没有看到问题(尽管我也没有看到无缘无故隐藏全局变量的意义:几乎可以肯定有一个更好的名称可用于局部变量)。
  • @chepner 我要更进一步,强调你不应该有同名的变量和函数。即使它有效,一年后阅读代码的人也会感到困惑——包括你自己。
  • @MarkRansom 嗯,在 Pascal 中,这甚至是标准的,这就是您从那里的函数返回值的方式。有时我发现它比使用 result 变量更清晰。
  • @KellyBundy 自从我写任何 Pascal 以来已经有 30 年了,你呢?这个问题专门针对 Python。你会在我的代码中找到很多result,因为这是一个很好的约定。而且它是一种约定,不会使编写递归函数成为不可能。

标签: python scope


【解决方案1】:

foo = 5 在你的函数中创建一个局部变量。 def foo 创建一个全局变量。这就是为什么它们可以使用相同的名称。

如果您在foo() 函数中引用foo,您指的是局部变量。如果您在该函数之外引用foo,您指的是全局变量。

由于它显然会导致试图遵循代码的人感到困惑,因此您可能不应该这样做。

【讨论】:

  • 另外,如果你尝试让foo 递归你会失败。 def foo(n): if n > 1: foo(n-1); foo = 5 这里对 foo(n-1) 的调用将查找 foo 作为局部变量,即使局部变量稍后才第一次分配......
【解决方案2】:

其他人还没有提到过:Python 是一种动态 语言,几乎没有静态检查。所以当你写

def foo():
    foo = 5
    print(foo + 5)

您可能一直认为这是一个“矛盾”——foo 怎么可能同时是一个函数和一个整数变量?一阶答案是“这是两个不同的foos”,因为内部的foo 只是一个局部变量,与全局名称foo 无关。但是 make 它是全局的,并且代码仍然有效!

def foo():
    global foo
    foo = 5
    print(foo + 5)

foo()  # OK, prints 10

这个代码中只有一个foo!但是,它不是“既是变量又是函数”。首先,在第 1-4 行,我们将 foo 定义为具有特定主体的函数 - 但我们还没有执行该主体。 在第 4 行之后,全局 foo 拥有一个函数。然后,在第 6 行,我们实际上调用了 foo 引用的函数,它执行了它的主体。主体所做的第一件事是将5 分配给foo... 现在 foo 保存一个整数。通过运行为foo 分配新值的代码,我们更改了foo 的值。它曾经是那个功能。现在是 5。在像 Python 这样的动态类型语言中,这没有什么问题。

def foo():
    global foo
    foo = 5
    print(foo + 5)

foo()  # OK, prints 10 (and incidentally assigns a new value to foo)
foo()  # Raises TypeError: 'int' object is not callable

【讨论】:

【解决方案3】:

答案是肯定的。 函数是 Python 中的一等对象。 foo 函数和foo 变量之间没有根本区别。这两个都是对内存的引用,并且都有一个范围,所以它们都等价于一个变量。 如果你已经将 foo 定义为一个函数,然后不在本地覆盖它,它就会像一个全局变量(取自上层作用域):

def foo():
  print(foo)

然后如果你用局部变量覆盖它,它只会在函数范围内定义一个局部变量:

def foo():
  foo = 3
  print(foo)

在 Python 中,每个引用(变量)都可以在特定范围的生命周期内被覆盖。你可以试试:

def foo(): pass
foo = 3

这将覆盖 foo 的值,它现在将指向 3 而不是内存中的函数 foo。

【讨论】:

    【解决方案4】:

    嗯,这是因为每个foo 的变量范围。函数foo()在全局范围内,因此可以在其他函数内部和函数外部调用。

    然而,变量foo 在本地范围内。这意味着它只“存在”在函数内部,不能在函数外部调用或引用。

    因此,每个不同的函数在被调用时都会创建自己的局部作用域,而在作用域内创建的变量会在作用域被销毁(函数结束)时被遗忘。

    全局变量可以在局部范围内访问,局部变量不能在全局范围内访问。

    如果要在全局范围内创建变量,可以这样调用:

    global var
    

    以下是全局和局部范围的示例:

    var=1
    def foo1():
      var=3
    foo1()
    print(var) #prints 1 because the "var" in foo1() is locally assigned
    def foo2():
      global var
      var=2
    foo2()
    print(var) #prints 2 because "var" is global
    

    所以,您的函数有效,因为它只在本地分配名称 foo,而不是全局分配。

    然而,如果您稍后在该函数中调用 foo(),它将引发错误,因为该本地范围已将 int 值分配给 foo,而不是函数,因此不可调用.

    【讨论】:

      【解决方案5】:

      使用同名的另一个问题显然是编写递归时的歧义:

      def fibo(n: int) -> int:
          if n <= 1:
              fibo = 1
          else:
              fibo = fibo(n - 1) + fibo(n - 2)
          return fibo
      

      结果:

      UnboundLocalError: 赋值前引用了局部变量 'fibo'

      这是因为在函数内部定义了fibo。 当 Python 尝试运行 fibo(n - 1) 时,它会搜索本地的 fibo 变量,但找不到它的值。

      【讨论】:

        【解决方案6】:

        只是一个简短的评论:pylint 用警告标记了这一点。 pylint 是我们的朋友!

        【讨论】:

          【解决方案7】:

          在python中变量的范围是通过从当前最内层范围搜索到全局范围来确定的。

          Python Scopes and Namespaces(强调我的)

          • 命名空间是从名称到对象的映射。
          • 范围是 Python 程序的文本区域,其中命名空间可直接访问。这里的“直接访问”是指 对名称的非限定引用试图在 命名空间。

          在执行过程中的任何时候,都有 3 或 4 个嵌套范围 命名空间可直接访问:

          1. 首先搜索的最内层范围包含本地名称
          2. 任何封闭函数的范围,从最近的封闭范围开始搜索,包含非本地的,但也 非全局名称
          3. 倒数第二个范围包含当前模块的全局名称
          4. 最外层范围(最后搜索)是包含内置名称的命名空间。

          Python 的一个特殊之处在于——如果没有 global 或者 nonlocal 声明生效 - 对名称的赋值总是进入 最里面的范围。

          在示例中,

          # Current module's scope starts here.
          
          def foo():
              # Local Scope Starts here.
              foo = 5
              print(foo + 5)
              # Local scope Ends here.
          
          foo()
          
          # Current module's scope ends here.
          

          应用上述规则解决范围

          • foo = 5 属于 本地范围,因为没有 globalnonlocal 语句。函数内部的foo属于函数的局部作用域。

          • print(foo + 5) 解析为函数内部存在的foo,因为 python 从最内部的范围开始搜索。适用范围解析规则 1 和 2。这里最里面的作用域和封闭函数的作用域是一样的。

          • foo() 在模块级别范围内调用。 Python 开始搜索最里面的可用范围,这里是模块级。因此,它解析为在模块级别定义的函数。

          另一个例子,

          def foo():
              print(foo + 5)
          
          foo()
          # TypeError: unsupported operand type(s) for +: 'function' and 'int'
          
          • foo() 在模块级别范围内调用。 Python 开始搜索最里面的可用范围,这里是模块级。因此,它解析为在模块级别定义的函数。
          • print(foo + 5) 也将解析为模块级 foo 函数,因为 python 在封闭函数范围内开始搜索但未找到匹配项。移动到下一个范围,即模块级别并找到匹配项。匹配是foo 函数。因此,错误。

          另一个例子,

          def foo():
              foo = "str"
              def inner():
          #(1)    foo = 1 # Commented this line
                  print(foo)
              inner()
              print(foo)
          
          foo()
          # str
          # str
          
          # Uncommenting (1) gives output as
          
          foo()
          # 1
          # str
          

          阅读更多关于Scoping rules

          【讨论】:

            猜你喜欢
            • 2011-06-16
            • 1970-01-01
            • 1970-01-01
            • 2015-02-26
            • 2017-05-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-02-12
            相关资源
            最近更新 更多