How to write factory functions for subclasses?

Viewed 76

Suppose there is a class A and a factory function make_A


class A():
...


def make_A(*args, **kwars):
# returns an object of type A

both defined in some_package.

Suppose also that I want to expand the functionality of A, by subclassing it, without overriding the constructor:

from some_package import A, make_A

class B(A):

    def extra_method(self, ...):
    # adds extra functionality 

What I also need is to write a new factory function make_B for subclass B.

The solution I have found so far is

def make_B(*args, **kwargs):
    """
    same as make_A except that it returns an object of type B
    """
    out = make_A(*args, **kwargs)
    out.__class__ = B
    return out

This seems to work, but I am a bit worried about directly modifying the __class__ attribute, as it feels to me like a hack. I am also worried about unexpected side-effects this modification may have. Is this the recommended solution or is there a "cleaner" pattern to achieve the same result?

2 Answers

I guess I finally found something not verbose yet still working. For this you need to replace inheritance with composition, this will allow to consume an object A by doing self.a = ....

To mimic the methods of A you can use __getattr__ overload to delegate those methods (and fields) to self.a

The next snippet works for me

class A:
   def __init__(self, val):
      self.val = val
   def method(self):
      print(f"A={self.val}")

def make_A():
   return A(42)

class B:
    def __init__(self, *args, consume_A = None, **kwargs):
        if consume_A is None:
            self.a = A(*args, **kwargs)
        else:
            self.a = consume_A

    def __getattr__(self, name):
        return getattr(self.a, name)

    def my_extension(self):
        print(f"B={self.val * 100}")

def make_B(*args, **kwargs):
    return B(consume_A=make_A(*args, **kwargs))

b = make_B()
b.method() # A=42
b.my_extension() # B=4200

What makes this approach superior to yours is that modifying __class__ is probably not harmless. On the other hand __getattr__ and __getattribute__ are specifically provided as the mechanisms to resolve attributes search in an object. For more details, see this tutorial.

Make your original factory function more general by accepting a class as parameter: remember, everything is an object in Python, even classes.

def make(class_type, *args, **kwargs):
    return class_type(*args, **kwargs)


a = make(A)
b = make(B)

Since B has the same parameters as A, you don't need to make an A and then turn it into B: B inherits from A, so it "is an A" and will have the same functionality, plus the extra method that you added.

Related