Home > Blockchain >  Extend a Python class functionality from a different class
Extend a Python class functionality from a different class

Time:03-24

I have a number of Python classes, Say, Class1, Class2, Class3 etc from a library/package. I want to extend all of the classes with some common functionalities. If I extend each class individually, we introduce a lot of redundancy and break the "Don't Repeat Yourself" acronym. So, my thought is to have a Base class and use it extend other classes. For example:

class Base:
    def __init__(self):
        # I want self.base_attr_1, self.base_attr_2 and so on...
    def base_method_1(self, *args, **kwargs):
        pass
    def base_method_2(self, *args, **kwargs):
        pass
    # and so on...

So we can extend Class1, Class2 and so on using maybe Multiple inheritances. Say,

class Class1(Class1, Base):
    pass
class Class2(Class2, Base):
    pass
# and so on...

So that at last when I create an object of Class1, Class2 etc., I can use the Base class attributes and methods. Like;

class_1 = Class1(*args, **kwargs)
print(class_1.base_attr_1)
print(class_1.base_attr_2)
class_1.base_method_1(*args, **kwargs)
class_2.base_method_2(*args, **kwargs)
# and so on..

Please explain how to implement the Class1, Class2 etc, to extend the Base class.

Any help is highly appreciated. Thank you.

CodePudding user response:

following your description, you would have two possibilities to handle your issue:

  1. metaclass
  2. decorator

If I was you, I would try something like this (decorator solution) :

from functools import wraps
def deco(cls):
    def test(x):
        return x**2

    d = {k:v for k,v in locals().items() if k != "cls"}

    @wraps(cls)
    def wrapper(*args, **kwargs):
        o = cls(*args, **kwargs)
        #o.test = test # setattr(o,"test", test) will be better solution,
                       # if you have more elements, which you'd like to add
        # generalized :
        # ============
        for k,v in d.items(): setattr(o,k,v)
        return o
    return wrapper

@deco
class A(object):
    pass

a = A()
print(a.__dict__)
print(a.test(10))

Result:

{'test': <function test at 0x02843C70>}
100

CodePudding user response:

When you have an inheritance setup like this:

class MyClass1(Base, ExternalClass1):

Calling super().__init__() will call Base.__init__() (first inherited class probably) and hence only initialize attributes in Base. You can explicitly call the init functions of all ancestors on the same inheritance "level" like this:

class MyClass1(Base, ExternalClass1):
    def __init__(self) -> None:
        Base.__init__(self)
        ExternalClass1.__init__(self)

That will initialize attributes from both Base and ExternalClass1.

Example:

class ExternalClass1():
    def __init__(self) -> None:
        self.external_attr_1 = "external attr"
    def external_func1(self):
        print("external_func1")


class Base():
    def __init__(self) -> None:
        self.base_attr_1 = "base attr"
    def base_method_1(self):
        print("base_method_1")


class MyClass1(Base, ExternalClass1):
    def __init__(self) -> None:
        Base.__init__(self)
        ExternalClass1.__init__(self)
        self.my_attr_1 = "my class attr"
        self.external_func1()
        self.base_method_1()
        self.my_class_1_func()
        print(self.external_attr_1)
        print(self.base_attr_1)
        print(self.my_attr_1)
    def my_class_1_func(self):
        print("my_class_1_func")

cl1 = MyClass1()

Outputs:

external_func1
base_method_1
my_class_1_func
external attr
base attr
my class attr

Note however that self is now shared between all 3 classes, which can cause clashes. Another pattern is of course to go for composition, and have the external class be a property of MyClass1. This is both safer and introduces less coupling.

  • Related