Confused about behaviour of base class - python

This follows a question I asked a few hours ago.
I have this code:
class A(object):
def __init__(self, a):
print 'A called.'
self.a = a
class B(A):
def __init__(self, b, a):
print 'B called.'
x = B(1, 2)
print x.a
This gives the error: AttributeError: 'B' object has no attribute 'a', as expected. I can fix this by calling super(B, self).__init__(a).
However, I have this code:
class A(object):
def __init__(self, a):
print 'A called.'
self.a = a
class B(A):
def __init__(self, b, a):
print 'B called.'
print a
x = B(1, 2)
Whose output is:
B called.
2
Why does this work? And more importantly, how does it work when I have not initialized the base class? Also, notice that it does not call the initializer of A. Is it because when I do a:
def __init__(self, b, a)
I am declaring b to be an attribute of B? If yes, how can I check b is an attribute of which class - the subclass or the superclass?

The way you defined it B does not have any attributes. When you do print a the a refers to the local variable a in the __init__ method, not to any attribute.
If you replace print a with print self.a you will get the same error message as before.

In your second code, calling print a works because you are printing the variable passed as a parameter to B's __init__ function, not the self.a variable (which is not initialized because A.__init__ was not called). Once you leave the scope of the __init__ function you will loose access to the a.

The short answer is that __init__ isn't a constructor in the sense of other languages, like C++. It's really just a function that runs on an object after the "bare" object is created. B's __init__ function is conventionally responsible for calling A's __init__, but it doesn't have to - if A.__init__ never gets called, then then A's attribute a doesn't get added to the object, and so you get an error when you try to access it.

To fix this problem, you'll need to use
class B(A):
def __init__(self, b, a):
super(A, self).__init__(a)
Make sure you declare the classes as new style objects, something you're already doing
[edit: clarification]

Related

Why did my code refer to Class B and not Class C?

Here is my code.
class A(object):
def __init__(self):
self.a = 1
def x(self):
print("A.x")
def y(self):
print("A.y")
def z(self):
print("A.z")
class B(A):
def __init__(self):
A.__init__(self)
self.a = 2
self.b = 3
def y(self):
print("B.y")
def z(self):
print("B.z")
class C(object):
def __init__(self):
self.a = 4
self.c = 5
def y(self):
print("C.y")
def z(self):
print("C.z")
class D(C, B):
def __init__(self):
C.__init__(self)
B.__init__(self)
self.d = 6
def z(self):
print("D.z")
obj = D()
print(obj.a)
Why does print(obj.a) return 2 and not 4? I thought Python scans inputs from left to right. So with that logic it should refer to the superclass C and find that self.a = 4 and not refer to the superclass B where self.a = 2
The attribute obj.a is found directly in the instance namespace, so the MRO is not really involved here.
>>> print(obj.__dict__)
{'a': 2, 'c': 5, 'b': 3, 'd': 6}
If you're asking why the instance namespace contains a=2 and not a=4, it's because it was set to 4 initially and then overwritten:
C.__init__(self) # sets self.__dict__["a"] = 4
B.__init__(self) # sets self.__dict__["a"] = 2
Why does print(obj.a) return 2 and not 4?
Because the object obj can only have one attribute named a, and its value was most recently set to 2.
I thought Python scans inputs from left to right.
To determine the class' method resolution order, yes. However, the MRO is only relevant when either implicitly looking for attributes that are missing in the current class, or explicitly passing along the chain via super.
So with that logic it should refer to the superclass C
No; when obj.a is looked up at the end, it doesn't look in any classes at all for the attribute, because the object contains the attribute. It doesn't look in C, B or A. It looks in obj, finds the attribute, and stops looking. (It does first look at D, in case it defines some magic that would override the normal process.)
The base classes do not create separate namespaces for attributes. Rather, they are separate objects, whose attributes can be found by the attribute lookup process (and, when they are, those attributes might be automatically converted via the descriptor protocol: e.g. attributes that are functions within the class, will normally become methods when looked up from the instance).
But when e.g. self.a = 2 happens, self means the same object inside that code that obj means outside. Assigning an attribute doesn't do any lookup - there's nothing to look up; there's already a perfectly suitable place to attach the attribute. So it just gets attached there. Where it will subsequently be found.
Because the parent classes were initialized explicitly, the order is clear: D.__init__ calls C.__init__ which sets self.a = 4; then that returns and D.__init__ also calls B.__init__; that calls A.__init__, which sets self.a = 1; then B.__init__ directly sets self.a = 2; then all the calls return (after setting other attributes). In each case, self is naming the same object, so it sets the same attribute in the same namespace (i.e. the attributes of that object, treated as a namespace).
and not refer to the superclass B where self.a = 2
Again, they are not separate namespaces (and unlike some other languages, not separate "parts" of the object), so B isn't a "place where" self.a can have a different value from the one it has "in" C. There's only one self object, with one __dict__, and one a (equivalently, __dict__['a']).

