Why method can't access class variable? - python

I am trying to understand variable scopes in Python, most of the things are clear to me except for the part that I don't understand why class variable is not accessible from its method.
In following example mydef1() can't access a, but if a is declared in global scope(outside class definition) it can.
class MyClass1:
a = 25
def mydef1(self):
print(a)
ins1 = MyClass1()
ins1.mydef1()
Output
Traceback (most recent call last):
File "E:\dev\Python\scope_test2.py", line 6, in <module>
ins1.mydef1()
File "E:\dev\Python\scope_test2.py", line 4, in mydef1
print(a)
NameError: name 'a' is not defined

It's important to understand that some of these comments are not equivalent. MyClass.a is a member of the class itself, self.a is a member of the instance of the class.
When you use self.a it will return a from the class, because there is no a on the instance. If there was also an a which was a member of the instance, it would return that instead. Generally the instance a is set using the __init__ constructor. Both of these can exist simultaneously.
class MyClass1:
a = 25
def __init__(self):
self.a = 100
def instance_a(self):
print(self.a)
def change_instance_a(self):
self.a = 5
def class_a(self):
print(MyClass1.a)
def change_class_a(self):
MyClass1.a = 10
# Create two instances
ins1 = MyClass1()
ins2 = MyClass1()
# Both instances have the same Class member a, and the same instance member a
ins1.instance_a()
ins2.instance_a()
ins1.class_a()
ins2.class_a()
# Now lets change instance a on one of our instances
ins1.change_instance_a()
# Print again, see that class a values remain the same, but instance a has
# changed on one instance only
print()
ins1.instance_a()
ins2.instance_a()
ins1.class_a()
ins2.class_a()
# Lets change the class member a on just one instance
ins1.change_class_a()
# Both instances now report that new value for the class member a
print()
ins1.instance_a()
ins2.instance_a()
ins1.class_a()
ins2.class_a()

Short answer: That's Python's scoping rules. Nested functions in Python are lexically scoped, but that doesn't apply to things nested in classes.
class Foo:
a = 25
print(a)
class Bar:
print(a)
The first one prints, but the second is a NameError.
Longer answer:
There is a function closure for class-level variables, but it is all wrapped in __class__. (The main use for this is in the super() magic, which is why it no longer needs arguments in Python 3.)
class MyClass1:
a = 25
def mydef1(self):
print(__class__.a)
ins1 = MyClass1()
ins1.mydef1() # 25
Normally, you'd access such things through the self parameter to allow subclasses to override them, but __class__ would even work for a staticmethod, which has neither self, nor cls.
class MyClass1:
a = 25
#staticmethod
def mydef1():
print(__class__.a)
ins1 = MyClass1()
ins1.mydef1() # 25
The class object technically doesn't even exist until after the class declaration finishes executing, that's why you can't do
class Foo:
a = 25
class Bar:
# NameError: free variable '__class__' referenced before assignment
print(__class__.a)
Nor even,
class Foo:
a = 25
def bar():
print(__class__.a)
# NameError: free variable '__class__' referenced before assignment in enclosing scope
bar()
You can, however, access the locals() dict before then.
class Foo:
a = 21
locals()['a'] *= 2
Foo.a # 42
So this works.
class Foo:
a = 25
global foolocals
foolocals = locals()
def bar():
print(foolocals['a'])
bar() # 25

Consider the following class:
class ScopeTest(object):
classvariable = 0
number_of_objects_created = 0
def __init__(self, value=1):
self.instancevariable = value
ScopeTest.number_of_objects_created += 1
def number_of_brothers(self):
print(ScopeTest.number_of_objects_created)
def confusion(self, value=2):
self.classvariable = value
print (f"class: {ScopeTest.classvariable}, self:{self.classvariable}")
And let's see what happens when you play around with it:
>>> a = ScopeTest()
>>> a.instancevariable
1
>>> a.classvariable
0
>>> ScopeTest.classvariable
0
So far so good, but when you assign a new attribute to a:
>>> a.classvariable = 2
>>> a.classvariable
2
>>> ScopeTest.classvariable
0
The same holds if you add the attribute inside a class's method:
>>> a.confusion(4)
class: 0, self:4
These kind of class attributes are good to keep things common to all objects, as the number_of_objects_created:
>>> b = ScopeTest()
>>> b.number_of_brothers()
2
>>> a.number_of_brothers()
2
You could get a little more from this by adding yet another method to the class:
class ScopeTest(object):
...
def other_function(self, classvariable=3):
print(f"class: {ScopeTest.classvariable}\t"
f"instance: {self.classvariable}\t"
f"argument:{classvariable}")
And calling it (after using the first 'confusion' method to set self.classvariable):
>>> a.confusion()
class: 0, self:2
>>> a.other_function()
class: 0 instance: 2 argument:3

