2

I am trying to understand the variable scope in Python (3.x), but below is an example of code that does not work and I am not sure why.

def function_a(A):
    function_b()

def function_b():
    print(A)

function_a(1)

Which results in NameError: name 'A' is not defined

So, the way I would think it works is that function_a() and function_b() are definid. Afterwards I run function_a() where A gets assigned the value of 1.

So in the scope of function_a() the variable A=1 exists.

Then function_b() gets called and is meant to print the value of variable A. A does not exist in the scope of function_b(). Hence I would expect it to look a level higher, which would be the scope of function_a() because function_b() is run within function_a().

But clearly, I am getting that wrong. What actually happens?

1
  • 2
    While it's not directly relevant to this question, I suspect that you may find this article helpful: Facts and myths about Python names and values, which was written by SO veteran Ned Batchelder. Commented Oct 4, 2017 at 8:25

4 Answers 4

6

Just because you have called function_b inside of function_a doesn't mean it would inherit the scope of function_a and for good reasons. The function gets the scope from where it's defined, not where it's called.

If you want to accomplish something like closures you should try to define function_b inside of function_a.

def function_a(A):
     def function_b():
         print(A)

With that said, I don't really see a use case for a closure here. You're better off passing the variable as an argument. That way it would be more reusable and testable.

def function_a(A):
    function_b(A)

def function_b(A):
    print(A)
Sign up to request clarification or add additional context in comments.

Comments

3

Hence I would expect it to look a level higher, which would be the scope of function_a() because function_b() is run within function_a()

It does look higher. But the scope of function_a is not higher than that of function_b. The scopes are somewhat independent. Don't confuse scoping hierarchies with stack frames. The next scope in the hierarchy is the module scope, which here, is the global scope; A is not in the global scope.

To access A in function_b, you can pass it as a parameter to function_b or define A in the module scope.

3 Comments

Upvoted, but I think you should consider wording it in a way that's easier for newbies to understand - IMO there's a bit too much technical lingo ("scoping hierarchies", "stack frames") here, when you could've expressed the same thing in a much clearer way by saying something like "the function's scope depends on where it was defined, not where it was called".
@Rawing Thanks for the feedback. I guess that's what the accepted answer did, and most probably why it got accepted. Learning...
As the one who has asked the question I can confirm that it was indeed the reason why I accepted @hspandher 's answer. I also upvoted your answer though because it did indeed answer the question. I just thought the other answer made it a bit clearer by saying 'The function gets the scope from where it's defined, not where it's called.' Thanks for your answer though
1

What happens is, in function_b, a search in the global scope is done and since the global scope doesn't define a name A and no enclosing function scope exists, you get a NameError.

If an enclosing scope existed:

def function_a(A):
    def function_b():
        print(A)
    function_b()
function_a(1)  # prints 1

Or a global name A was defined:

A = 2
def function_a(A):
    function_b()

def function_b():
    print(A)

function_a(1)  # prints 2 (finds global name A)

you'd get the result of A printed since the look-up would succeed.

Comments

0

I assume that this occurs because python does not place the bindings to parameters in the same namespace that a variable binding receives. The parameter bindings exist only within the function's scope.

The proper way to achieve your effect is using python closures (which enclose their parent function's namespace):

def function_a(A):
    def function_b():
        print(A)

    function_b()

function_a(1)

You may simply pass the value A to both functions, but this may suggest that you should make a class containing A (especially if A is required by many functions):

class A(object):
    def __init___(self, a):
        self.a = a

    def function_a(self):
        self.function_b()

    def function_b(self):
        print(self.a)

 foo = A(1)
 foo.function_a()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.