I am really struggling to understand the inheritance in python classes. In particular, I am trying to figure out how to pass the outer class attributes to inner class. Below is a sample code:
class Numbers:
def __init__(self,a,b):
self.a = a
self.b = b
class Operations:
def __init__(self):
Numbers.__init__(a,b)
self.sum = self.a + self.b
self.prod = self.a * self.b
twoNumbers = Numbers(5,4)
print(twoNumbers.Operations.sum)
When I run the code, I am getting an error:
Traceback (most recent call last):
File "/Users/aabedin/Downloads/test_schedule.py", line 117, in <module>
print(twoNumbers.Operations().sum)
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/aabedin/Downloads/test_schedule.py", line 111, in __init__
Numbers.__init__(a,b)
^
NameError: name āaā is not defined`
My philosophy is as follows:
I create an object from the Numbers class. Inside that class there is another class called Operations which has certain attributes, corresponding to different arithmetic operations. I would imagine that accessing the attributes of the outer class should not be a problem, if the outer class Numbers has been instantiated inside the __init__ method of the inner class, no?
Can you please help me understand how I can access the parent attributes within the child class?
Inner class is not inheritance. Here is an example of one.
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
class NumbersWithOperations(Numbers):
def __init__(self, a, b):
# Calling the initializer of the parent class.
super().__init__(a, b)
def sum(self):
return self.a + self.b
def prod(self):
return self.a * self.b
if __name__ == "__main__":
two_numbers = NumbersWithOperations(5, 4)
print(two_numbers.sum())
In this case, NumbersWithOperations inherits from Numbers and thus have access to a and b. super is used to call parent (Numbers)'s __init__ method to set the values of both a and b.
The reason your code doesnāt work is that the outer class Numbers does not exist until its class body is done executing. Since Operations is inside that body, at the time Operations is defined, Numbers does not exist.
āInner classā is not really a special concept in Python. You can syntactically put one class definition inside another one, but all it does is make the inner class (not an instance of it) an attribute of the outer class. They have no special relationship the inner class will not āknow aboutā the outer class in any way.
Itās fine to have an Operations class that manages some behavior of a Numbers instance. But why do you think that you should accomplish that by defining Operations inside Numbers? You can just define Operations separately and then have Numbers assign itself an instance attribute that holds an instance of Operations, something like:
class Numbers:
def __init__(self, a, b):
self.a = a
self.b = b
self.operations = Operations(self)
...
class Operations:
def __init__(self, nums):
self.nums = nums
self.sum = self.nums.a + self.nums.b
self.prod = self.nums.a * self.nums.b
Thank you for the elegant solution! Since I am new to OOP, I have really difficult time understanding how classes work. I know what I want to achieve and how my code to behave, but donāt seem to understand how to implement it.
Regarding your solution, I am trying to understand how self.operations = Operations(self) works. The Operation class requires one positional argument nums (in your definition), but when you declare the attribute self.operations = Operations(self) , how does the code work without doing self.operations = Operations(self, nums)? It looks like in your definition nums is an object, but what I fail to understand is where it gets instantiated and how. self.nums = nums, I assume means that self.nums expect that we pass nums to the Operations class.
__init__ is ājustā a method like any other, the special part is only that itās automatically called passing along the parameters given to the class when objects are created. And all methods automatically get the object theyāre called on passed as the first parameter. Expanding it out, this line is like
self.operations = object.__new__(Operations) # Makes a new blank object
self.operations.__init__(self)
# Which works like:
Operations.__init__(self.operations, self)
The fact thereās two different self variables here in the two methods, one passed as the other could be a little confusing. Keep in mind the name of the variable passed into a function and the name of the parameter itās assigned to inside the function donāt have to match at all, though they often do. Itās just the order that matters, unless you use keyword= arguments to explicitly specify which to set.
Doing Operations(self, nums) would not make sense, as nums is not defined in the scope where that call is made. The self that is passed there is the Numbers instance. All Iām doing here is making the Operations class accept a Numbers instance, so that when you create an Operations instance it knows which Numbers object it ābelongs toā. Note that self in the two __init__ calls refers to two different objects; in Numbers.__init__ it refers to the Numbers instance and in Operations.__init__ it refers to the Operations instance.
nums is the Numbers object being initialized. We are indeed passing that object as an argument to an Operations object which is also being initialized.
Basically if I were to call Numbers(2, 3), this creates a new instance of Numbers and calls Numbers.__init__ on it, passing in the numbers 2 and 3 as arguments. That then calls Operations(self), which creates a new instance of Operations and calls __init__ on that, passing in the Numbers instance as an argument.
This is known as composition. In a nutshell, composition is basically creating instances of classes with attributes from other classes where the attribute itself is the instance (or object). The attributes may belong to any method including the constructor (i.e., __init__) method which can be created during instantiation of the top class.
Here is a simple example:
class Top:
def __init__(self):
self.var1 = 1
self.var2 = 2
obj1 = Top() # Typical instantiation
class Bottom:
def __init__(self):
self.composition = Top() # Composition (i.e., instantiation via an attribute)
obj2 = Bottom() # Typical instantiation
This might be a bit oversimplification but the main take away is that composition is basically instantiation via an object attribute (self.composition) versus independently outside of a class (obj1 and obj2).
More specifically, it is composition with a circular reference.
Each instance of Numbers contains a reference to a Operations object, and Operations objects have references to Numbers objects. This doesnāt cause an infinite loop, because it is possible to have references to incomplete objects. So they both get a reference to each other, and then they both finish being constructed.
It is a little dangerous to have circular references like that. It can cause issues with garbage collection (memory management). And it makes the code somewhat harder to think about I think.
When itās possible itās nice to keep the object construction hierarchical.