OOP - Understanding Inheritance - python

I'm beginning my journey into OOP by trying to learn inheritance. I saw this code in an online quiz about the topic and I was hoping someone could explain it to me since it makes little sense to me.
class A(object) :
def __init__(self, x) :
self._x = 2 * x
def m1(self, x) :
return self.m2(x) + 2
def m2(self, x) :
return x - 1
class B(A) :
def m2(self, y) :
self._y = y
return self._x + self._y
For the following, if I was to say a = A(1), what would be the expected return? The initialization multiplies the 1 with 2, so now the instance of x has value 2. What happens with the method m1? it receives this instance of X but in the return it refers to m2? so is that x=2 passed into m2 first and then the return of 1 is passed to m1? which adds 2 to it?
As for Class B, I see it changes the inherited m2 method from Class A, but the x value that is added to the y, is that an inherited x value from Class A?
Sorry about the endless questions but I'm just beginning and it seems hard to wrap my head around.

The easiest way to figure out what the code does is to run it.
>>> a = A(1)
>>> a.m1(10)
11
An important thing to note here (which from your question it sounded like you might be confused on) is that the value of x you pass to A.__init__ does not get used by A.m1! So A.m1(x) just returns x + 1 no matter how you initialized the A instance.
>>> a = A(1000)
>>> a.m1(1)
2
>>> a.m1(10)
11
>>> a.m1(100)
101
Okay, what if we make a B the same way?
>>> b = B(1)
>>> b.m1(10)
14
Now it's different, because B.m2 is different from A.m2. We can just run those on their own to see:
>>> a.m2(10)
9
>>> b.m2(10)
12
a.m2(10) is 9 because a.m2(x) is always just x - 1 regardless of what a._x is.
But b.m2(10) returns b._x + 10, which in this case is 12 -- and so b.m1(10) returns that plus 2. The results will be different if we change what b._x is by initializing our B with a different value:
>>> b = B(2)
>>> b.m2(10)
14
>>> b.m1(10)
16

You can always track what happens:
...
class B(A) :
def m2(self, y) :
self._y = y
return self._x + self._y
class B then looks like Bx as m2 is overridden:
class Bx:
def __init__(self, x) :
self._x = 2 * x
def m1(self, x) :
return self.m2(x) + 2
def m2(self, y) :
self._y = y
return self._x + self._y
So this is how class B looks like-ish when it has inherited A

I'll try answering your questions in order:
If you were to say a = A(1), __init__ would set variable self._x to 2. This does not change, as you do not ever edit the self._x variable after this.
The functions m1 and m2 could return anything based on x that you pass in. Did you mean self._x? And yes, m2 would execute when you call m1, m1 for any number x would just return x + 1.
a = A(1) is just instantiating class A, the methods would work with just A(x).m1(y), whereas if you include a = A(1) you would execute a.m1(y).
Instantiating class B with any number (B(x)) runs the __init__() function of A (super().init(args)) with the x passed (Try running it yourself with something like b = B(2); print(b._x)!). Thus, self._x is 2 * x that you passed for B!
You have overwritten method m2() in class B, so m2() from A is not used in B.
If you have any other questions comment and I'll attempt to answer them!

Related

How to use inputs within the class?

I am new to python and object language. I am learning class in python now. but the code below confused me.
class math:
def __init__(self, a, b):
self.a = a
self.b = b
math.add = self.a+self.b
def sum(self):
math.sum = self.a+self.b
s= math(2,1)
x = math.add
y = math.sum
the results look not right. x is 3 but y is not 3. where is my problem?
thanks
I'm guessing what you wanted to do was this.
class math:
def __init__(self, a, b):
self.a = a
self.b = b
self.add = self.a+self.b
def sum(self):
return self.a+self.b
s= math(2,1)
x = s.add
y = s.sum()
print(x)
print(y)
The errors were as follows
You were not calling your instance of math. As these are instance methods, they must reference an instance x = math.add should be x = s.add.
You created a method sum but did not call it y = s.sum simply creates a reference to the function and does not call it, instead you should use y = s.sum()
Your function sum doesn't return anything, so assigning a value to its return value will yield a value of None. To resolve this return a value from sum i.e. return self.a + self.b.
self.add is also quite strange, as it named like a function, but its actually a value, this will not be expected by others looking at your code. A better name for it might be total or remove it since it provides the same functionality as sum.

