Coming to Python from Java, I've been told that factories are not Pythonic. Thus, I'm looking for a the Python way to do something like the following. (I'm oversimplifying my goal so that I don't have to describe my entire program, which is very complicated).
My script will read in names of people (along with some information about them) and, from this, construct objects of type Person. The names may be repeated, and I only want one Person instance per name. These People may also belong to subclasses Man and Woman.
One way to do this would be to create a PersonFactory which would either return a newly instantiated Man or Woman or a reference to the previously instantiated Man/Woman with the same name. The other would be to create a set of all Person objects and check each time for the presence of a Person with the given name before instantiating a new object. Neither approach strikes me as Pythonic, though. The first seems a bit too cumbersome for Python (creating a whole class just to handle creation of another object? Really?) and the second will get expensive quickly, as I have a lot of names to process.
I don't think factories are un-Pythonic. You don't need a whole class, though. One big difference between Java and Python is that in Python you can have code outside of classes. So you might want to create a factory function. Or you can make the factory be a class method on the Person class:
class Person:
name_map = {}
@classmethod
def person_from_name(cls, name):
if name not in cls.name_map:
cls.name_map[name] = cls(name)
return cls.name_map[name]
def __init__(self, name):
etc...
Often the same patterns are at work in Python code as in Java, but we don't make as big a deal of it. In Java, you'd have a whole new class, which would imply a whole new .java file, and you'd need to make it a singleton, etc, etc. Java seems to breed this sort of complexity. A simple class method will do, so just use it.
A free-standing function def PersonFactory(name, gender):
is fine, though packaging it up as a classmethod, as @Ned suggests, shouldn't hurt (in this particular case it won't help much either, since the exact class of person to instantiate must vary). I think the cleanest implementation is in fact as a free-standing function, just because I prefer a classmethod to return instances of the class it's called on (rather than, of some other class) -- but this is a stylistic point that cannot be said to be sharply defined either way.
I'd code it (with some assumptions which I hope as clear, e.g. the gender is coded as M
or F
and if not specified is heuristically inferred from the name, &c):
def gender_from_name(name): ...
person_by_name = {}
class_by_gender = {'M': Man, 'F': Woman}
def person_factory(name, gender=None):
p = person_by_name.get(name)
if p is None:
if gender is None:
gender = gender_from_name(name)
p = person_by_name[name] = class_by_gender[gender](name)
return p
The place to put a "no two objects with same key" registration is in __new__
, like this:
class Person(object):
person_registry = {}
mens_names = set('Tom Dick Harry'.split())
womens_names = set('Mary Linda Susan'.split())
gender = "?"
def __new__(cls, *args):
if cls is Person:
fname,lname = args[0].split()
key = (lname, fname)
if key in Person.person_registry:
return Person.person_registry[key]
if fname in Person.mens_names:
return Man(*args)
if fname in Person.womens_names:
return Woman(*args)
else:
return object.__new__(cls, *args)
def __init__(self, name):
fname,lname = name.split()
Person.person_registry[(lname, fname)] = self
class Man(Person):
gender = "M"
class Woman(Person):
gender = "W"
p1 = Person("Harry Turtledove")
print p1.__class__.__name__, p1.gender
p2 = Person("Harry Turtledove")
print p1 is p2
prints:
Man M
True
I also took a stab at your Man/Woman distinction, but I'm not thrilled with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With