2016-12-18 27 views
0

最近我試過玩過多個類繼承(在Python 3中),這是我以前從未做過的(從來沒有真正用過它)。python:用於多重繼承的更好的基礎'對象'

我很驚訝它有多「壞」。它不像我期待的那樣行事。我瞭解MRO和訂單解決方案,但除非我錯過了一些東西,否則設計不允許在鏈條末尾提供額外的參數。

讓我舉一個簡單的例子:

class BaseA(object): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 
     if kwargs.pop('is_cool', False): 
      self.a = True 
     else: 
      self.a = False 


class FixedBaseA(object): 
    def __init__(self, *args, **kwargs): 
     if kwargs.pop('is_cool', False): 
      self.a = True 
     else: 
      self.a = False 
     # if you move super to the top (see BaseA), MyThing instances will raise an error 
     # Note: instead of having super down here, one could have explicitly extracted 
     # the keyword 'is_cool' in the definition of __init__ method 
     # --> __init__(self, *args, is_cool=False, **kwargs) 
     super().__init__(*args, **kwargs) 


class BaseB(object): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 
     if kwargs.pop('is_cool', False): 
      self.b = True 
     else: 
      self.b = False 


class MyThing(BaseA, BaseB): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 


class FixedMyThing(FixedBaseA, BaseB): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 


class FixedMyThing2(BaseB, FixedBaseA): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 


class MyThing2(BaseB, BaseA): 
    def __init__(self, *args, **kwargs): 
     super().__init__(*args, **kwargs) 

正如你可能猜到了,我希望的行爲是:

  • 不管我給出BaseA和BaseB以什麼順序繼承的類,我會最終與屬性ab正確設置,並沒有錯誤
  • 給予額外的參數不會引起錯誤

當然情況並非如此。下面是一些測試:

def test(cls, *args, **kwargs): 
    try: 
     x = cls(*args, **kwargs) 
     print('{:} --> a, b = {:}, {:}'.format(cls.__name__, x.a, x.b)) 
    except TypeError as err: 
     print('[ERR] {:} raised a TypeError: {:}'.format(cls.__name__, err)) 

test(MyThing, is_cool=True) 
test(FixedMyThing, is_cool=True) 
test(MyThing2, is_cool=True) 
test(FixedMyThing2, is_cool=True) 
test(FixedMyThing2, z=3.4) 

這給:

[ERR] MyThing raised a TypeError: object.__init__() takes no parameters 
FixedMyThing --> a, b = True, False 
[ERR] MyThing2 raised a TypeError: object.__init__() takes no parameters 
FixedMyThing2 --> a, b = True, True 
[ERR] FixedMyThing2 raised a TypeError: object.__init__() takes no parameters 

現在我的問題不是關於爲什麼發生這種情況,有可能在網絡上找到解釋。儘管我無法判斷這些原因,但我想知道爲什麼object類型/類的行爲不同於接受任何額外參數。

所以真正的問題是:由以下類代替object一個壞主意?如果是的話爲什麼?

class BaseObject(object): 
    def __init__(self, *args, **kwargs): 
     try: 
      # this is necessary to pass along parameters for next-in-line inherited classes 
      super().__init__(*args, **kwargs) 
     except TypeError as err: 
      if err.__str__().startswith('object.__init__() takes no parameters'): 
       pass 
      else: 
       raise 

代替object使用這個類無處不在允許交換的邏輯上可以互換(即他們沒有在他們的屬性/方法重疊)班順序,並允許無任何使用額外的參數麻煩,但仍然會在任何時候出現錯誤。

MyThing --> a, b = True, True 
FixedMyThing --> a, b = True, False 
MyThing2 --> a, b = True, True 
FixedMyThing2 --> a, b = True, True 
FixedMyThing2 --> a, b = False, False 

對此的任何想法都不錯。

回答

0

首先,在Python 3中,您不需要從object繼承。那只是爲了Python 2;它現在已經隱含地完成了。


I'm wondering why the object type/class is not behaving differently by accepting any extras parameters.

我不知道你的意思在這裏。

  • 如果你是在問爲什麼object不帶任何參數,那是因爲它對它們沒有用處。提出錯誤永遠比默默地忽略意外情況要好。 (「錯誤不應該默默通過,除非明確聲明爲無效」 - Python的禪宗)
  • 如果你問爲什麼你的類接受額外的參數:這是因爲你明確地允許它們使用*args, **kwargs

So the real question: is replacing object by the following class a bad idea? And if yes why?

是的,因爲你是默默忽略錯誤。


,一般情況下不建議使用*args, **kwargs,除非你使用了大量的參數,或者你正在寫一箇中間件庫(即使用它來你使用一個庫從庫中傳遞參數)。

相反,你應該重寫BaseA這樣的:

class BaseA(object): 
    def __init__(self, is_cool=False): 
     super().__init__() 
     if is_cool: 
      self.a = True 
     else: 
      self.a = False 

這同樣適用於你的其他類如此。


現在,越來越偏離主題的言論:

可以進一步清理BaseA,因爲你使用Python 3:

class BaseA(object): 
    def __init__(self, is_cool=False): 
     if is_cool: 
      self.a = True 
     else: 
      self.a = False 

你甚至可以刪除不必要的脫節:

class BaseA(object): 
    def __init__(self, is_cool=False): 
     self.a = is_cool 
+0

感謝您的回答。是的,這些例子當然可以更乾淨,我只是寫得很快。我同意不應該默默地忽略錯誤,但在'object'的情況下,我沒有看到提出錯誤的觀點:它意味着在到達'object'之前必須消耗所有參數。儘管不推薦使用'*參數,** kwargs',它是完全有效的,並且對於您所提到的靈活性和中間件非常有用。但'物體'或多或少地阻止了它們的使用。它認爲這應該留給編碼人員,並且這將與Python的其他部分更加一致(例如,函數) – mhavel