Class variables behave differently for list and int?

The class shared variables are shared with all the instances of the classes as far as I know. But I am having trouble getting my head around this.
class c():
a=[1]
b=1
def __init__(self):
pass
x=c()
x.a.append(1)
x.b+=1 #or x.b=2
print x.a #[1,1]
print x.b #2
y=c()
print y.a #[1,1] :As Expected
print y.b #1 :why not 2?
y.a resonates with the x.a but
y.b doesn't.
hope someone can clarify.
EDIT: And how can the same functionality be created for ints.
x.a.append(1)
changes the class attribute c.a, a list, by calling its append method, which modifies the list in-place.
x.b += 1
is actually a shorthand for
x.b = x.b + 1
because integers in Python are immutable, so they don't have an __iadd__ (in-place add) method. The result of this assignment is to set an attribute b on the instance x, with value 2 (the result of evaluating the right-hand side of the assignment). This new instance attribute shadows the class attribute.
To see the difference between an in-place operation and an assignment, try
x.a += [1]
and
x.a = x.a + [1]
These will have different behavior.
EDIT The same functionality can be obtained for integers by boxing them:
class HasABoxedInt(object):
boxed_int = [0] # int boxed in a singleton list
a = HasABoxedInt()
a.boxed_int[0] += 1
b = HasABoxedInt()
print(b.boxed_int[0]) # prints 1, not zero
or
class BoxedInt(object):
def __init__(self, value):
self.value = value
def __iadd__(self, i):
self.value += i
larsmans' answer is excellent, but it might provide additional insight if we look at id of x.b before and after the assignment.
class c():
a=[1]
b=1
def __init__(self):
pass
x=c()
print "initial a : {} at {}".format(x.a, id(x.a))
print "initial b : {} at {}".format(x.b, id(x.b))
x.a.append(1)
x.b+=1 # x.b = x.b + 1, created a new object
# we created an instance variable x.b and it
# is shadowing the class variable b.
print "after change a : {} at {}".format(x.a, id(x.a))
print "after change b : {} at {}".format(x.b, id(x.b))
y=c()
# We can already see from the class object that
# b has not changed value
print "in class c b : {} at {}".format(c.b, id(c.b))
print "in instance y a : {} at {}".format(y.a, id(y.a))
print "in instance y b : {} at {}".format(y.b, id(y.b))
Result:
initial a : [1] at 50359040
initial b : 1 at 40974280
after change a : [1, 1] at 50359040
after change b : 2 at 40974256 # Shows id of instance variable x.b; hence it is
# different
in class c b : 1 at 40974280
in instance y a : [1, 1] at 50359040
in instance y b : 1 at 40974280
If you want to use an int as a class variable, this should work:
class MyClass(object):
b=1
def increase_b(self, n):
MyClass.b += n
Result:
>>> mc_1 = MyClass()
>>> mc_1.b
1
>>> mc_1.increase_b(5)
>>> mc_1.b
6
>>> mc_2 = MyClass()
>>> mc_2.b
6
>>> mc_2.increase_b(10)
>>> MyClass.b
16
>>> mc_2.b
16
>>> mc_1.b
16

Need help walking through logic of this code

