2017-09-06 115 views
1

我想創建一個類,可以用** kwargs初始化並具有每個字段的默認值的系統。下面是一些代碼代表什麼,我試圖做的:Python類的繼承:動態子類初始化與自己和父母的默認值和初始化方法

Gen2item1 = Gen2() 
Gen2item2 = Gen2(p_var_1 = "different name", G2_var_1 = 666) 

Gen2item1將被初始化爲所有默認值:

    設置幾個實例後

    class Parent(object): #should only have p_var fields 
        DEFAULTS = { 
        "p_var_1" : "my name", 
        "p_var_2" : 2 
        } 
    
        def __init__(self, **kwargs): 
         for key in DEFAULTS: 
          #Also include check for list type and copy, not assign 
          if key in kwargs: 
           setattr(self, key, kwargs[key]) 
          else: 
           setattr(self, key, self.DEFAULTS[key]) 
         #Check for super being 'object' - problems? 
         #if not super() == object: 
         # super().__init__(**kwargs) 
    
    
    class Gen1(Parent): #should have p_var and G1_var fields 
        DEFAULTS = { 
        "G1_var_1" : "Generation 1", 
        "G1_var_2" : [] 
        } 
    
        #redefining only in case if Parent can't check for super 
        def __init__(self, **kwargs): 
         for key in DEFAULTS: 
          #Also include check for list type and copy, not assign 
          if key in kwargs: 
           setattr(self, key, kwargs[key]) 
          else: 
           setattr(self, key, self.DEFAULTS[key]) 
    
         #check whether super() is object 
         super().__init__(**kwargs) #pass all args to parent init 
    
    class Gen2(Gen1): #should have p_var, G1_var and G2_var fields 
        DEFAULTS = { 
        "G2_var_1" : 1337, 
        "G2_var_2" : (10,4) 
        } 
    
        #After Gen1, redefining __init__ shouldn't be necessary 
    

    使

  • Gen2item1.p_var_1是「我的名字」
  • Gen2item1.p_var_2是2
  • Gen2i tem1.G1_var_1爲 「第1代」
  • Gen2item1.G1_var_2是[](複製)
  • Gen2item1.G2_var_1是1337和
  • Gen2item1.G2_var_2是(10,4)

但Gen2item1會有不同的p_var_1和G2_var_1值,其餘部分相同。 然而,由於 '默認' 兒童類被覆蓋,則Gen2item1值:

  • Gen2item1.p_var_1是666
  • Gen2item1.p_var_2是(10,4)

被設置三種次,按每代(級),並且G1_varp_var值未設置。對於Gen2item2,所有三代__init__的G2_var_1設置爲666,G2_var_2設置爲(10,4)。


因此,底線的問題是關於一種方法,使一些「默認」字段(字典)每個類產生,因此:

  1. 在調用子類通過item = subclass()__init__,它會搶來自kwargs的參數取決於'默認'字典的鍵,
  2. 通過setattrib(self, key, value)設置其自己的字段,如果來自默認值的鍵存在,則從kwargs中取值,否則從默認值中取值,以及
  3. 在通過super()__init__(**kwargs)將kwargs傳遞給它的父輩的init時調用它。

父然後將其代的字典定義的子類的同一個實例相同的方式,默認值,調用並傳遞給自己的父母__init__,依此類推,直到父object,在這種情況下, super().__init__根本不會被調用/傳遞參數。
我已經閱讀了關於metaclasses,這似乎矯枉過正,和got my hopes high on this,它有一個單獨的父母和子類的實例。
有沒有一種方法來實現這樣的東西沒有元類?將默認值存儲在外部的全球字典中是否會更好?(在這種情況下,我們將讚賞一些有關尋址的幫助),還是有辦法建立一個工廠?


解決編輯:
__init_subclass__作品,差不多我想要做什麼,只要它也定義__init__的類被初始化(爲我的項目的具體細節)。這是我的問題的答案,並提供了一些有用的信息,以瞭解如何在我的項目中實現我想要的設置。我認爲定義一個classmethod就足夠了,它可以設置合適的__init__並在定義類時運行它(這是新的__init_subclass__似乎可以幫助的),但是至少有一些向後兼容性,它似乎元類是做到這一點的方法。

