4

Below is a pattern from :https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Factory.html . My question is this, is this still the best idiom/pattern to do generic object creation in Python 3.x? I can't seem to find much on this topic. The code is below:

class Shape(object):
    # Create based on class name:
    def factory(type):
        #return eval(type + "()")
        if type == "Circle": return Circle()
        if type == "Square": return Square()
        assert 0, "Bad shape creation: " + type
    factory = staticmethod(factory)

class Circle(Shape):
    def draw(self): print("Circle.draw")
    def erase(self): print("Circle.erase")

class Square(Shape):
    def draw(self): print("Square.draw")
    def erase(self): print("Square.erase")

# Generate shape name strings:
def shapeNameGen(n):
    types = Shape.__subclasses__()
    for i in range(n):
        yield random.choice(types).__name__

shapes = \
  [ Shape.factory(i) for i in shapeNameGen(7)]

for shape in shapes:
    shape.draw()
    shape.erase()

You can also create a factory by using the __class__ method as well I've noticed, but I'm unsure of the best way to use this.

code base 5000
  • 3,812
  • 13
  • 44
  • 73
  • 2
    I don't see much use for the `factory` method or `shapeNameGen` here. Why not just `types = Shape.__subclasses__()`; `shapes = [random.choice(types)() for _ in range(7)]`? Why hardcode the strings `'Circle'` and `'Square'` if you don't have to? The factories for `Circle` and `Square` are `Circle` and `Square`. – timgeb Oct 23 '18 at 11:47
  • I have a situation where I could have multiple geometries being given. This is an over simplified example, but I am getting JSON response of various geometries, and instead of having to write multiple if statements multiple times, I thought using a Factory could reduce the issue: so Shape(JSON) -> Circle or Shape(JSON) - Square, etc... – code base 5000 Oct 23 '18 at 11:53
  • 3
    I still think this factory is awkward.You could have a dictionary `{'Circle': Circle, ...}` or instantiate a class [dynamically by name](https://stackoverflow.com/questions/4821104/python-dynamic-instantiation-from-string-name-of-a-class-in-dynamically-imported). – timgeb Oct 23 '18 at 11:56
  • @timeb Thanks for the suggestion, I'm going to check out the dynamically by name link. – code base 5000 Oct 23 '18 at 12:14
  • the reason you don’t find much existing info is this pattern isn’t very useful in Python. Now, if you had a complex set of rules that the Shape class had to work out and really didn’t concern the rest of the code, then maybe. As in dumping a chunk of json and letting the factory’s internals decide. But it’s overkill in this case. Also Python classes often struggle a bit knowing about themselves, making this awkward. – JL Peyret Oct 25 '18 at 00:56

1 Answers1

1

I could be missing something, but I don't like this pattern.

You already have factories for Circle and Square - Circle and Square. :)

The code in your question unnecessarily hardcodes the class names in factory and then goes through some extra hoops by getting the names of the subclasses of Shape and then calling factory with those names.

A more direct way to generate the shapes list is

types = Shape.__subclasses__()
shapes = [random.choice(types)() for _ in range(7)]

I have a situation where I could have multiple geometries being given. This is an over simplified example, but I am getting JSON response of various geometries, and instead of having to write multiple if statements multiple times, I thought using a Factory could reduce the issue: so Shape(JSON) -> Circle or Shape(JSON) - Square

This does not justify the factory as it is coded here. You could have a simple dictionary like

classes = {'Circle': Circle, 'Square': Square, ...}

or possibly create it dynamically with

classes = {cls.__name__:cls for cls in Shape.__subclasses__()}

and then call classes[some_string]() for the instantiation. You can even dynamically instantiate a class by string name using getattr.

timgeb
  • 76,762
  • 20
  • 123
  • 145