X Champ
X Champ

Reputation: 11

Inherit from a class given as an argument

My setup: I am coding in python 3.7, with spyder4, on a windows10 machine.

Context

I am writing classes for a package. It is some kind of handler for classes defined in another package that I am importing.

Here is a simplified version of the situation:

# This I cannot modify, as they are in the package
class Letter_Class():
    def __init__(self, name):
        self.name = name

class A(Letter_Class):
    def source(self):
        print("from class A")
    def funcA(self):
        print("from class A")

class B(Letter_Class):
    def source(self):
        print("from class B")
    def funcB(self):
        print("from class B")

class C(Letter_Class):
    def source(self):
        print("from class C")
    def funcC(self):
        print("from class C")

# This I can modify
class Dynamic(Letter_Class):
    def __new__(self, name, this_class): # -------- issue lies here? --------
        obj = this_class(name)
        return obj

    def new_func(self):
        print("function defined in dynamic class")

The classes Letter_Class, A, B, Chave already been defined by someone else (in that 'other package' I am handling) and I cannot change them. I want to make a class, here called "Dynamic", who will inherit from a class given as an argument. It must still have the (many, many) methods that I wish to add such as new_func(). I cannot know in advance which Letter_Class child is used.

Classes A, B, C have both polymorphic and specific methods, such as source() and funcA(). Because of that, I cannot make Dynamic inherit from these classes. It would have been too easy.

Problem

If I execute the previous and the following code:

# user executes in main: 
instance = Dynamic("test name", B)
print(instance.name)
instance.source()
instance.funcB()
instance.new_func()

I get:

test name  
from class B  
from class B  

AttributeError: 'B' object has no attribute 'new_func'  

The instance is created, source() and funcB() are executed without any problem. Then, an attribute error is raised, because a Device object was not truly created.

Could anyone explain to me what to put in the __new__() constructor function to actually return an instance of the Device class, with the proper inheritance from this_class? I would be very grateful.
This discusion seems to address the issue, but in a different context and I cannot apply the suggested solution.

Upvotes: 1

Views: 165

Answers (3)

X Champ
X Champ

Reputation: 11

Solutions

I have found a solution to MY problem (thanks to the SO community). To all coders searching for a solution to THEIR problem, the use case might make you want to choose one of these two solutions:

factory functions

Using a factory function that builds your class - see Reblochon Masque's answer for that.

using a proxy class

This was suggested by juanpa.arrivillaga in this question.

This is the solution I believe I will be using from now on:

# classes Letter_Class, A, B, and C are the same as in the "Context" section (above)  
class Proxy():
    """ attempt to make a transparent proxy
    """
    def __init__(self, name, letterclass, *args, **kwargs):
        self.letter = letterclass(name, *args, **kwargs)

    def __getattr__(self, attribute):
        return getattr(self.letter, attribute)


class Dynamic(Proxy):
    """ My actual class and its methods
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # anything else to initiate the instance

    def new_func(self):
        print("calling a method defined in the Dynamic class")


I can now execute:

instance = Dynamic("foo bar", B)
print(f"instance is called: '{instance.name}'")
instance.source()
instance.funcB()
instance.new_func()

I will get:

instance is called: 'foo bar'
from class B
from class B
calling a method defined in the Dynamic class

Any calls to the attributes that are NOT defined in the Dynamic class will be redirected to self.letter, with the magic method: __getattr__(). Using __getattribute__() instead would redirect ALL calls to self.letter instead.

I chose this approach specifically because I expect my users to be building their own type of Dynamic class; I find it simpler to make an intermediate proxy class.

This resource was also helpful, and has clues to make a much more transparent proxy class: Object proxying. I did not attempt to implement this though.

So, thanks for the help folks, Take care.
XC

Upvotes: 0

Reblochon Masque
Reblochon Masque

Reputation: 36732

You could use a factory function that builds your class instances using the given class to inherit from:

class Letter_Class:
    def __init__(self, name):
        self.name = name

class A(Letter_Class):
    def source(self):
        print("from class A")
    def funcA(self):
        print("from class A")

class B(Letter_Class):
    def source(self):
        print("from class B")
    def funcB(self):
        print("from class B")

class C(Letter_Class):
    def source(self):
        print("from class C")
    def funcC(self):
        print("from class C")


def make_dynamic_letterclass_factory(cls):
    """makes an returns a Letter_Class subclass instance that inherits
    from the class passed as argument
    """
    class Dynamic(cls):
        def __init__(self):
            pass
        def new_func(self):
            print("function defined in dynamic class")

    return Dynamic()


a = make_dynamic_letterclass_factory(A)
a.source(), a.funcA()

b = make_dynamic_letterclass_factory(B)
b.source(), b.funcB()

c = make_dynamic_letterclass_factory(C)
c.source(), c.funcC()

output:

from class A
from class A
from class B
from class B
from class C
from class C

Upvotes: 1

nightowl
nightowl

Reputation: 21

The problem is that you're setting instance to equal the return value of the Letter_Class; instance is a B object, and has no relationship with Dynamic.

Upvotes: 1

Related Questions