Python: what scopes are accessible when defining class variables?
From https://docs.python.org/3/reference/executionmodel.html:
Class definition blocks and arguments to exec() and eval() are special in the context of name resolution. A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:
In the definition of B
, y
is an unbound local variable, and so is looked up in the global scope (where it is not defined), not in the namespace created by the enclosing class
statement.
A class
statement does not define a scope at all; it creates a namespace which is passed to the metaclass in order to construct a new class.
Python and Variable Scope
In Python, variables declared inside the class definition, but not inside a method are class or static variables:
class TestB:
varNum = 0
This creates a class-level varNum
variable, but this is distinct from any instance-level varNum
variable, so you could have:
class TestB:
varNum = 0
def __init__(self):
self.varNum = 3
b = TestB()
print(b.varNum) # print 3
print(TestB.varNum) # print 0
Thus, class TestB
should work in this way:
class TestB:
varNum = 0
def __init__(self):
self.varNum = 3
def printInstanceNum(self):
print(self.varNum)
def printClassNum():
print(TestB.varNum)
b = TestB()
b.printInstanceNum() # print 3
TestB.printClassNum() # print 0
Note that since there's no any reference to instance object in method printClassNum()
, we don't have to put self
as an argument. The method could actually become a staticmethod:
class TestB:
varNum = 0
@staticmethod
def printClassNum():
print(TestB.varNum)
Ref
- class objects
- static method
Confusion about scoping in python classes
As the tutorial said, scopes are searched in the order local, nonlocal, global, builtin.
The nonlocal scope is for enclosing functions. A class declaration is not a function. Its namespace gets thrown away after it is used to create the class object's __dict__
, so variables at the class level cannot produce nonlocals in enclosed functions. Think of class-level assignments and variable reads like implicit assignments to and reads from a hidden dict, rather than as function locals. (Metaclasses can even replace this hidden dict with some other mapping.)
But the class scope does add one nonlocal, __class__
. This is rarely used directly, but it's important for the zero-argument form of super()
.
This is the class object itself, so it's uninitialized until the class declaration finishes executing. So __class__.tricks
would work inside a method if it's called after the class body executes (the usual case), but not if it's called during the execution of the class body.
There are other scopes to be aware of in Python. Comprehensions create a local scope like functions do. (They're basically compiled like generator functions--the kind with yield
inside.) Also, caught exceptions are automatically deleted at the end of their handling clause to prevent a reference cycle.
You can see locals namespace using the locals()
builtin and globals using globals()
. The builtin scope is just the builtins module. The nonlocals are tricky. They'll actually show up in locals()
if the compiler sees them being used. Function objects keep a reference to the nonlocals they use in their __closure__
attribute, which is a tuple of cells.
Python, scope of instance method variable in a class
self
always refers to the actual instance of a class. So in your case even if you lazily declare a variable on self
in a method, after it is declared it is accessible by any method bound to self
, i.e. they share the same state.
There is no local instance scope in Python classes.
Python Variable Scope and Classes
When you put it right after class MyClass
, it becomes a class attribute and you can get access to it via MyClass.my_var
or as self.my_var
from within the class (provided you don't create an instance variable with the same name).
Here's a little demo:
my_var = 'global'
class MyClass(object):
my_var = 'class'
def __init__(self):
print my_var #global
print MyClass.my_var #class
print self.my_var #class -- Only since we haven't set this attribute on the instance
self.my_var = 'instance' #set instance attribute.
print self.my_var #instance
print MyClass.my_var #class
Why is a class variable not accessible from a method?
The class body is not a nestable scope, no. The Python Execution Model explicitly excludes it:
The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods
That's because the body of a class is executed to form the class attributes; see it as a function with locals, and the locals become the attributes of the new class object.
You can then access those attributes either on the class directly (Foo.a
) or via an instance (where attribute lookup falls through to the class).
Variable Scope In Generators In Classes
As stated in other answer, it is True
that it happens because it is static variable. But it is not just that property that restricts your code to work. Actual reason is the scope of the variable and the scope in which it executes. For example, create a class as:
class A(object):
x = 999999
y = x +1
If you access it's class properties A.x
and A.y
, it will work. Because, at the time of initializing y
, x
is replaced the value in expression x+1
. As the scope of x
was within the class.
However this doesn't happens in the case of generators. i.e. in your example:
class A(object):
x = 4
gen = (x for _ in range(3))
When you do list(a.gen)
, it is executed outside the class (as generators are evaluated during run-time) and checks for the reference of x
in the current scope. Since, x
it is not initialized in that scope, it throws error.
When you explicitly initialize x=4
, it works because now the generator expression has value of x
to which it could use.
In order to make your generator expression work, as stated by others you have to define it like:
class A(object):
x = 4
gen = (A.x for _ in range(3))
# ^ mentioning `A.x` is the value to access
Python Class Variables' scope?
- Creation of an object (i.e.
Emplyee()
calls the__init__()
method, the initializer). All methods that then have theself
argument take that object's instance to operate on. Meaning these methods will have access to that object'sself
variables. This means that static methods do not have access to this, and why accessing those instance variables does not error. As an example, this would error:
class Employee:
def __init__(self):
self.variable = 123
@staticmethod # Decorator to indicate this method should not accept 'self'
def static_method():
print(self.variable)
Because the method static_method()
does not operate on an initialize instance of the Employee
object.
- Strings, like any other unassigned variable, will be interpreted and then discarded. Like the following:
123
"test"
[1, 2]
These all do nothing, but given that they're technically valid statements these do not result in errors. You will often see this type of statement in docstrings, like:
"""
Long string goes here.
"""
Class variable scope for static vs class methods
When decorated with @classmethod
the first argument cls
to inc_class(cls)
is, well, the class. <class '__main__.A'>
and <class '__main__.B'>
respectively for A
and B
. So cls._var
refers to A
's _var
, and similarly for B
. In inc_static
, decorated with @staticmethod
there is no argument, you're explicitly referring to <class '__main__.Base'>
, a different _var
.
Note the '_var': 0
attribute in Base
's and A
's __dict__
. @classmethod
is doing what you'd expect it to do, binding members to classes, in this case A
and B
.
>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 0, 'inc_class': <classmethod
object at 0x7f23037a8b38>, 'inc_static': <staticmethod object at
0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base' objects>,
'__weakref__': <attribute '__weakref__' of 'Base' objects>, '__doc__': None})
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})`
After calling Base.inc_static()
:
>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 1, 'inc_class':
<classmethod object at 0x7f23037a8b38>, 'inc_static': <staticmethod
object at 0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base'
objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>,
'__doc__': None})
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})
After calling A.inc_class()
:
>>> Base.__dict__
mappingproxy({'__module__': '__main__', '_var': 1, 'inc_class':
<classmethod object at 0x7f23037a8b38>, 'inc_static': <staticmethod
object at 0x7f23037a8c18>, '__dict__': <attribute '__dict__' of 'Base'
objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>,
'__doc__': None})
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, '_var': 1})
What's interesting is how A
's _var
is initialised. Note that you do cls._var += 1
before cls._var
has been defined. As explained here, cls._var += 1
is equivalent to cls._var = cls._var; cls._var += 1
. Because of the way python does lookup the first read of cls._var
will fail in A
and continue to find it in Base
. At the assignment _var
is added to A
's __dict__
with the value of Base._var
, and then all is fine.
>>> class Base(object):
... _var = 10
... @classmethod
... def inc_class(cls):
... cls._var += 1
...
>>> class A(Base):
... pass
...
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None})
>>> A.inc_class()
>>> A.__dict__
mappingproxy({'__module__': '__main__', '__doc__': None, '_var': 11})
Related Topics
Saving Utf-8 Texts With Json.Dumps as Utf8, Not as \U Escape Sequence
Is There a Standardized Method to Swap Two Variables in Python
Why Does Running the Flask Dev Server Run Itself Twice
Why Is This Printing 'None' in the Output
How to Get a Substring of a String in Python
How to Sort a Dataframe in Python Pandas by Two or More Columns
Selecting Multiple Columns in a Pandas Dataframe
How Do Python'S Any and All Functions Work
Python Exit Commands - Why So Many and When Should Each Be Used
How to Convert Local Time String to Utc
How to Write to an Excel Spreadsheet Using Python
Why Is Python Ordering My Dictionary Like So
Install a Python Package into a Different Directory Using Pip