4

It seems to me that it is not mandatory to pass the variables you want to use inside a function to that function in python.

This code:

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)

gives me this output (without errors or warnings):

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

I was surprised that no exceptions were raised. This because (it seems to me that) allowing functions to use variables that have not been passed to them obfuscates how that function interacts with other code pretty severely.
As far as I understand the whole point of functions is to break programs into small parts that have their interaction with the rest of the code clearly defined.

Question:
Why does python allow using variables inside a function that has not been passed to that function?

Additional info:
The reason for the excess code in the example is this thread Use a Variable in a Function Without Passing as an Argument where the accepted answer claims that using unpassed variables is only allowed when the called function does not modify or rename the variable (as I understood at least), which the above code counterexamples.

I understand that this question is borderline subjective or opinion based as it is a "Why..." question, but I felt that this is so fundamental that there has to be some kind of consensus argument for this. In addition, it would be useful knowledge for many new python programmers.

Nazim Kerimbekov
  • 4,712
  • 8
  • 34
  • 58
Anton
  • 365
  • 2
  • 12
  • 2
    "... does not modify ... the variable". I think you've misunderstood the linked answer. Lists are mutable so altering them does not cause them to be re-bound to a name, it's still the same object. If you tried to increment a global integer (immutable), without `global`, then it will throw an error. – roganjosh Jun 20 '18 at 09:20
  • 2
    The point of global variables is they are accessible (almost) everywhere in the module scope, this is sometimes useful and not a unique feature of Python, for example R is similar – Chris_Rands Jun 20 '18 at 09:26
  • 2
    Global variables are a widely used construct. In your case one is used without convincing need. I find the expectation somewhat debatable, that this should be detected by the Python interpreter (or any other dynamic language). For that kind of strictness other languages are better suited. – guidot Jun 20 '18 at 09:42
  • You may want to re-read [Python Scopes and Namespaces](https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces) for more background. – guidot Jun 20 '18 at 09:55

2 Answers2

3

A complete answer would require an in depth explanation of how the whole language works, and this is already documented - the official doc about scoping and namespaces being probably the best starting point here.

To make a long story short, in Python everything is an object - including functions, classes and modules - and there's no distinct namespace for functions, modules and classes. IOW, in the following code:

FOO_FACTOR = 2

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

def bar(arg):
    return arg + 3

in foo, bar is actually a global variable just like FOO_FACTOR is. If functions were not allowed to access names in the global scope, you would have to explicitely pass bar and FOO_FACTOR, and if you needed a special declaration to access a global var you'd have to write a lot of boilerplate code each time a function uses another function, class or module, which would be quite impractical to say the least.

allowing functions to use variables that have not been passed to them obfuscates how that function interacts with other code pretty severely.

Well, rebinding (or even just mutating) non-local names can indeed cause quite a lot of problems - wrt/ readability (ability to reason about the code), testability etc, but also wrt/ concurrency and reentrancy, and should be avoided as much as possible.

BUT from a practical POV, you can hardly avoid having at least "read-only" globals (symbolic constants, other functions, classes, modules etc), and sometimes you also need at least a couple functions that will indeed mutate / rebind global names (think about application settings for example) - the point here being that those functions should only be called at application startup to define your symbolic constants etc.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
1

This practice is using "shadowing name" defined outside the scope and in general this is a very bad practice. Many IDEs and code inspection tools will report an error or a warning here.

Actually, many if not all dynamically typed languages allow this behavior. IMO, this is because, when writing functions, we want to be able to write the functions in any order we like. For a language like C that separates declaration and definition we can ensure that everything is declared in the definition part.

However, for python, which does not declare a type for names, it is a different story. If we do not allow names from outside scope and we suddenly cannot have mutual recursion and many programming constructs!

Qing
  • 33
  • 4
  • "Actually, many if not all dynamically typed languages allow this behavior" => this has very few to do with typing - you can use global variables in C, C++, Pascal etc, actually in almost all procedural languages. – bruno desthuilliers Jun 20 '18 at 09:59
  • But in those languages, if you use a variable/function in a function before declaring it, you will get an error/warning. (IMO, this is closer to the behavior that OP seeks) In a dynamically typed language, the interpreter does not need to know what the name actually is at function definition time. – Qing Jun 20 '18 at 13:01
  • That's indeed how _I_ understood your answer, but the way you wrote it is quite unclear and can be misleading for programming newbies, hence my comment. – bruno desthuilliers Jun 20 '18 at 13:10