By calling print(a) in mydef1, python is looking for a local (or global, as you discovered) variable "a". That is, a variable not directly related to MyClass1 in any way, but such a variable has not been defined yet.
If you're trying to access the class variable "a" (i.e. a is a member of the class itself, not any instance of it), you must use MyClass1.a. Alternatively, because there is no instance variable named "a", you can also use self.a to the same effect. However, as soon as self.a is explicitly defined, self.a == MyClass1.a may not be true. For example:
>>>class MyClass1:
... a = 25
...
>>>my_obj = MyClass1()
>>>MyClass1.a
25
>>>my_obj.a
25
>>>MyClass1.a += 1
>>>MyClass1.a
26
>>>my_obj.a
26
>>>my_obj.a = 5 # explicitly define "a" for this instance; my_obj.a not longer synonymous with MyClass1.a
>>>MyClass1.a += 1
>>>MyClass1.a
27
>>>my_obj.a
5

You need self.a. There is no implicit class scope.

Related

How to get object attributes to update dynamically in Python

I'd like to create a class that has 2 input attributes and 1 output attribute such that whenever one of the input attributes are modified the output attribute is modified automatically
I've tried defining the attributes as instance variables within and outside the constructor function but in either case, after instantiating the object, the output attribute remains fixed at the value set at the moment of instantiation
class Example():
def __init__(self,n):
self.name=n
inA=1
inB=1
if inA==1 and inB==1:
outA=1
else:
outA=0
when instantiated outA is set to 1 as expected
but if I try to update:
object.inA=0
object.outA remains 1 whereas I need it to be updated to 0
Trying to avoid the use of functions if possible. New to python and OOP so sorry if this question is nonsensical or has an obvious answer
If you want instance attributes that depend on other instance attributes, properties are the way to go.
class Example:
def __init__(self, n):
self.name = n
self.inA = 1
self.inB = 1
#property
def outA(self):
return self.inA and self.inB
You access outA like a regular instance attribute, obj.outA.
>>> my_obj = Example("example")
>>> my_obj.outA
1
Changing the attributes inA and inB affect outA.
>>> my_obj.inA = 0
>>> my_obj.outA
0
You can create a function in the class and some other minor changes:
class Example():
def __init__(self,n):
self.name=n
self.inA=1
self.inB=1
def f(self):
if self.inA==1 and self.inB==1:
self.outA=1
else:
self.outA=0
To call it:
a = Example('foo')
a.inA = 0
a.f()
print(a.outA)
Output:
0
As you can see, taking out:
a.f()
line would make it give an error:
AttributeError: 'Example' object has no attribute 'outA'
Do you want it to return your output?
Expanding on U9-Forward's answer:
class Example():
def __init__(self,n):
self.name = n
self.inA = 1
self.inB = 1
def f(self):
return self.inA and self.inB

Accessing attributes from another nested class

