nested classes in Python - 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.

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']).

How can I reuse another classes' method without inheritance in Python 2?

Two of my classes need to have the same method, but they are not related by inheritance.
The following works in Python 3:
class A(object):
def __init__(self):
self.x = 'A'
def printmyx(self):
print(self.x)
class B(object):
def __init__(self):
self.x = 'B'
printmyx = A.printmyx
a = A()
b = B()
a.printmyx()
b.printmyx()
and prints
A
B
However, in Python 2 I'm getting
Traceback (most recent call last):
File "py2test.py", line 18, in <module>
b.printmyx()
TypeError: unbound method printmyx() must be called with A instance as first argument (got nothing instead)
I think the problem is that in Python 3 printmyx is just a regular function while in Python 2 it's an unbound method.
How to make the code work in Python 2?
edit
In my real code, A and B inherit from different parent classes. They need to share one helper method but have no other relation to each other.
Bear in mind that Python does support multiple inheritance, so it's very possible to define a mixin class and have both A and B inherit from it without disturbing the main inheritance hierarchy. I understand you're saying the classes have little in common - but they do both have a variable called x and a method to print it - and to me at least, that's enough in common to consider using inheritance.
But that said, another way to do this is using a class decorator to add the common method:
def add_printmyx(original_class):
def printmyx(self):
print (self.x)
original_class.printmyx = printmyx
return original_class
#add_printmyx
class B(object):
def __init__(self):
self.x = 'B'
b = B()
b.printmyx()
The class decorator takes the original class and adds (or replaces) a printmyx method that prints the contents of x.
Apparently, in Python 2 the original function an unbound method was created from is stored in the im_func attribute.1
To make the code work in Python 2 like it does in Python 3, use
printmyx = A.printmyx.im_func
in B's body.
1 Described in the The standard type hierarchy
section of the Python 2 Data Model documentation.
Why is inheritance not allowed? This is the perfect use case for inheritance.
class Common(object):
def printmyx(self):
print(self.x)
class A(Common):
def __init__(self):
self.x = 'A'
class B(Common):
def __init__(self):
self.x = 'B'
a = A()
b = B()
a.printmyx()
b.printmyx()

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

Is it possible to refer to the owner class that an object belongs to as an attribute?

I am not quite sure this is possible (or something similar) in python. I want to access a method (or another object) of a class from an object that is an attribute of such class.
Consider the following code:
class A():
def __init__(self):
self.b = B()
self.c = C()
def print_owner(self):
print('owner')
class B():
def __init__(self):
pass
def call_owner(self):
self.owner().print_owner()
so that b as an object attribute of class A, can refer to a method or attribute of A?
Or similarly, is it possible that b can access c?
It's possible. You can pass a reference to A to B constructor:
...
self.b = B(self)
...
class B:
def __init__(self, a):
self.a = a
So, B.a stores the reference to its owner A.
There can be many references to object B(), not only the one in instance of class A. So it's not possible as it is in your code. (Well you could try a hack, like finding all instances of class A in memory and find the one whose attribute b points to your B instance, but that's a really bad idea).
You should explicitly store in instance of B a reference to the owner.
You have a couple of options here. The better one is probably #Sianur suggests. It's simple, effective, and explicit. Give that answer an upvote.
Another option is to have the owner force itself on its minions. B can do something like
def call_owner(self):
if hasattr(self, 'owner'):
self.owner().print_owner()
else:
print('I am free!')
Meanwhile, A would set the owner attribute to itself:
def __init__(self):
self.b = B()
self.c = C()
self.b.owner = self.c.owner = self
In any case, if you want an object to have access to another object, store the reference into an accessible place. There's no magic here.

Keep object reference when class is deleted?

I have a class Foo which is instantiated an indefinite number of times during my program sequence. Like so:
def main():
f = Foo()
while f.run():
del f
f = Foo()
with run() being a method that runs an decisive condition for keeping the program alive.
Now, my Foo class creates on its __init__ method two objects a and b:
Foo class
class Foo:
def __init__(self):
a = A()
b = B(a.var)
I'm looking for a way to a being declared only at the first Foo instantiation and use that same first-instantiated a at the other Foo instantiations.
Problem arises because b depends on a. I thought about a couple solutions - from playing with __new__ and __init__ to override __del__ and global variable as cache - but none of them worked.
note: A needs to be at the same module as Foo
Maybe using a class variable?
class Foo:
a = None
def __init__(self):
if not Foo.a:
Foo.a = A()
b = B(Foo.a.var)
And function B needs to check whether a is None.
If I understand you correctly, you should be able to just make a a class variable.
class Foo:
a = A()
def __init__(self):
b = B(Foo.a.var)
I'm afraid some of your requirements will make Foo extremely difficult to test. Instead, I would suggest that you move some of the dependencies from your constructor to a start class method that would be responsible for creating the initial A instance (at the same module as Foo) and then reusing that instance in a refresh method.
class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
#classmethod
def start(cls):
a = A()
b = B(a.var)
return cls(a, b)
def refresh(self):
b = B(self.a.var)
return self.__class__(self.a, b)
Then, your main function would look something like:
def main():
f = Foo.start()
while f.run():
f = f.refresh()
By overwriting the f variable, you are effectively deleting the reference to the old instance which will eventually be garbage collected.

Categories