Python Design – Best Practices for Python Factory Functions

designpython

Suppose I have a file foo.py containing a class Foo:

class Foo(object):
   def __init__(self, data):
      ...

Now I want to add a function that creates a Foo object in a certain way from raw source data. Should I put it as a static method in Foo or as another separate function?

class Foo(object):
   def __init__(self, data):
      ...
# option 1:
   @staticmethod
   def fromSourceData(sourceData):
      return Foo(processData(sourceData))

# option 2:
def makeFoo(sourceData):
   return Foo(processData(sourceData))

I don't know whether it's more important to be convenient for users:

foo1 = foo.makeFoo(sourceData)

or whether it's more important to maintain clear coupling between the method and the class:

foo1 = foo.Foo.fromSourceData(sourceData)

Best Answer

The choice should between a factory function and a third option instead; the class method:

class Foo(object):
   def __init__(self, data):
      ...

   @classmethod
   def fromSourceData(klass, sourceData):
      return klass(processData(sourceData))

Classmethod factories have the added advantage that they are inheritable. You can now create a subclass of Foo, and the inherited factory method would then produce instances of that subclass.

With a choice between a function and a class method, I'd choose the class method. It documents quite clearly what kind of object the factory is going to produce, without having to make this explicit in the function name. This becomes much clearer when you not only have multiple factory methods, but also multiple classes.

Compare the following two alternatives:

foo.Foo.fromFrobnar(...)
foo.Foo.fromSpamNEggs(...)
foo.Foo.someComputedVersion()
foo.Bar.fromFrobnar(...)
foo.Bar.someComputedVersion()

vs.

foo.createFooFromFrobnar()
foo.createFooFromSpamNEggs()
foo.createSomeComputedVersionFoo()
foo.createBarFromFrobnar()
foo.createSomeComputedVersionBar()

Even better, your end-user could import just the Foo class and use it for the various ways you can create it, as you have all the factory methods in one place:

from foo import Foo
Foo()
Foo.fromFrobnar(...)
Foo.fromSpamNEggs(...)
Foo.someComputedVersion()

The stdlib datetime module uses class method factories extensively, and it's API is much the clearer for it.

Related Topic