Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write factory functions for subclasses?

Tags:

python

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?

like image 673
zap Avatar asked Feb 03 '26 19:02

zap


1 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.

like image 195
Alexey Larionov Avatar answered Feb 05 '26 08:02

Alexey Larionov