开发者

Moving Beyond Factories in Python

开发者 https://www.devze.com 2023-01-12 22:37 出处:网络
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

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 des开发者_如何学Ccribe 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.


class Person(object):
    # ...

class Man(Person):
    # ...

class Woman(Person):
    # ...

constructors = {
    'male': Man,
    'female': Woman,
    None: Person,
}

people = {}

# in processing loop
if person.name not in people:
    people[person.name] = constructors[person.gender]()
person_object = people[person.name]

Since Python allows you to do things like calling storing class types in a dict, you don't need a factory; you can just look up a type and instantiate it.


Flyweight pattern in Python? (appears to be broken, Internet Archive version)

Edit: Another flyweight example in Python.


The easiest way is probably to use builtin lru_cache from functools on __new__.

import functools

class Person:
    gender = 'unknown'
    @functools.lru_cache(maxsize=None)
    def __new__(cls, full_name):
        names = first, last = full_name.split()
        for subclass in cls.__subclasses__():
            if first in subclass.names:
                cls = subclass
        self = super().__new__(cls)
        self.first, self.last = names
        return self

class Woman(Person):
    gender = 'female'
    names = {*'Mary Linda Susan'.split()}

class Man(Person):
    gender = 'male'
    names = {*'Tom Dick Harry'.split()}
0

精彩评论

暂无评论...
验证码 换一张
取 消