回答

0

您可以使用在Python 3.6添加到object()新類方法:__init_subclass__

當類的子類這種方法被稱爲,並允許您自定義子類的創作,而無需使用元類。

你的榜樣應該是這樣的:

class Base: 
    def __init_subclass__(cls, **kwargs): 
     super().__init_subclass__() 
     for key, value in kwargs.items(): 
      setattr(cls, key, value) 

    def __init__(self, *args, **kwargs): 
     super().__init__() 
     for key, value in kwargs.items(): 
      setattr(self, key, value)  

class Parent(Base, p_var_1='my_name', p_var_2=2): 
    pass 

class Gen1(Parent, G1_var_1='Generation 1', G1_var_2=[]): 
    pass 

class Gen2(Gen1, G2_var_1=1337, G2_var_2=(10,4)): 
    pass 
+0

這適用於設置默認值,但似乎我仍然必須定義'__init__'來更改創建實例時的值,因爲在執行類似'g1 = Gen1(G1_var_1 =「asdf之類的操作時,使用此代碼時會引發TypeError 「)' – DZHEX

+1

通過在'__init_subclass__'中設置'__init__'創建者函數,似乎我想要做的工作。經過一些測試,如果沒有問題出現,將標記爲答案。 – DZHEX

+0

是的,這個例子太骨架了。它只是爲了顯示'__init_subclass__'方法。你是對的:'__init_subclass__'被調用來創建子類,但如果你想在實例化中傳遞參數(位置或名字),則需要'__init__'。我更新了代碼。 –

0

我會跳過DEFAULTS作爲不必要的膨脹。如果您確實需要訪問這些參數的默認值,您可以通過內省__init__方法來獲取它們。否則,使用super的一般規則是剝離您的課程特有的參數,並將所有其他參數傳遞給__init__的下一個呼叫。

確保的object.__init__被調用之前,確保生成的MRO中的任何類都被移除,這是類設計者的責任。在下面的示例中,Parent.__init__將用實際參數調用object.__init__的唯一方法是如果self的MRO中某些其他類未能刪除其參數。

class Parent(object): 
    def __init__(self, p_var_1="my name", p_var_2=2, **kwargs): 
     super().__init__(**kwargs) 
     self.p_var_1 = p_var_1 
     self.p_var_2 = p_var_2 

class Gen1(Parent): #should have p_var and G1_var fields 
    def __init__(self, g1_var_1="Generation 1", g1_var_2=None, **kwargs): 
     super().__init__(**kwargs) 
     if g1_var_2 is None: 
      g1_var_2 = [] 
     self.g1_var_1 = g1_var_1 
     self.g1_var_2 = g1_var_2 

class Gen2(Gen1): #should have p_var, G1_var and G2_var fields 
    def __init__(self, g2_var_1=1337, g2_var_2=(10,4), **kwargs): 
     super().__init__(**kwargs) 
     self.g2_var_1 = g2_var_1 
     self.g2_var_2 = g2_var_2 

考慮像

Gen2(p_var_1=..., 
    p_var_2=..., 
    g1_var_1=..., 
    g1_var_2=..., 
    g2_var_1=..., 
    g2_var_2=...) 

調用的第一個方法是Gen2.__init__一個呼叫,這將通過僅pg1瓦爾到super.__init__。該呼叫導致致電Gen1.__init__,其僅傳遞p變量。當調用Parent.__init__時,**kwargs爲空。

+0

我也會,但是希望有一些防呆的設置,如測試變量的類型,並恢復爲默認值,如果該類型是不恰當的,或者忽略了一個意想不到的關鍵字參數,最好在爲每個類添加大量參數的情況下,最好不要創建大量的__init__。我現在試圖做的方式是通過爲每個類設置(外部)字典,一個用於默認值,另一個用於可接受類型的元組。 – DZHEX

+0

您不應該默默地替換錯誤類型的值;如果它無法使用,那應該被視爲錯誤。至於意想不到的關鍵字參數,你不能真正假設這只是因爲*你的類沒有期望有一個參數,它不應該傳遞給MRO中更遠的類。 – chepner