2010-02-03 56 views
3

我想保留一個基類的(所有,非直接包含的)子類的字典,以便我可以從一個字符串實例化它們。我這樣做是因爲CLSID是通過Web表單發送的,所以我想限制從子類設置的選項。 (我不想eval()/globals()的類名)。如何從類體中獲取對當前類的引用?

class BaseClass(object): 
    CLSID = 'base' 
    CLASSES = {} 

    def from_string(str): 
     return CLASSES[str]() 

class Foo(BaseClass): 
    CLSID = 'foo' 
    BaseClass.CLASSES[CLSID] = Foo 

class Bar(BaseClass): 
    CLSID = 'bar' 
    BaseClass.CLASSES[CLSID] = Bar 

這顯然不工作。但是init有沒有像@classmethod這樣的東西?我們的想法是,這個classmethod只會在讀取每個類時運行一次,並向基類註冊該類。然後像下面的內容可以工作:(也可以節省額外的線FooBar

class BaseClass(object): 
    CLSID = 'base' 
    CLASSES = {} 

    @classmethod 
    def __init__(cls): 
     BaseClass.CLASSES[cls.CLSID] = cls 

    def from_string(str): 
     return CLASSES[str]() 

我想過使用__subclasses__,然後CLSIDfilter(),但只適用於直接的子類。

因此,希望我解釋我的目的,問題是如何使這項工作?還是我以完全錯誤的方式去解決這個問題?

+3

通常我們爲此而不是超類使用單獨的工廠。爲什麼不使用更常見的** Factory **設計模式? – 2010-02-03 11:31:33

+0

我認爲將工廠方法放在超類中會更清潔。另外,我不想爲classes/id選項進行硬編碼,我希望任何子類都是自包含的,這樣基類和工廠方法可以保持一個黑盒子。 – noio 2010-02-03 11:55:00

+0

把工廠放在超類中遠非乾淨 - 正如你的問題所揭示的那樣。 「硬編碼的選項?」沒有意義。工廠和超級工廠一樣「黑匣子」,所以我不明白這一點。 – 2010-02-03 12:14:28

回答

6

不可撤銷地與基類搭售這樣的:

class AutoRegister(type): 
    def __new__(mcs, name, bases, D): 
    self = type.__new__(mcs, name, bases, D) 
    if "ID" in D: # only register if has ID attribute directly 
     if self.ID in self._by_id: 
     raise ValueError("duplicate ID: %r" % self.ID) 
     self._by_id[self.ID] = self 
    return self 

class Base(object): 
    __metaclass__ = AutoRegister 
    _by_id = {} 
    ID = "base" 

    @classmethod 
    def from_id(cls, id): 
    return cls._by_id[id]() 

class A(Base): 
    ID = "A" 

class B(Base): 
    ID = "B" 

print Base.from_id("A") 
print Base.from_id("B") 

或者保持不同的關注其實是分開的:

class IDFactory(object): 
    def __init__(self): 
    self._by_id = {} 
    def register(self, cls): 
    self._by_id[cls.ID] = cls 
    return cls 

    def __call__(self, id, *args, **kwds): 
    return self._by_id[id](*args, **kwds) 
    # could use a from_id function instead, as above 

factory = IDFactory() 

@factory.register 
class Base(object): 
    ID = "base" 

@factory.register 
class A(Base): 
    ID = "A" 

@factory.register 
class B(Base): 
    ID = "B" 

print factory("A") 
print factory("B") 

你可能已經對我更喜歡哪一個回升。從類層次結構單獨定義,你可以很容易地擴展和修改,如通過在兩個名字註冊(使用ID屬性只允許一個):

class IDFactory(object): 
    def __init__(self): 
    self._by_id = {} 

    def register(self, cls): 
    self._by_id[cls.ID] = cls 
    return cls 

    def register_as(self, name): 
    def wrapper(cls): 
     self._by_id[name] = cls 
     return cls 
    return wrapper 

    # ... 

@factory.register_as("A") # doesn't require ID anymore 
@factory.register   # can still use ID, even mix and match 
@factory.register_as("B") # imagine we got rid of B, 
class A(object):   # and A fulfills that roll now 
    ID = "A" 

您也可以保持工廠實例基地「內」同時保持它的解耦:

class IDFactory(object): 
    #... 

class Base(object): 
    factory = IDFactory() 

    @classmethod 
    def register(cls, subclass): 
    if subclass.ID in cls.factory: 
     raise ValueError("duplicate ID: %r" % subclass.ID) 
    cls.factory[subclass.ID] = subclass 
    return subclass 

@Base.factory.register # still completely decoupled 
         # (it's an attribute of Base, but that can be easily 
         # changed without modifying the class A below) 
@Base.register # alternatively more coupled, but possibly desired 
class A(Base): 
    ID = "A" 
+0

謝謝!裝飾者的解決方案看起來非常有吸引力。我現在看到爲什麼第一個選項是一個壞主意:)。儘管如此,我仍然可以在工廠中包含/連接工廠,因爲其他「類」組(從另一個基類繼承)具有不同的安全限制。 – noio 2010-02-03 12:35:47

2

你可以使用元類淤泥爲你做的工作,但我認爲一個簡單的解決方案可能就足夠了:

class BaseClass(object): 
    CLASS_ID = None 
    _CLASSES = {} 

    @classmethod 
    def create_from_id(cls, class_id): 
     return CLASSES[class_id]() 

    @classmethod 
    def register(cls): 
     assert cls.CLASS_ID is not None, "subclass %s must define a CLASS_ID" % cls 
     cls._CLASSES[cls.CLASS_ID] = cls 

然後定義一個子類,只需使用:

class Foo(BaseClass): 
    CLASS_ID = 'foo' 

Foo.register() 

最後使用BaseClass中的工廠方法爲您創建實例:

foo = BaseClass.create_from_id('foo') 

在此解決方案,在類定義之後,您必須調用register類的方法來將子類註冊到基類中。另外,如果用戶忘記定義它,則默認CLASS_ID爲無,以避免覆蓋註冊表中的基類。

+0

+1建議我不要用元類錯誤 – noio 2010-02-03 12:38:33

相關問題