How to make child class use parent's default values? - python

I have a parent class:
class BaseClass:
def __init__(self, foo, bar=10):
self.foo = foo
And an inherited class from it:
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=10):
super().__init__(True, bar=bar)
self.x = x
self.y = y
At the moment, bar has a default value in both the parent and the inherited class, which I have to keep track of to make sure they are both synced. How can I change the InheritedClass constructor to instead use the default value for bar defined in the BaseClass?
I've tried:
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=None):
if bar is None:
super().__init__(True)
else:
super().__init__(True,bar)
self.x = x
self.y = y
which provides the behavior I'm looking for, but doesn't seem very scalable. Is there a better way?

Don't expose the parent's arguments at all in the signature; just pass what you receive on (if anything) on to the next class in the MRO.
class BaseClass:
def __init__(self, foo, bar=10, **kwargs):
super().__init__(**kwargs)
# Do something with bar
self.foo = foo
class InheritedClass(BaseClass):
def __init__(self, x, y, **kwargs)
kwargs['foo'] = True
super().__init__(**kwargs)
self.x = x
self.y = y
You have to accept and pass on arbitrary keywords anyway to use super properly, so you may as well do the same thing for the parent class's arguments, even though you "know" them already.
This also means always using keyword arguments for __init__, as you can't really predict how your class's positional arguments will interact with another class on the MRO. (Positional arguments can only be consumed on a first-come, first-served basis, and you simply don't know in the case of multiple inheritance when your class's method will be called.)
foo = InheritedClass(bar=9, x=3, y=10)

You can use variable arguments instead:
class InheritedClass(BaseClass):
def __init__(self, x, y, *args, **kwargs):
super().__init__(True, *args, **kwargs)
self.x = x
self.y = y

Your approach is not bad and yes there is a better way to do it maybe better than mine but here is what I come up with hope this helps.
Sample:
class BaseClass:
def __init__(self, foo, bar=10):
self.foo = foo
self.bar = bar
class InheritedClass(BaseClass):
def __init__(self, x, y, bar=None):
super().__init__(True, bar=bar)
if bar is None: bar = self.bar
self.x = x
self.y = y

Related

Can you make a class attribute a specific instance of that class within the class definition?

