Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Class inheritance
#1
Hello,
I am just learning programming with Python as my first language.

I want to build a small application to manage electronic parts.
All parts share some global attributes: ipn, mpn, dpn, manufacturer...

But then there are additional attributes that only parts from a specific category have.
For example a capacitor will have these additional attributes: dielectric, volts...

I've done it this way:

# Define parent class
class Part():
    def __init__(self, ipn, mpn, dpn, man):
        self.ipn = ipn
        self.mpn = mpn
        self.dpn = dpn
        self.manufacturer = man


# Define child class
class PartCapacitor(Part):
    def __init__(self, ipn, mpn, dpn, man, dielectric):
        super().__init__(ipn, mpn, dpn, man)
        self.dielectric = dielectric
A programming rule I've already learned is: "Dont repeat yourself".
However in the child class I have to repeat all arguments in line 12 and 13.

If I want to add an attribute to the parent class, I'll have to write it twice(!) in every single child class too?
If I have 10 child classes I'll need to add it in 20 places...

That sounds insane and absolutely wrong to me.
Am I doing it wrong? Is there an easier way?

And another question:
I want to add a method to print out the object's attributes.
But since the parent and sub classes will have different attributes, I am not sure where to create this method.
Should I create it in the parent and control which attributes to print with if statements?
Or should I create one print method in every child class? (Which means I have to repeat some parts over and over)
What's best practice here?

Thank you in advance Blush
Reply
#2
Use named arguments.
class Part():
    """Abstract base class for all parts"""
    
    def __init__(self, ipn=None, mpn=None, dpn=None, man=None):
        self.ipn = ipn
        self.mpn = mpn
        self.dpn = dpn
        self.manufacturer = man

    def __repr__(self):
        return f'{type(self).__name__}: {self.ipn}, {self.mpn}, {self.dpn}, {self.manufacturer}'

class Capacitor(Part):
    """Capacitor part"""
    def __init__(self, dielectric=None, **kvargs):
        super().__init__(**kvargs)
        self.dielectric = dielectric

    def __repr__(self):
        return f'{super().__repr__()}, {self.dielectric}'


cap = Capacitor(ipn='a', mpn='b', dpn='c', man='d', dielectric='e')
print(cap)
Using a bunch of position arguments is bad for many reasons. It makes refactoring code difficult. It makes it easy to mix up the order of arguments and pass the wrong value to an argument. It is hard to remember the order so you have to keep referencing the function. It makes the code difficult to read. Named arguments fix all of these problems and they make it so your subclasses only have to declare additional arguments that are not handled by the superclass.

Another way to do this is initialize all your class attributes to None and set them outside the __init__.
class Part():
    """Abstract base class for all parts"""
    
    def __init__(self,):
        self.ipn = None
        self.mpn = None
        self.dpn = None
        self.manufacturer = None

    def __repr__(self):
        return f'{type(self).__name__}: {self.ipn}, {self.mpn}, {self.dpn}, {self.manufacturer}'

class Capacitor(Part):
    """Capacitor part"""
    def __init__(self, **kvargs):
        super().__init__(**kvargs)
        self.dielectric = None

    def __repr__(self):
        return f'{super().__repr__()}, {self.dielectric}'


cap = Capacitor()
cap.ipn = 'a'
cap.mpn = 'b'
cap.dpn = 'c'
cap.manufacturer = 'd'
cap.dielectric = 'e'
oclmedyb likes this post
Reply
#3
@deanhystad Thank you! **kwargs and __repr__ was exactly what I was looking for.
Couldn't imagine a better answer. Wink

I already intended to use named arguments. I haven't noticed that I actually didn't...
Reply
#4
One problem with relying on named arguments is that you cannot use position arguments. You could not do this:
cap = Capacitor(ipn, mpn, dpn, manufacturer, dielectric)
Output:
TypeError: Capacitor() takes 1 positional argument but 5 were given
I've noticed some functions in the Python standard libraries that have this same problem/limitation.
Quote:random.choices(population, weights=None, *, cum_weights=None, k=1)
I cannot call random.choices like this:
c = random.choices(values, weights, None, 10)
"cum_weights" and "k" are name only arguments. This is enforced by "*" in the signature. If you want to use name-only arguments you could do the same.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  super() and order of running method in class inheritance akbarza 7 594 Feb-04-2024, 09:35 AM
Last Post: Gribouillis
  Child class inheritance issue eakanathan 3 1,299 Apr-21-2022, 12:03 PM
Last Post: deanhystad
  Importing issues with base class for inheritance riccardoob 5 4,595 May-19-2021, 05:18 PM
Last Post: snippsat
  3D vector class with inheritance from 2D vector class buss0140 4 3,077 Dec-20-2020, 08:44 PM
Last Post: deanhystad
  Can we access instance variable of parent class in child class using inheritance akdube 3 13,887 Nov-13-2020, 03:43 AM
Last Post: SalsaBeanDip
  Performance degradation with IO class inheritance wsygzyx 2 2,092 Jun-18-2020, 06:02 PM
Last Post: wsygzyx
  Confusion with sublcassing a threading class, and inheritance bigmit37 2 7,569 Apr-08-2019, 09:28 AM
Last Post: DeaD_EyE
  Class Attributes Inheritance Harry_Potter 3 3,794 Nov-16-2017, 07:01 PM
Last Post: snippsat

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020