2010-08-26 45 views
7

來自Java的Python,我被告知工廠不是Pythonic。因此,我正在尋找 a Python方式來做類似下面的事情。 (我簡化了我的目標,因此我不必描述我的整個程序,這非常複雜)。在Python中移動工廠

我的腳本會讀取人的姓名(以及關於他們的一些信息),並由此構建Person類型的對象。這些名字可能會重複出現,而我只想爲每個名稱指定一個Person實例。這些人也可能屬於男人和女人的分類。

這樣做的一種方法是創建一個PersonFactory,它將返回一個新實例化的男人或女人對先前實例化的同名男人/女人的引用。另一種方法是創建一組所有Person對象,並在實例化一個新對象之前每次檢查具有給定名稱的Person的存在。雖然這兩種方法都沒有讓我感覺到Pythonic。對Python來說,第一個看起來有點麻煩(創建一個完整的類只是爲了處理另一個對象的創建?真的嗎?),第二個會很快變得昂貴,因爲我有很多名字需要處理。

+0

檢查字典中是否存在密鑰並不是很貴,它是O(1)。 – Amber 2010-08-26 03:18:35

+0

對,我忽略了這一點。謝謝,琥珀! – chimeracoder 2010-08-26 03:49:43

+3

當我在這種情況下看到術語「pythonic」時,我會畏縮。 Python只是一個工具。使用該工具以您知道如何解決問題的最佳方式解決問題。如果那涉及到工廠,就這樣吧。你的客戶不關心你是否「pythonic」。 – 2010-08-26 11:41:57

回答

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似乎滋生了這種複雜性。一個簡單的類方法可以做到,所以就用它吧。

+0

我認爲你的意思是'class method';) – aaronasterling 2010-08-26 03:15:41

+0

oops,是的,固定的。 – 2010-08-26 03:16:38

+2

你的代碼依然不適用於子類化 - 你應該使用'= cls(name)'代替。 – Amber 2010-08-26 03:21:00

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允許您在字典中調用存儲類類型,因此您不需要工廠;你可以查找一個類型並實例化它。

+0

這會每次調用構造函數,然後拋棄大部分結果 - 完全浪費資源('setdefault'常常鼓勵這種肆意浪費)。 – 2010-08-26 04:39:58

+0

確實,alex;如果setdefault可以傳遞一個可調用對象,並且只在未設置該值時才評估它,那將會很好。 – Amber 2010-08-26 05:56:05

+0

改爲使用collections.defaultdict。這就是你所要求的 - 只有在密鑰丟失的情況下才會被評估的函數。 – 2010-08-26 07:44:01

2

一個獨立功能def PersonFactory(name, gender):沒問題,儘管按照@Ned的說法,把它打包成一個classmethod,應該不會受到傷害(在這種特殊情況下,它也不會有太大的幫助,因爲人的確切類別實例化必須改變)。我認爲最簡潔的實現實際上是作爲一個獨立函數,因爲我更喜歡一個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 
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 

我也刺傷了你的男人/女人的區別,但我並不滿意。

+1

請注意,即使'__new__'返回一個預先存在的對象,它也會再次運行'__init__'。在這種情況下,它沒有任何傷害,但很容易。一般來說,我發現使用'__new__' *或*'__init__'最清潔,而不是兩者。 – Ben 2012-01-12 09:04:45

+0

事實上,當你調用'Woman(* args)'時,會導致在對象上調用__init__',那麼你從'__new__'返回它,'__init__'將再次被調用*。一般來說,在同一個類層次結構中同時使用__new__和__init__是非常不直觀的。通過重寫* metaclass中的'__call__'而不是使用'__new__',我發現這樣做更容易。 – Ben 2012-01-12 09:18:06

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