Based on reading your updated Q2 and looking at your Q1 again, it sounds like you have a basic misunderstanding of classes and what they can do for you. (This makes sense since you say you are "new to OOP".)
I am going to run afoul of terminology here unless I know which non-OOP languages you are familiar with, but I'll use a C example and deliberately gloss over a whole slew of issues, and claim that your class A
is like a struct A
, and your class B
is just a struct B
that contains an entire struct A
:
struct A {
int x, y; /* ... etc */
};
struct B {
struct A a;
int z;
}
If you make a new instance of a B
, you can't make the type of its a
member change, it's always going to be an actual A
. Doesn't matter if there's a struct A2
(type or instance), the contents of a B
includes exactly one A
.
It sounds like what you want is not a subclass at all, but rather just an ordinary member, which (in C) is more like having a pointer to some other object, rather than incorporating it bodily:
class A1(object):
def __init__(self):
pass
def about(self):
print 'I am an A1'
class A2(object):
def __init__(self):
pass
def about(self):
print 'I am an A2'
class B(object):
def __init__(self, connected_to = None):
self.connected_to = connected_to
def about(self):
print 'I am a B'
if self.connected_to is not None:
print '... and I am connected to a %s:' % type(self.connected_to)
self.connected_to.about() # and ask it to describe itself
else:
print '... and I am connected to no one!'
(In C, having a pointer—at least, if it's void *
—means you can point to whatever type you need to, or to nothing at all, just as a B
above can be connected_to None
instead of some A1 or A2, or indeed anything as long as it has .about()
.)
Given the above, you can then do:
>>> B(A1()).about()
I am a B
... and I am connected to a <class '__main__.A1'>:
I am an A1
>>> B(A2()).about()
I am a B
... and I am connected to a <class '__main__.A2'>:
I am an A2
>>> B().about()
I am a B
... and I am connected to no one!
>>>
and of course, you can make B
's __init__
find an A1 or A2 (if one exists) on its own if passing it explicitly is not what you want.
This, of course, obscures what (sub)classes do do for you. Given your example above, you might create a class Maths(object)
and then a sub-class Algebra(Maths)
, and maybe another sub-class Topology(Maths)
. Separately, you could create a class HumanLanguage(object)
and a sub-class English(HumanLanguage)
, and another sub-class Russian(HumanLanguage)
. But you would not create a sub-class Ring(HumanLanguage)
, while Ring(Algebra)
might make sense.
Edit to address Q3: in the interpreter you can see why printing super(B, self).__class__.__name__
only prints "super". Having done z = B()
:
>>> type(z)
<class '__main__.B'>
>>> super(B, z)
<super: <class 'B'>, <B object>>
Invoking super
simply gets you this special proxy object. The proxy object is how instances can call "upwards" (technically, "mro"-wards); follow the links inside the Python2 docs.