Was wondering if there was a way to set a class attribute to a specific instance from within the class definition. For example,
class Value:
def __init__(self, x):
self.x = x
# Something like
# half = Value(0.5)
>>> Value.half.x
0.5
>>> Value.half.half.x
0.5
I'm also aware I can easily set it outside the class that seems a bit more bulky and error prone, like this
class Value:
def __init__(self, x):
self.x = x
Value.half = Value(0.5)
>>> Value.half.x
0.5
>>> Value.half.half.x
0.5
No. At the time the body of the class is being evaluated, the class doesn't yet exist. A class statement is a declarative syntax for calling a metaclass:
class Value:
def __init__(self, x):
self.x = x
is roughly equivalent to
def init(self, x):
self.x = x
Value = type('Value', (object,), {'__init__': init})
Your class attribute would have to be a member of the dict passed as the third argument, which has to be fully defined before type is called.
not quite, but you can make a class method that return a new instance of your class in whatever way you want with the classmethod decorator
>>> class Value:
def __init__(self, x):
self.x=x
def __repr__(self):
return f"{type(self).__name__}({self.x})"
#classmethod
def half(cls):
return cls(0.5)
>>> Value(10)
Value(10)
>>> Value.half()
Value(0.5)
>>>
look like in py3.9 you can combine it with the property decorator to accomplish just that, see linked documentation above (but I don't have it at the moment)
Simply, you can't because the class hasn't yet existed. But you can use either metaclass or class decorator to achieve the same goal as the following shows:
#Metaclass
class Meta(type):
def __init__(cls, clsname, clsbases, clsdict):
cls.half = cls(0.5)
class Value(metaclass=Meta):
def __init__(self, x):
self.x = x
#Decorator
def decorator(cls):
cls.half = cls(0.5)
return cls
#decorator
class Value2:
def __init__(self, x):
self.x = x
print(Value.half.half.x)
print(Value.half.x)
print(Value2.half.half.x)
print(Value2.half.x)

Is there an accepted pythonic way of splitting off a base class part from a subclass object?

I am looking for the best way to split off the base class content of a subclass object. That is, if I have an object b of class B, which subclasses class A, I would like a method for class B that returns an object a of class A with the relevant content taken from b (which is a subset of all the content in b). Currently I have roughly the following (the actual classes are more complicated of course, this is a simplified toy example)
class A:
def __init__(self):
self.x = 0
def update(self, dx):
self.x += dx
class B(A):
def __init__(self):
super().__init__()
self.y = None
def update(self, dx):
super().update(dx)
self.y = self.x*self.x
def split_off_base_part(self):
a = A()
a.__dict__.update({k: v for k, v in self.__dict__.items() if k in a.__dict__})
return a
but I am not too happy with it. Is there a more pythonic way of doing this? Possibly one that doesn't require calling A.__init__ or is this simply not possible in general?
You could re-define the init for A so that it can optionally take parameters, that is nice than trying to update the __dict__.
class A:
def __init__(self, _x = 0):
self.x = _x
def update(self, dx):
self.x += dx
class B(A):
def __init__(self):
super().__init__()
self.y = None
def update(self, dx):
super().update(dx)
self.y = self.x*self.x
def toA(self):
return A(self.x)
Or you could define that as a class-method (almost like a second constructor) on A, which takes a B and returns a stripped down A.

Should I use the class name or `cls` param to construct a an instance?

As title described, I an confused as the example:
class Point(object):
def __init__(self, x=0.0, y=0.0):
self.x, self.y = x, y
#classmethod
def get_point1(cls, cor): # cor is list with x=1 and y=2
return Point(cor[0], cor[1])
#classmethod
def get_point2(cls, cor):
return cls(cor[0], cor[1])
I am confused which one(get_point1 or get_point2) should I use, and what is the difference between them?
The #classmethod decorator makes the function a class method, as opposed to instance method. To make it more robust, it is preferable to use cls rather than the actual class name where it is defined.
If you use cls, the parameter which will be passed depends on the actual class being called (for example, if you subclass Point) while using Point explicity, may cause issues if you subclass it and use the class method.
Look at this code for example
class Point(object):
def __init__(self, x=0.0, y=0.0):
self.x, self.y = x, y
#classmethod
def get_point1(cls, cor): # cor is list like [1,2] with x=1 and y=2
return Point(cor[0], cor[1])
#classmethod
def get_point2(cls, cor):
return cls(cor[0], cor[1])
class SubPoint(Point):
pass
sub1 = SubPoint.get_point1([0, 1])
sub2 = SubPoint.get_point2([2, 2])
print sub1.__class__
print sub2.__class__
<class '__main__.Point'>
<class '__main__.SubPoint'>
Is there any other difference? - well, if you need to do some logic inside your class method, which depends on class attributes, then yes it does.

Adding to the dir() of instance

I have a class (named "A") with some instance variables. I want to add the dir() of this variables to the dir() of instances of class A.
For example:
class A(object):
def __init__(self, x, y):
self.x = x
self.y = y
class X(object):
def f_x(self):
pass
class Y(object):
def f_y(self):
pass
x = X(); y = Y()
a = A(x,y)
I want f_x and f_y to appear in
dir(a)
Is there a better way, or a more 'correct' one, than just iterating X.dict and Y.dict and for each element, use something like:
setattr(A, str(element), element)
Thanks.
A should really be a subclass of X and Y in this case. (Just be sure to read Michele Simionato's article on super and diamond inheritence before you get too deep into it.)
class X(object):
def f_x(self):
pass
class Y(object):
def f_y(self):
pass
class A(X, Y):
def __init__(self, *args, **kwargs): # splats optional
# do what you need to here
dir(A(X(),Y())) # Ah! Lisp!
However, if you really need things to be magic, then just override __getattr__ for X to look in self.x and self.y before throwing an error. But seriously, don't do this.
Why don't you simply inherit from both classes?
class B(A, X):
pass
a = B()
dir(a)

How do I initialize the base (super) class?

In Python, consider I have the following code:
class SuperClass(object):
def __init__(self, x):
self.x = x
class SubClass(SuperClass):
def __init__(self, y):
self.y = y
# how do I initialize the SuperClass __init__ here?
How do I initialize the SuperClass __init__ in the subclass? I am following the Python tutorial and it doesn't cover that. When I searched on Google, I found more than one way of doing. What is the standard way of handling this?
Python (until version 3) supports "old-style" and new-style classes. New-style classes are derived from object and are what you are using, and invoke their base class through super(), e.g.
class X(object):
def __init__(self, x):
pass
def doit(self, bar):
pass
class Y(X):
def __init__(self):
super(Y, self).__init__(123)
def doit(self, foo):
return super(Y, self).doit(foo)
Because python knows about old- and new-style classes, there are different ways to invoke a base method, which is why you've found multiple ways of doing so.
For completeness sake, old-style classes call base methods explicitly using the base class, i.e.
def doit(self, foo):
return X.doit(self, foo)
But since you shouldn't be using old-style anymore, I wouldn't care about this too much.
Python 3 only knows about new-style classes (no matter if you derive from object or not).
As of python 3.5.2, you can use:
class C(B):
def method(self, arg):
super().method(arg) # This does the same thing as:
# super(C, self).method(arg)
https://docs.python.org/3/library/functions.html#super
Both
SuperClass.__init__(self, x)
or
super(SubClass,self).__init__( x )
will work (I prefer the 2nd one, as it adheres more to the DRY principle).
See here: http://docs.python.org/reference/datamodel.html#basic-customization
How do I initialize the base (super) class?
class SuperClass(object):
def __init__(self, x):
self.x = x
class SubClass(SuperClass):
def __init__(self, y):
self.y = y
Use a super object to ensure you get the next method (as a bound method) in the method resolution order. In Python 2, you need to pass the class name and self to super to lookup the bound __init__ method:
class SubClass(SuperClass):
def __init__(self, y):
super(SubClass, self).__init__('x')
self.y = y
In Python 3, there's a little magic that makes the arguments to super unnecessary - and as a side benefit it works a little faster:
class SubClass(SuperClass):
def __init__(self, y):
super().__init__('x')
self.y = y
Hardcoding the parent like this below prevents you from using cooperative multiple inheritance:
class SubClass(SuperClass):
def __init__(self, y):
SuperClass.__init__(self, 'x') # don't do this
self.y = y
Note that __init__ may only return None - it is intended to modify the object in-place.
Something __new__
There's another way to initialize instances - and it's the only way for subclasses of immutable types in Python. So it's required if you want to subclass str or tuple or another immutable object.
You might think it's a classmethod because it gets an implicit class argument. But it's actually a staticmethod. So you need to call __new__ with cls explicitly.
We usually return the instance from __new__, so if you do, you also need to call your base's __new__ via super as well in your base class. So if you use both methods:
class SuperClass(object):
def __new__(cls, x):
return super(SuperClass, cls).__new__(cls)
def __init__(self, x):
self.x = x
class SubClass(object):
def __new__(cls, y):
return super(SubClass, cls).__new__(cls)
def __init__(self, y):
self.y = y
super(SubClass, self).__init__('x')
Python 3 sidesteps a little of the weirdness of the super calls caused by __new__ being a static method, but you still need to pass cls to the non-bound __new__ method:
class SuperClass(object):
def __new__(cls, x):
return super().__new__(cls)
def __init__(self, x):
self.x = x
class SubClass(object):
def __new__(cls, y):
return super().__new__(cls)
def __init__(self, y):
self.y = y
super().__init__('x')

Categories