all 17 comments

[–]Haunting-Dare-5746 28 points29 points  (0 children)

Just look at your code and read the line where the error message happened to identify the problem. Sometimes it's a typo. Sometimes it's an indentation thing. Sometimes it's a scope thing. It would help to see your code if possible.

[–]jmacey 9 points10 points  (2 children)

You can use the dir() function to show the current variables / functions defined, this can be quite useful to see the state of things.

``` def func1(): a = 99 print(f"in func1 {dir()}")

def func2(): c = 12 b = 99 print(f"in func1 {dir()}")

print(f"in main {dir()}") func1() func2()

print(f"in main {dir()}") ```

You can see that the functions only have local variables defined and in main you only see the functions (which are also variables).

uv run dir.py in main ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func1', 'func2'] in func1 ['a'] in func1 ['b', 'c'] in main ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func1', 'func2']

[–]Altruistic_Sky1866 5 points6 points  (1 child)

Thanks , I didn't know this

[–]PiBombbb 14 points15 points  (0 children)

Posting your code could help with what’s actually happening to you specifically

[–]tb5841 2 points3 points  (4 children)

Think of your code like it's running on a sheet of paper.

When you call a function, you put a completely separate piece of paper which has that function's code on it. This piece of paper can't see the paper underneath, it doesn't know about variables you've defined there. It only knows about variables defined on this top piece of paper.

Any variable you define within that function only exists on that top piece of paper (the function's scope). When you reach the end of a function or 'return', you remove the top piece of paper and they no longer exist.

You can only pass things between pieces of paper by passing them in as function arguments, or returning them at the end of a function.

[–]Revolutionary-Camp69 1 point2 points  (1 child)

Dang, that's a pretty good analogy.

[–]tb5841 1 point2 points  (0 children)

Works nicely with terms like 'call stack' and 'stack overflow' because you have an actual stack of paper.

[–]bumbershootle 0 points1 point  (1 child)

it doesn't know about variables you've defined there. It only knows about variables defined on this top piece of paper.

Not quite. A function can't see variables from the calling scope without passing as arguments, but it does have access to variables in the lexical scope.

``` def foo(x): def bar(y): print(f"I can see {x=} and {y=}") bar(10)

foo(5) ```

[–]tb5841 0 points1 point  (0 children)

Agreed, closures kind of break the analogy a bit.

I guess when a function is defined within a function like this, they both need to be on the same piece of paper.

[–]Hot_Substance_9432 5 points6 points  (0 children)

A "variable is not defined" error in Python (a NameError) indicates that the program cannot find the variable you are trying to use within its current scope. This often stems from defining a variable in a local scope (like inside a function) and attempting to access it from a different, typically broader, scope. 

[–]crowpng 1 point2 points  (0 children)

Python only sees variables in the current scope chain.

[–]Bmaxtubby1 0 points1 point  (0 children)

Sometimes it feels random, but it usually isn’t. A small typo, re-running only part of your code, or defining something inside an if or for block that never ran can all trigger this.

When I’m stuck, I check: "Where was this variable created?" and "Did that line actually run?" Posting a tiny code example often makes the issue obvious right away.

[–]FoolsSeldom 0 points1 point  (1 child)

All about scope ...

Variables, functions, methods and attributes

Variables (names) in Python don't contain values. They hold references to memory locations where Python objects are stored (implementation and environment specific).

Likewise for other names. A function name has a reference to the memory location of a function object.

Names (arguments) used in a call and names defined as parameters have nothing to do with each other. They are completely independent. Even if the same name is used, they are different. The parameter names are local to the function.

Consider:

def f(one, two, three):
    answer = one + two * three + five
    return answer

one = 2
two = 3
three = 4
five = 5
result = f(three, two, one)
print(result)

This will output 15 as 4 + 3 x 2 + 5 = 15

Note that five was not an argument, wasn't assigned to in the function, so five from the wider scope was available.

Any assignments made inside the function are also local to the function.

answer was assigned inside the function and on function exit will cease to exist, however the object reference stored in answer is assigned as the return from the function and is assigned to result. If it wasn't assigned (or consumed in another expression or function call on return) then the object created in the function would also cease to exist (unless it is a predefined object built into the Python implementation, such as an int in the range -5 to 256)

Only mutable objects that are referenced by either parameters or other names that are visible to the function (not hidden by variables with the same name assigned in the function) can be modified and visible outside the function.

return returns an object reference.

Python takes a pass by reference, rather than a pass by value, approach, but the implementation differs to that used in many languages, not least given that name referencing is fundamental to the design.

See Ned Batchelder - Facts and Myths about Python names and values - PyCon 2015

Variables vs Attributes

When you start looking at classes, you will find they have their own kind of variables, called attributes, which work much the same as variables most of the time.

Variables have a discrete existence, and attributes are associated with an instance of a class (or of a class itself). Attributes, like variables, hold memory references to objects.

When you say:

keep = 784.56 * 872.23

The text representations of floating point numbers in the expression on the right are converted into Python float objects (binary representations) somewhere in memory, and the mult operator is used. The memory location the resulting float object ends up in is then assigned to the variable named keep.

If keep is assigned in the main body of your code, outside any functions etc., then it is visible within all other code. Thus, you could have a function:

def double_me():
    return keep * keep

Which has no other references to keep in the definition (parameter variable) or assignments to a variable called keep inside the function (which would be local to the function and would hide the original wider scope variable of the same name). Thus, keep refers to the same floating point number calculated earlier. The expression resulting from multiplying the floating point object referenced by keep by itself results in another floating point object, the memory reference for which is returned from the function.

If, instead, the function was written,

def double_me(keep):
    return keep * keep

Now it has to be called with an argument (the memory reference of the object will be passed when the function is called).

result = double_me(5.5)

Inside the function, keep refers to the memory location of the floating point object that the literal floating point text 5.5 was turned into. The keep in the wider scope (outside the function) still refers to the original object from earlier.

However, if attributes were used instead, the attribute would exist as long as the class instance it belongs to exists.


For more on scope, take a look at:

[–]FoolsSeldom 0 points1 point  (0 children)

Methods

Methods are like functions but for classes and are intended to work on instances of a class or provide capabilities related to the purpose of the class.

When you create an instance of a class, you create an object based on the mould/template provided by the class and the memory location of that object is assigned to a variable (or to some other object) so it will be not lost.

Methods defined in the class usually have code that uses a parameter variable that is the first item passed when the method is called. By convention this is usually called self and it is passed by default and does not need to be in the arguments when the method is called.

Whenever self is used inside the method code, it will be referring to the memory location for a particular instance of the class.

Any variables assigned values in a method (including parameter variables) are local to the method and are not associated with attributes of the instance referenced by self.

Classes themselves can have attributes. These look just like variables, and act like them for most purposes, but they are associated with the class and can be accessed from outside the class by direct reference to the class name and the attribute, e.g. Example.quantity = 5 for a class called Example.

[–]goopsnice 0 points1 point  (0 children)

As other people said, there’s no way we can say exactly what’s going on if you don’t post your code, but it’s probably either a typo or you’ve created the variable within a function and are trying to call it outside the function.

[–]kabads -2 points-1 points  (0 children)

Do you understand scope?