在Python中超越工厂

时间:2010-08-26 03:04:52

标签: java python

从Java进入Python,我被告知工厂不是Pythonic。因此,我正在寻找 a Python方式来执行以下操作。 (我过分简化了我的目标,所以我不必描述我的整个程序,这非常复杂。)

我的脚本将以人名(以及有关它们的一些信息)读取,并从中构建Person类型的对象。名称可能会重复,我只需要每个名称一个Person实例。这些人也可能属于男人和女人的子类。

执行此操作的一种方法是创建一个PersonFactory,它将返回一个新实例化的Man或Woman 对以前实例化的具有相同名称的Man / Woman的引用。另一种方法是创建一组所有Person对象,并在实例化一个新对象之前每次检查是否存在具有给定名称的Person。然而,这两种方法都没有像Pythonic那样让我感到震惊。对于Python来说,第一个似乎有点过于繁琐(创建一个完整的类只是为了处理另一个对象的创建?真的吗?)而第二个会很快变得昂贵,因为我有很多要处理的名称。

6 个答案:

答案 0 :(得分:12)

我不认为工厂不是Pythonic。但是,你不需要全班。 Java和Python之间的一个重要区别是,在Python中,您可以在类之外使用代码。所以你可能想要创建一个工厂函数。或者您可以将工厂设置为Person类的类方法:

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...

通常,相同的模式在Python代码中与在Java中一样有效,但我们并没有做出如此大的处理。在Java中,你有一个全新的类,它意味着一个全新的.java文件,你需要使它成为单例等等.Java似乎会产生这种复杂性。一个简单的类方法就可以了,所以只需使用它。

答案 1 :(得分:2)

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]

由于Python允许你做一些事情,比如在dict中调用存储类类型,你不需要工厂;你可以只查找一个类型并实例化它。

答案 2 :(得分:2)

一个独立的函数def PersonFactory(name, gender):很好,虽然将其打包为类方法,正如@Ned建议的那样,不应该受到伤害(在这种特殊情况下它也没有多大帮助,因为确切的类实例化的人必须变化)。我认为最干净的实现实际上是一个独立的函数,只是因为我更喜欢使用classmethod来返回它所调用的类的实例(而不是其他类的实例) - 但这是一个不能成为的风格点。据说两种方式都有明确的定义。

我会对其进行编码(我希望这些假设很明确,例如,性别编码为MF,如果未指定,则从名称推断,& 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

答案 3 :(得分:2)

将“没有两个具有相同密钥的对象”注册的地方放在__new__中,如下所示:

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

打印:

Man M
True

我也抨击了你的男人/女人的区别,但我对此并不感到兴奋。

答案 4 :(得分:2)

答案 5 :(得分:0)

最简单的方法可能是在__new__上使用来自functools的内置lru_cache。

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()}