1

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.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
X Champ
  • 11
  • 3
  • 2
    " I want to make a class, here called "Dynamic", who will inherit from a class given as an argument." That doesn't make much sense. Inheritance happens *when a class is defined* not when an instance is created. There's probably some hacky way you can get this done, but you almost certainly shouldn't. Likely, you just want some sort of proxy class. – juanpa.arrivillaga Jun 15 '20 at 20:31
  • 1
    So, here was a [very similar question](https://stackoverflow.com/questions/56746709/can-i-choose-the-parent-class-of-a-class-from-a-fixed-set-of-parent-classes-cond/56747123#56747123) where I gave an answer that demonstrated how to define a proxy class. – juanpa.arrivillaga Jun 15 '20 at 20:33
  • @juanpa.arrivillaga Thanks for your link. I had come up with a solution like yours, by redirecting the calls to a private variable, of the class I want to wrap. I was lacking the `__getattr__()` special method, and that does the trick perfectly. A proxy class is exactly what I was looking for, I will see if I can implement that in my project. Many thanks – X Champ Jun 17 '20 at 10:23
  • Just watch out, proxying like this won't work with dunder methods, e.g. `__eq__` or `__str__`. Often that's fine... [here's](https://stackoverflow.com/questions/59742625/dynamically-adding-builtin-methods-to-point-to-a-propertys-built-ins/59743116#59743116) a workaround I cooked up in case it isn't, and some other answers if that will be a problem for you... – juanpa.arrivillaga Jun 17 '20 at 10:26

3 Answers3

1

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.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
nightowl
  • 21
  • 3
  • Yes, that is exactly what I had happening, and I didn't know how to have it otherwise. I didn't explicit that, thanks – X Champ Jun 17 '20 at 09:44
1

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
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
  • This does seem to do the trick ! However, with the way I am designing this package, I will rather go with a proxy class proposed by @juanpa.arrivillaga in a higher comment. I will soon update my final solution – X Champ Jun 17 '20 at 11:01
0

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

Community
  • 1
  • 1
X Champ
  • 11
  • 3