Super class appears to wrongly reference properties of derived class

In the following python code (I'm using 3.8), an object of class B derived from class A calls methods bar and foo that access members of the parent class through the super() function. Hence, I would expect the same result as calling bar and foo directly on A. Oddly, what is returned is affected by the parameterization of p of B, which should not happen because A should shielded from its children, shouldn't it?! Here is the code to reproduce:
class A(object):
#property
def p(self):
return 3
def bar(self):
return self.p
def foo(self):
return self.bar()
class B(A):
#property
def p(self):
return 6
def bar(self):
return super().p
def foo(self):
return super().bar()
a, b = A(), B()
print(a.p) # prints 3, OK
print(b.p) # prints 6, OK
print(a.bar()) # prints 3, OK
print(b.bar()) # prints 3, OK, since where accessing super().p
print(a.foo()) # prints 3, OK
print(b.foo()) # prints 6, NOT OK, because we are accessing super().bar() and expect 3
I'm out of my wits here, so if someone could iluminate on the rationale of this behavior and show a way to avoid it, this would be most helpful. Thanks a lot.
Welcome to the intricacies of super!
super() here is a shortcut for super(B, self). It returns a proxy that will look in the class MRO for the class coming before B, so A and super().bar() will actually call:
A.bar(self)
without changing the original b object...
And A.bar(self) is actually... b.p and will give 6
If you are used to other object oriented languages like C++, all happens as if all method in Python were virtual (non final in Java wordings)
super().attr means to look up the attr attribue in the parent. If the attr is a method, it looks up the method's code (instructions to execute). But that does not modify the passed arguments in any way, it just sets the instuctions.
In Python the self is an argument behind the scenes. If c=C(), then c.meth(...) means C.meth(c, ...), i.e. a call of method meth defined in the class C with first argument c (other args follow, if any). The first arg becomes the self argument in the method's implementation. The name self is just a convention, not a special keyword)
Back to the question. Here is a simplified program without properties, it behaves the same:
class A:
P = 3
def bar(self):
return self.P
def foo(self):
return self.bar()
class B(A):
P = 6
def bar(self):
return super().P
def foo(self):
return super().bar()
b.foo() invokes super().bar(), i.e. the bar() in the parent class A. That method contains code that simply returns self.P. But self is b, so the lookup returns 6. (In your original program p is a property that returns 6)

OOP: Init method questions

I have two questions regarding the code below.
What is the difference between self.a=self.test1() and a=self.test1()? One is class field and other one is object field?
Why cannot I define result = self.a+self.b? How to correct it?
class Test():
def __init__(self):
self.a=self.test1()
a=self.test1()
self.b=Test.test2()
result = self.a+self.b
def test1(self):
a=100
return a
#classmethod
def test2(cls):
b=200
return b
#staticmethod
def test3():
print("Testing3 is calling ")
c=500
return c
self.a = self.test1() creates an instance attribute named a. The attribute will be accessible from the object anywhere you have a reference to the object.
a = self.test1() defines a local variable named a. It will go out of scope once __init__ returns.
result = self.a + self.b doesn't work because it is in a context where self is not defined. self is just the (conventional) name of the first parameter of an instance method. It's not defined in the namespace of the class statement itself.
self.a is a property in this class. It will remain accessible throughout functions in the Test() class. a = self.test1(), however, goes away once __init__(self) finishes, because a is local to __init__(self).
For result = self.a + self.b, I assume you want a variable called result calculated after self.a and self.b is defined? At that indentation level a statement like this is usually not allowed (I could be wrong), usually a declaration of a property of a class happens here.

how to know from which class instance a function is called to access the instance attributes

I want to access an attribute of the class instance that called a function :
for example:
class A:
def a(self):
return B.q
class B:
q=0
def b(self):
M=A()
return M.a()
c=B()
c.q = 6
print(c.b())
the output will be 0 but I want it to print the q attribute of the instance c of the class B which has the value 6
Pass the instance as a parameter.
class A:
def a(self, b):
return b.q
class B:
q=0
def b(self):
M=A()
return M.a(self)
c=B()
c.q = 6
print(c.b())
This appears to be very bad program design. What are you trying to accomplish with this?
You have a class attribute and an instance attribute -- in that class -- of the same name, q. This makes your code difficult to follow and to maintain.
You have method B.b instantiate an instance of class A. You immediately call A.a, which has been assigned the questionable task of returning an instance attribute from and object of class B.
Clean up your design.
Use init appropriately for each class.
Design your class methods to work appropriately with the characteristics of instances of that class. Your question strongly suggests that your design is not yet clean in your mind, nor in code.
define an init method so that you can work with the instance attributes instead of the class variable
class A:
def a(self):
return B.q
class B:
def __init__(self):
self.q = 0
def b(self):
M=A()
return M.a()
c=B()
c.q = 6
print(c.b())

nested classes in Python

Dealing with classes (nested etc) does not look easy in Python, surprisingly! The following problem appeared to me recently and took several hours (try, search ...) without success. I read most of SO related links but none of them has pointed the issue presented here!
#------------------------------------
class A:
def __init__(self):
self.a = 'a'
print self.a
class B(A):
def __init__(self):
self.b = 'b'
A.a = 'a_b'
print self.b, A.a
#------------------------------------
class C:
class A:
def __init__(self):
self.a = 'a'
print self.a
class B(A):
def __init__(self):
self.b = 'b'
A.a = 'a_b'
print self.b, A.a
#------------------------------------
#------------------------------------
>>> c1 = A()
a
>>> c1.a
'a'
>>> c2 = B()
b
>>> c2.a, c2.b
('a_b', 'b')
>>> c3 = C()
>>> c4 = c3.A()
a
>>> c4.a
'a'
>>> c5 = c3.B()
b a_b
>>> c5.b
'b'
>>> c5.a
Traceback (most recent call last):
File "", line 1, in
AttributeError: B instance has no attribute 'a'
Where is the problem in the code?
AND
In both cases it seems that when B(A) is initialized A() is not initialized. What is the solution for this issue? Note that the term A.__init__() being called inside B()'s __init__() does not work!
Updates:
class Geometry:
class Curve:
def __init__(self,c=1):
self.c = c #curvature parameter
print 'Curvature %g'%self.c
pass #some codes
class Line(Curve):
def __init__(self):
Geometry.Curve.__init__(self,0) #the key point
pass #some codes
g = Geometry()
C = g.Curve(0.5)
L = g.Line()
which results in:
Curvature 0.5
Curvature 0
what I was looking for.
The code executed in a method runs in the local scope of that method. If you access an object that is not in this scope, Python will look it up in the global/module scope, NOT in the class scope or the scope of any enclosing class!
This means that:
A.a = 'a_b'
inside C.B.__init__ will set the class attribute of the global A class, not C.A as you probably intended. For that you would have to do this:
C.A.a = 'a_b'
Also, Python will not call parent methods if you override them in subclasses. You have to do it yourself.
The scoping rules mean that if you wanted to call the __init__ method of the parent class inside C.B.__init__, it has to look like this:
C.A.__init__(self)
and NOT like this:
A.__init__(self)
which is probably what you've tried.
Nested classes seems so unpythonic, even if considered as factories. But to answer your question: There simply is no c5.a (instance of C.B). In the init-method of C.B you add to the CLASS C.A an attribute a, but not to C.B! The class A does already have an attribute a, if instantiated! But the object of class B (and even the class) doesn't!
You must also keep in mind, that __init__ is not an constructor like in C++ or Java! The "real constructor" in python would be __new__. __init__ just initializes the instance of a class!
class A:
c = 'class-attribute'
def __init__(self):
self.i = 'instance-attribute'
So in this example c is a class-attribute, where i is an attribute of the instance.
Even more curios, is your attempt to add an attribute to the baseclass at the moment of the instantiation of the child-class. You are not getting a "late" inheritance-attribute that way.
You simply add to the class A an additional attribute, which surprises me to even work. I guess you are using python 3.x?
The reason for this behaviour? Well, i guess it has to do with pythons neat feature that in python definitions are executed(AFAIK).
The same reason why:
def method(lst = []):
is almost ever a bad idea. the deafult-parameter gets bound at the moment of the definition and you won't generate a new list-object every-time you call the method, but reusing the same list-object.

Categories