I would like to achieve something similar to this construction:
class Outer:
class A:
foo = 1
class B:
def __init__(self):
self.bar = A.foo
Outer.B().bar # ==> 1
But this fails with
NameError: name 'A' is not defined
I'm not even sure I understand why as A is (I thought) in scope.
Could you help me clarify why this doesn't work and how I could get around it?
Names are looked up only in globals, locals, and nonlocal cells (but you don't have a closure here).
Write Outer.A instead of A, or consider making Outer a module.
It works if you use Outer.A.foo.
Just like what you did for Outer.B().bar: do the same for self.bar=Outer.A().foo
Inner classes in Python do not have acces to the members of the enclosing class. A is not in scope of B as you state. A and B are both in scope of Outer, but they do not know of each other. Therefore a possible solution to your problem:
class Outer:
class A:
foo = 1
class B:
def __init__(self, class_a):
self.bar = class_a.foo
def __init__(self):
self.a = self.A()
self.b = self.B(self.a)
print(Outer.A.foo) # 1
print(Outer.B.bar) # AttributeError: type object 'B' has no attribute 'bar'
outer = Outer()
print(outer.a.foo) # 1
print(outer.b.bar) # 1

Python: looking for a short-hand way to setup setters/getters for lots of variables

I have one class (Bar) embedded inside another class (Foo).
class Foo():
class Bar():
def __init__(self):
self.a = 1
self.b = 2
...
self.z = 26
def __init__(self):
self.bar = Bar()
To access the attributes of class Bar, the user would need to the following:
>>> f = Foo()
>>> f.bar.a
1
How can I setup a short dot notation so that users can use BOTH:
>>> f.bar.a
1
and
>>> f.a
1
In my example, I'm trying to demonstrate that Bar class has a lot of variables. So I don't want to write a getter/setter for each one manually. So I was thinking to use the property() in a for loop like this:
def __init__(self):
self.bar = Bar()
# Allow shorter dot notation
for parm in self.bar.__dict__:
setattr(self, i, getattr(bar, i))
self.i = property(...)
But I'm unsure how to use property in this context without manually writing several setter functions.
Any suggestions on how to allow access to both shorter and longer notations?
That's what the __getattr__hook is ideally suited for:
class Foo:
# ...
def __getattr__(self, name):
return getattr(self.bar, name)
__getattr__ is only called for attributes that are missing; so only attributes that are not already present on instances of Foo() are passed to Foo().__getattr__(). The getattr() function then lets you use the same attribute name on self.bar; if the attribute doesn't exist there either, an AttributeError is thrown, as would be expected.

how to change the value of a global varible in the child classe of python?

I have a problem in declaring variable in the child class. I have simplified the case because the code is too long and have many dependencies. So here is the case :
I have 2 classes A and B :
class A ():
global_var=0
def some_function(self):
return self.global_var+2
class B(A):
def method_that_returns_global_var(self):
#do some operations on global_var
global_var=(.....some logic....)
return global_var
How to declare the global_var in the child class B so that it executes the function of the parent class some_function with the value of global_var of child class B
So... There's a couple of concepts that you should probably clarify.
That global_var is not a global variable. It's a class attribute. It means it's only defined once: When you declare the class. After that, all the classes of the same type (or their inherited classes) will share the same variable. I found this, which is probably helpful.
That means that all the instances of type A or of any type inheriting from A (namely B in the example) that you create will share it. However, you still need to access them through self or through the cls (usually the class instance is named like that in classmethods) What you're doing in this method...
def method_that_returns_global_var(self):
#do some operations on global_var
global_var=(.....some logic....)
... is just creating a global_var variable local to the method_that_returns_global_var method (and has nothing to do with the global_var in the class)
If it helps you see it (it certainly helps me), you can think of that global_var as attached to the class, not to the instances of that class.
This means that class B already has access to global_var because it inherits from A. When you try to access global_var in an instance method (the ones that you pass self to, broadly speaking), what the interpreter is gonna try to do is find the global_var attribute among the ones of the instance (self). If it doesn't find it it will go to the class. However (very important) when you assign in an instance method (you do self.global_var = whatever), the interpreter will just place global_var in the instance (in its "internal" dictionary of attributes __dict__) Don't let that confuse you!
See this:
class A(object):
global_var = 5
def some_function(self):
return self.global_var + 2
def print_global_bar(self):
print("Global bar value=%s" % self.global_var)
class B(A):
def method_that_returns_global_var(self):
# do some operations on global_var
self.global_var = 1000
return self.global_var
if __name__ == "__main__":
b = B()
b.print_global_bar()
b.method_that_returns_global_var()
b.print_global_bar()
print("Because `method_that_returns_global_var` does an ASSIGNMENT, now I have two `global_var`."
" One in the class, one in the instance `b`")
print("In the instance: %s" % b.global_var)
print("In the class: %s" % b.__class__.global_var)
It outputs:
Global bar value=5
Global bar value=1000
Because `method_that_returns_global_var` does an ASSIGNMENT, now I have two `global_var`. One in the class, one in the instance `b`
In the instance: 1000
In the class: 5
If you wanted to do the assignment to the class variable in method_that_returns_global_var you should either do it a class method (with the #classmethod decorator) or access the class of the instance (located in self.__class__)
See now:
class A(object):
global_var = 5
def some_function(self):
return self.global_var + 2
def print_global_bar(self):
print("Global bar value=%s" % self.global_var)
class B(A):
def method_that_returns_global_var(self):
# do some operations on global_var
self.__class__.global_var = 1000
return self.global_var
if __name__ == "__main__":
b = B()
b.print_global_bar()
b.method_that_returns_global_var()
b.print_global_bar()
print("In the instance: %s" % b.global_var)
print("In the class: %s" % b.__class__.global_var)
Outputs:
Global bar value=5
Global bar value=1000
In the instance: 1000
In the class: 1000
See the difference? In the first method, doing self.global_var = 1000 creates a new attribute in the instance, disregarding whatever is written in the class. Basically, you have two global_vars around: One connected to the class (whose value is still 5), and one connected to the instance (whose value is 1000). In the second method, you are accessing and modifying all the time the class variable (A.global_var)
I recommend you play a bit more with this... More prints, more .__class__... things like that. :-)
Despite the "global" in the name, variable is actually a class field. You can overwrite it in child class like this (I shortened it to "var"):
class A ():
var = 0
def some_function(self):
return self.var + 2
class B(A):
var = 1
B().some_function() # =3
When you define a variable in the class scope it becomes a class variable. You can refer to it using class name.
class A():
var = 123
print(A.var)
>>> 123
This variable will get inherited, so you should be able to do this
class B(A):
pass
print(B.var)
>>> 123
and you should be able to override the default in the child class or change its value in the methods
class C(A):
var = 234
def inc_var(self):
self.var += 1
print(C.var)
>>> 234
c = C()
c.inc_var()
print(C.var)
>>> 235

In a Python class, what is the difference between creating a variable with the self syntax, and creating one without ?

What is the difference between creating a variable using the self.variable syntax and creating one without?
I was testing it out and I can still access both from an instance:
class TestClass(object):
j = 10
def __init__(self):
self.i = 20
if __name__ == '__main__':
testInstance = TestClass()
print testInstance.i
print testInstance.j
However, if I swap the location of the self, it results in an error.
class TestClass(object):
self.j = 10
def __init__(self):
i = 20
if __name__ == '__main__':
testInstance = TestClass()
print testInstance.i
print testInstance.j
>>NameError: name 'self' is not defined
So I gather that self has a special role in initialization.. but, I just don't quite get what it is.
self refers to the current instance of the class. If you declare a variable outside of a function body, you're referring to the class itself, not an instance, and thus all instances of the class will share the same value for that attribute.
In addition, variables declared as part of the class (rather than part of an instance) can be accessed as part of the class itself:
class Foo(object):
a = 1
one = Foo()
two = Foo()
Foo.a = 3
Since this value is class-wide, not only can you read it directly from the class:
print Foo.a # prints 3
But it will also change the value for every instance of the class:
print one.a # prints 3
print two.a # prints 3
Note, however, that this is only the case if you don't override a class variable with an instance variable. For instance, if you created the following:
class Bar(object)
a = 1
def __init__(self):
self.a = 2
and then did the following:
one = Bar()
two = Bar()
two.a = 3
Then you'd get the following results:
print Bar.a # prints "1"
print one.a # prints "2"
print two.a # prints "3"
As noted in the comments, assigning to two.a creates an instance-local entry on that instance, which overrides the a from Bar, hence why Bar.a is still 1 but two.a is 3.
j is a class variable as pointed by Amber. Now, if you come from C++ background, self is akin to the this pointer. While python doesn't deal with pointers, self plays the similar role of referring to current instance of the class.
In the python way, explicit is better than implicit. In C++, the availability of this is conventionally assumed for each class. Python, on the other hand, explicitly passes self as first argument to each of your instance methods.
Hence self is available only inside the scope of your instance methods, making it undefined for the place from which you tried using it.
Since you're made to explicitly pass self to instance methods, you could also call it something else if you want to -
>>> class Foo:
... b = 20
... def __init__(them):
... them.beep = "weee"
...
>>> f = Foo()
>>> f.beep
'weee'

Categories