从Java进入Python,我被告知工厂不是Pythonic。因此,我正在寻找 a Python方式来执行以下操作。 (我过分简化了我的目标,所以我不必描述我的整个程序,这非常复杂。)
我的脚本将以人名(以及有关它们的一些信息)读取,并从中构建Person类型的对象。名称可能会重复,我只需要每个名称一个Person实例。这些人也可能属于男人和女人的子类。
执行此操作的一种方法是创建一个PersonFactory,它将返回一个新实例化的Man或Woman 或对以前实例化的具有相同名称的Man / Woman的引用。另一种方法是创建一组所有Person对象,并在实例化一个新对象之前每次检查是否存在具有给定名称的Person。然而,这两种方法都没有像Pythonic那样让我感到震惊。对于Python来说,第一个似乎有点过于繁琐(创建一个完整的类只是为了处理另一个对象的创建?真的吗?)而第二个会很快变得昂贵,因为我有很多要处理的名称。
答案 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来返回它所调用的类的实例(而不是其他类的实例) - 但这是一个不能成为的风格点。据说两种方式都有明确的定义。
我会对其进行编码(我希望这些假设很明确,例如,性别编码为M
或F
,如果未指定,则从名称推断,& 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()}