Syntactic sugar for creating factory classes

I opened an issue in github with an idea proposal.
was told to post it here first.

attaching it here as well:

Feature or enhancement

When creating a factory (class that conforms to some abstract class and instantiates subclasses by providing some distinguishing attribute).

*written code I’m not sure will work as is but to indicate the idea why such syntactic sugar will be useful.
Current implementation iv’e encountered when implementing this behaviour in python:

class FactoryInterface(ABC):
  def __init__(*args, **kwargs):
    pass

attribute_to_constructor:[] = { "default":someConstructor}

class Factory(FactoryInterface):
  def __new__(cls,*args,__distinguishing_attribute:str="default",**kwargs):
    if distinguishing_attribute in attribute_to_constructor:
      return attribute_to_constructor[__distinguishing_attribute:str](*args,**kwargs)
 

Pitch

Required input:
Abstract class interface
Distinguishing keys mapping/list(keys that map to constructors)

Desired output:
Class that recieves the distinguishing attribute and its constructor passes its arguments to the mapped constructor.

example interface in python:

class FactoryInterface(ABC):
  def __init__(*args, **kwargs):
    pass

 sub_constructors = [a,b,c] # a,b,c are constructors  of classes inheriting/conforming to FactoryInterface

#by default map by variable(constructor/class) name or if not possible enforce name mapping

# functional way to create:
Factory:FactoryInterface = FactoryGenerator(sub_constructors,name_mapping:Optional[List]=None)

# class way:

class Factory(FactoryGenerator):
  a = constructorA
  b = constructorB
#properties names will be mapped to the constructor objects

a_instance = Factory('a')
b_instance = Factory('b')

2 Likes

I kinda know what factory classes are (every time I’ve looked up them I get confused), but I don’t think I’ve ever written one and I find your example hard to follow.

Starting with your “current example” could you clarify why you need the attribute_to_constructor variable, and why a __new__ method is necessary? Then, in the “pitch example”, could you explain what the FactoryGenerator is doing?

2 Likes

Yeah,

in the example:

I’m not sure attribute_to_constructor is necessary explicitly i want to abstract it away if possible. It’s purpose is to map from some distinguishing attribute(when instantiating the factory) to a specific constructor,
Il give a more concrete example:
Lets say I want to abstract away the filesystem’s interface similarly to fsspec.
I want to create an object that has the interface of a filesystem.
I don’t want to know(interface wise) if underneath i’m using local filesystem or google cloud storage or ftp.
What i wish to make easier is to be able to instantiate the file system object dynamically using a single constructor(the factory).

How i want it to work is that instantiating the Factory object will instantiate the corresponding subclass.
Using the __new__ method we can dynamically instantiate different subclasses while utilizing the interface of calling Factory() so it looks like you are just instantiating the generic class in the specific example it would be FileSystem() and using the parameters sent when invoking FileSystem i want to map to the wanted subclass constructor.

pitch:

I wanted to show 2 different interfaces/approaches of how it will look like when we want to generate a Factory without explicitly starting to implement the mapping that i think will make for a good experience when you need to create factories:

  1. functional(FactoryGenerator is a function) - FactoryGenerator is given all of the required info , in this case which constructors to map to, and it returns a Factory class which conforms to the suggested interface.
  2. class way - when inheriting the FactoryGenerator class(or maybe a metaclass will be more appropriate?) every property(its name) of the Factory class will actually be used as the key to map to a constructor which is its value. so you don’t have to explicitly handle the mapping.

I feel the semantics of your proposal can be easily and well achieved by writing a plain function:

def create_file_system(what, **kwargs):
    if what == ‘local’:
        return LocalFileSystem(**kwargs)
    elif what == ‘google’:
        return GoogleFileSystem(**kwargs)

Does this fulfill your requirement?

3 Likes

For making a specific Factory yeah it handles all of the logic.

I wanted to be able to generate factories(this function) without explicitly writing this function.
If theres a way to abstract it away in an easier interface.

I had needed this pattern twice in larger, somewhat recent, projects - and, I find that (1) it is a rather specific usecase which can be done in Python by a combination of __new__ and __init_subclass__ (for registering the subclasses) in a base-class that will behave as the factory class.

And (2) I can’t, even having used the pattern, understand your suggestion - the whole idea seems to make an already complex mechanism less explicit, and therefore, harder to follow - so it would need to be some thing rather nice for we to push for it.

but I really can; t figure out your example as is: the names “a, b. c” come out of nowehere (should they be in the factory class body, or at module level? your indentation is incorrect, btw). And then, one passes a string when instantiating a class, and out of nowhere the string is translated to a class attribute which is then used in your mechanism.

What if I want a custom predicate to pick my final subclass, instead of a specific content in one of the arguments when instantiating a class? (which where my cases).

And - finally, in all cases I’ve used this pattern it was somewhat due to fancy, and me being able to write the __new__ code correctly: writing a simple factory function to pick the subclass would have worked in both cases. So, this might even be an argument for such a feature, rather than a complaint against it, but I feel confortable with few lines of custom code in __new__ and __init_subclass__ in contrast with a specialised mechanism in this case.

2 Likes