I'm pretty new with Python and programming in general, so excuse the lack of "fu". :)
I'm having trouble understanding this class call:
snippet
class bar:
def __init__(self, a):
self.a = a
def __add__(self, b):
s = self.a + b.a
return s
end snippet
So, from the interpreter, if I issue:
x = bar(10)
y = bar(20)
z = x + y
print(z)
I get '30' back. That's correct.
I see how self.a is created, but I don't understand how b.a is getting created to do the addition.
Any guidance is appreciated.
When you call x + y it is actually translated to:
x.__add__(y)
Therefore, this method is called:
__add__(self, b) # __add__(x, y)
Which results in:
s = x.a + y.a # 30
return 30
In this code, b.a isn't being created, it is being accessed. You're basically passing in y as the argument b, which already has an a attribute associated with it since it is an object of type bar. If you want to step through your code go to http://www.pythontutor.com
x = bar(a) creates an object of the class bar with an a value of 'a'. Each bar object has a property/variable named a.
In x + y, the function add of x is called using y as the parameter.
So b = y, meaning b.a = y.a = 20.

Attempting to create a read-only property attribute - getter returns initialized value, direct access returns changed value

Although I may be very confused as to what the property() function does, I'm trying to use it to create an attribute which is read-only. Ideally, I'd like to be able to refer to the attribute directly but not be allowed to assign to it. When experimenting, I got this very curious behavior:
>>> class Boo(object):
... def __init__(self, x):
... self.__x = x
... def getx(self):
... return self.__x
... x = property(getx)
...
>>> b = Boo(1)
>>> b.__x = 2
>>> b.getx()
1
>>> b.__x
2
I'd like to add that when I used x and _x as the attribute names, reassigning the attribute caused the getter to return the changed value, i.e. both b.getx() and b.x/b._x gave me 2.
I realize that I'm using x as the property name, though, but when I tried the following I got an AttributeError in my __init__():
>>> class Boo(object):
... def __init__(self, x):
... self.__x = x
... def getx(self):
... return self.__x
... __x = property(getx)
...
>>> b = Boo(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
AttributeError: can't set attribute
The problem here has nothing to do with property, but with double-underscore attributes, which are subject to what's called "Private name mangling".
>>> b = Boo(1)
>>> '__x' in dir(b)
False
>>> '_Boo__x' in dir(b)
True
So, when you do this:
>>> b.__x = 2
You're not changing the value of the attribute the getx function is looking at, you're creating a new attribute.
If you just use a name for the attribute that doesn't start with two underscores—such as _x—everything works as you intended.
As a general rule, use a single underscore for "advisory private"—as in, "users of this object probably shouldn't care about this value", and a double underscore only when you actually need mangling (because of complex inheritance issues that rarely come up).
What if you want "real private", like C++ or Java? You can't have it. If you hide or protect the attribute well enough, someone will just monkeypatch the getx method or the x property. So, Python doesn't give a way to hide attributes.
Your problem is that using double underscore attribute names mangles the name. So when you are dealing with __x inside of your class definition, outside of the class it actually looks like _Boo__x. That is,
_ + (class name) + (double underscore attribute name)
To demonstrate,
>>> b = Boo(1)
>>> b.__x = 2
>>> b.getx()
1
>>> b.x # NOTE: same as calling getx
1
>>> b.__x # why didn't x return 2 if we changed it?
2
>>> b._Boo__x # because it's actually saved in this attribute
1
>>> b._Boo__x = 3 # setting it here then works
>>> b.x
3
>>> b.getx()
3
Really just wanted to comment (rather than answer) your question. I think you will find the following informative:
>>> b = Boo(1)
>>> print b.__dict__
{'_Boo__X': 1}
>>> b.__x = 2
>>> print b.__dict__
{'__x': 2, '_Boo__X': 1}
Might provide a hint as to the behavior (which I do not understand sufficiently well to explain).
It's because you don't have a setx function defined.
#!/usr/bin/python
class Boo(object):
def __init__(self, initialize_x):
self.x = initialize_x
def getx(self):
print
print '\t\tgetx: returning the value of x', self.__x
print
return self.__x
def setx(self, new_x):
print
print '\t\tsetx: setting x to new value',new_x
print
self.__x = new_x
x = property(getx, setx)
print '1 ########'
print
print '\tinitializing Boo object with a default x of 20'
print
o = Boo(20)
print '\treading the value of x through property o.x'
t = o.x
print
print '2 ########'
print
print '\tsetting x\'s value through the property o.x'
o.x = 100
print
print '3 ########'
print
print '\treading the value of x through the property o.x'
t = o.x
When run produces:
1 ########
initializing Boo object with a default x of 20
setx: setting x to new value 20
reading the value of x through property o.x
getx: returning the value of x 20
2 ########
setting x's value through the property o.x
setx: setting x to new value 100
3 ########
reading the value of x through the property o.x
getx: returning the value of x 100
I use property as decorator. It convenient for calculating data.
May be for make read-only attribute better use magic function as __set__ and __get__?

Sharing scope in Python between called and calling functions

Is there a way in Python to manage scope so that variables in calling functions are visible within called functions? I want something like the following
z = 1
def a():
z = z * 2
print z
def b():
z = z + 1
print z
a()
print z
b()
I would like to get the following output
2
4
2
The real solution to this problem is just to pass z as a variable. I don't want to do this.
I have a large and complex codebase with users of various levels of expertise. They are currently trained to pass one variable through and now I have to add another. I do not trust them to consistently pass the second variable through all function calls so I'm looking for an alternative that maintains the interface. There is a decent chance that this isn't possible.
I think this is what you're looking for. If you control the inner functions, and the variable you're looking for is already in scope somewhere in the call chain, but you don't want your users to have to include the variable in the call itself. You clearly don't want the global keyword, because you don't want inner assignments to affect the outer scope.
Using the inspect module gets you access to the calling context. (But, be warned that it's somewhat fragile. You should probably use only for CPython unthreaded.)
import inspect
def calling_scope_variable(name):
frame = inspect.stack()[1][0]
while name not in frame.f_locals:
frame = frame.f_back
if frame is None:
return None
return frame.f_locals[name]
z = 1
def a():
z = calling_scope_variable('z')
z = z * 2
print z
def b():
z = calling_scope_variable('z')
z = z + 1
print z
a()
print z
b()
This does give the right answer of:
2
4
2
This might be the right place for a class:
class MyClass(object):
def __init__(self, z):
self.z = 1
def a(self):
self.z = self.z * 2
print self.z
def b():
self.z = self.z + 1
print self.z
self.a()
print self.z
You can get the same effect with globals but I'd suggest creating your own separate namespace (such as with a class) and using that for your scope.
This is a bit ugly, but it avoids using globals, and I tested it and it works. Using the code from the selected answer found here, the function getSetZ() has a "static" variable that can be used to store a value that is passed to it, and then retrieved when the function is called with None as the parameter. Certain restrictions are that it assumes None is not a possible value for z, and that you don't use threads. You just have to remember to call getSetZ() right before each call to a function that you want the calling function's z to be available in, and to get the value out of getSetZ() and put it in a local variable in that function.
def getSetZ(newZ):
if newZ is not None:
getSetZ.z = newZ
else:
return getSetZ.z
def a():
z = getSetZ(None)
z = z * 2
print z
def b():
z = getSetZ(None)
z = z + 1
print z
getSetZ(z)
a()
print z
getSetZ.z = 0
getSetZ(1)
b()
I hope this helps.
I don't quite understand what you're trying to achieve, but this prints 2, 4, 2:
z = 1
def a():
global z
y = z * 2
print y
def b():
global z
z = z + 1
print z
a()
print z
b()
In general, the best solution is always just to pass the values around - this will be much more predictable and natural, easier to work with and debug:
def a(z):
z = z * 2
print z
def b():
global z
z = z + 1
print z
a(z)
print z
b()
You could define a() in b() and do something similar:
def b():
global z
z = z + 1
print z
def a():
z = z * 2
print z
a()
print z
However, this isn't really optimal.

Categories

Resources