47

Here's an example of what I mean:

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

foo()
# => 10

The code doesn't produce any errors and runs perfectly. This contradicts the idea that variables and functions shouldn't have the same name unless you overwrite them. Why does it work? And when applied to real code, should I use different function/local variable names, or is this perfectly fine?

Ch3steR
  • 20,090
  • 4
  • 28
  • 58
jianmin-chen
  • 603
  • 7
  • 9
  • 2
    To add on to what khelwood said, since the inner `foo` is a local variable, you can actually declare `foo` to be a global variable, and assign to it like that. The function will still complete without error, and any further references to `foo` will yield in the assigned value. – Conor O'Brien Dec 16 '21 at 19:42
  • 2
    The only thing you are really breaking by doing this is the ability to call `foo` recursively. If you don't need to do that, I don't really see a problem (though I don't see the point in shadowing a global variable for no reason, either: there's almost certainly a better name to use for the local variable). – chepner Dec 16 '21 at 22:09
  • 2
    @chepner I'm going to go a step further and state *emphatically* that you should not have a variable and a function with the same name. Even if it works, it's just going to be confusing to whoever's reading the code a year later - including yourself. – Mark Ransom Dec 17 '21 at 00:58
  • @MarkRansom Hmm, in Pascal, that's even standard, that's how you return a value from a function there. And sometimes I find it clearer than using a `result` variable. – Kelly Bundy Dec 19 '21 at 11:11
  • 1
    @KellyBundy it's been about 30 years since I've written any Pascal, how about you? This question is about Python specifically. You'll find a lot of `result` in my code, because it's a good convention. And it's a convention that doesn't make it impossible to write recursive functions. – Mark Ransom Dec 19 '21 at 17:03
  • @MarkRansom Not sure I ever *wrote* Pascal, might just remember this from reading. What if you want to indicate that the function isn't recursive? Could be seen as an advantage :-) – Kelly Bundy Dec 19 '21 at 18:01

7 Answers7

54

foo = 5 creates a local variable inside your function. def foo creates a global variable. That's why they can both have the same name.

If you refer to foo inside your foo() function, you're referring to the local variable. If you refer to foo outside that function, you're referring to the global variable.

Since it evidently causes confusion for people trying to follow the code, you probably shouldn't do this.

khelwood
  • 55,782
  • 14
  • 81
  • 108
  • 6
    Also, if you try to make `foo` recursive you will fail. `def foo(n): if n > 1: foo(n-1); foo = 5` here the call to `foo(n-1)` will lookup `foo` as a local variable even though the local variable is only assigned later on for the first time... – GACy20 Dec 17 '21 at 08:28
  • [shadowing](https://en.wikipedia.org/wiki/Variable_shadowing) – Vorac Dec 24 '21 at 04:32
30

Something nobody else has mentioned yet: Python is a dynamic language, with very little static checking. So when you write

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

you might have been thinking of that as a "contradiction" — how can foo be a function and an integer variable at the same time? The first-order answer is "It's two different foos," because the inner foo is just a local variable, unrelated to the global name foo. But make it global, and the code still works!

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

foo()  # OK, prints 10

In this code there is only one foo! However, it is not "both a variable and a function." First, on lines 1–4, we define foo as a function with a certain body — but we do not execute that body yet. After line 4, the global foo holds a function. Then, on line 6, we actually call the function referred to by foo, which executes its body. The first thing the body does is assign 5 to foo... and now foo holds an integer. By running code that assigns a new value to foo, we've changed foo's value. It used to be that-function-there; now it's 5. In a dynamically typed language like Python, there's nothing wrong with this.

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
Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • 4
    Good answer. Also the concept of two different `foo`s called **variable shadowing** - https://en.wikipedia.org/wiki/Variable_shadowing – Sanya_Zol Dec 17 '21 at 15:38
11

The answer is yes. Functions are first-class objects in Python. There is no fundamental difference between the foo function and the foo variable. Both of those are references to the memory and both have a scope, so they are both equivalent to a variable. If you have defined foo as a function and then don't overwrite it locally, it will be like a global variable (taken from the upper-level scope):

def foo():
  print(foo)

and then if you overwrite it with a local variable, it will just define a local variable within the function scope:

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

In Python, every reference (variable) can be overwritten for the life span of a particular scope. You can try:

def foo(): pass
foo = 3

This will overwrite the value of the foo and it will point now to 3 instead of the function foo in the memory.

1

Well, it is because of the scope of variable each foo is. The function foo() is in the global scope, therefore can be called within other functions and outside of functions.

The variable foo however, is in the local scope. That means it only "exists" inside of the function, it cannot be called or referenced outside of the function.

So, each different function creates its own local scope when it is called, and the variables created within are forgotten as soon as the scope is destroyed (the function ends).

Global variables can be accessed within local scope, local variables cannot be accessed in global scope.

If you want to create the variable in the global scope, call it like this:

global var

Here is an example of global and local scope:

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

So, your function works because it i only assigning the name foo locally, not globally.

However, if you call foo() later in that function, it will raise an error because that local scope has assigned an int value to foo, not a function, therefore is not callable.

Agent Biscutt
  • 712
  • 4
  • 17
1

In python variable's scope is determined by searching from the current innermost scope to the global scope.

Python Scopes and Namespaces(Emphasis mine)

  • A namespace is a mapping from names to objects.
  • A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.

At any time during execution, there are 3 or 4 nested scopes whose namespaces are directly accessible:

  1. the innermost scope, which is searched first, contains the local names
  2. the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names
  3. the next-to-last scope contains the current module’s global names
  4. the outermost scope (searched last) is the namespace containing built-in names.

A special quirk of Python is that – if no global or nonlocal statement is in effect – assignments to names always go into the innermost scope.

In the example,

# 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.

Applying the above rules to resolve the scope of

  • foo = 5 belongs to local scope as there's no global or nonlocal statement. foo which is inside the function belongs to the local scope of the function.

  • print(foo + 5) resolve to the foo present inside the function as python starts searching from the innermost scope. Scope resolution rules 1 and 2 apply. The innermost scope here is the same as the scope of enclosing function.

  • foo() is called at the module-level scope. Python starts searching the innermost scope available which here is module-level. Hence, it resolved to the function defined at the module level.

Another example,

def foo():
    print(foo + 5)

foo()
# TypeError: unsupported operand type(s) for +: 'function' and 'int'
  • foo() is called at the module-level scope. Python starts searching the innermost scope available which here is module-level. Hence, it resolved to the function defined at the module level.
  • print(foo + 5) would also resolve to module-level foo function because python starts the search in the enclosing function scope but found no match. Moved to next scope i.e module-level and found a match. The match is foo function. Hence, the error.

Another example,

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

Read more about Scoping rules

Ch3steR
  • 20,090
  • 4
  • 28
  • 58
0

Another problem with using the same name is obviously the ambiguity when writing recursions:

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

Results in:

UnboundLocalError: local variable 'fibo' referenced before assignment

It happens because when fibo is defined inside the function. When Python tries to run fibo(n - 1) it searches the local fibo variable, and fails to find its value.

Yam Mesicka
  • 6,243
  • 7
  • 45
  • 64
0

Just a short remark: pylint marks this with a warning. pylint is our friend!

M.Nemes
  • 49
  • 1
  • 4