2012-03-02 85 views
55

This article具有顯示的__bases__使用以動態地改變的一些Python代碼繼承層次結構,通過添加類的從它繼承的類的現有的類集合的片段。好吧,這是難以閱讀,代碼可能更清楚:如何在運行時動態更改實例的基類?

class Friendly: 
    def hello(self): 
     print 'Hello' 

class Person: pass 

p = Person() 
Person.__bases__ = (Friendly,) 
p.hello() # prints "Hello" 

也就是說,Person沒有在源代碼級別從Friendly繼承,而是這種繼承關係在運行時動態由__bases__屬性的修改添加Person類。但是,如果你改變FriendlyPerson是新樣式類(由對象繼承),您會收到以下錯誤:

TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object' 

谷歌搜索關於這一點的一點似乎與新型老樣式類indicate some incompatibilities關於在運行時更改繼承層次結構。具體來說:"New-style class objects don't support assignment to their bases attribute"

我的問題是否有可能使用Python 2.7+中的新式類工作,可能通過使用__mro__屬性來使上述Friendly/Person示例工作?

聲明:我充分認識到,這是晦澀難懂的代碼。我充分認識到,在實際生產代碼的技巧這樣往往不可讀接壤,這純粹是一個思想實驗,併爲funzies瞭解如何用一些相關的多重繼承問題的Python交易。

+0

如果學習者不熟悉metaclass,type(),...:這對學習者來說也是很好的:http://www.slideshare.net/gwiener/metaclasses-in-python :) – loolooyyyy 2014-01-30 11:30:20

+0

這是我的用法案件。我正在導入一個類B繼承自A類的庫。 – FutureNerd 2014-06-25 02:39:47

+2

這是我的實際使用案例。我正在導入一個擁有從類A繼承的類B的庫。我想用new_A_method()創建從A繼承的New_A。現在我想創建New_B繼承自... B,就像B繼承自New_A一樣,以便B的方法,A的方法和new_A_method()都可用於New_B的實例。如何在不修補現有A類的情況下做到這一點? – FutureNerd 2014-06-25 02:49:46

回答

29

確定,再次,這是不是你通常應該做的事,這是僅供參考之用。

凡Python將實例對象的方法是由__mro__屬性的類的,其定義了對象(中號 ethod ř esolution ö刻申屬性)來確定。因此,如果我們可以修改的Person__mro__,我們會得到期望的行爲。喜歡的東西:

setattr(Person, '__mro__', (Person, Friendly, object)) 

的問題是,__mro__是一個只讀屬性,因而SETATTR將無法正常工作。也許,如果你是一個Python大師有周圍的一種方式,但很明顯,我達不到大師的地位,我不認爲一個人的。

可能的解決方法是簡單地重新定義類:

def modify_Person_to_be_friendly(): 
    # so that we're modifying the global identifier 'Person' 
    global Person 

    # now just redefine the class using type(), specifying that the new 
    # class should inherit from Friendly and have all attributes from 
    # our old Person class 
    Person = type('Person', (Friendly,), dict(Person.__dict__)) 

def main(): 
    modify_Person_to_be_friendly() 
    p = Person() 
    p.hello() # works! 

什麼,這並不做的就是修改任何先前創建的Person實例有hello()方法。例如(只是修改main()):

def main(): 
    oldperson = Person() 
    ModifyPersonToBeFriendly() 
    p = Person() 
    p.hello() 
    # works! But: 
    oldperson.hello() 
    # does not 

如果type通話的細節並不清楚,然後閱讀e-satis' excellent answer on 'What is a metaclass in Python?'

+0

-1:爲什麼強制新類只能從Frielndly繼承才能保存通過調用:'type('Person',(Friendly)+ Person .__ mro__,dict(Person .__ dict__)')來最初的'__mro__''(更好的是,加入保護措施以便友好不會在那裏結束兩次。)這裏還有其他一些問題 - 至於「Person」類實際定義和使用的位置:你的函數只改變它在當前模塊上 - 運行Person的其他模塊將不會被識別 - 你最好在模塊上執行monkeypatch Person is定義。 (甚至在那裏有問題) – jsbueno 2015-05-22 21:23:20

+6

'-1'你首先完全錯過了例外的原因。只要'Person'不直接從'object' **繼承**,你就可以在Python 2和3中愉快地修改'Person .__ class __.__ bases__'。請參閱下面的akaRem和Sam Gulve的答案。這種解決方法只能解決您對問題的誤解。 – 2015-05-26 23:29:47

7

我不能擔保後果,但是這段代碼在py2.7.2中做了你想要的。

class Friendly(object): 
    def hello(self): 
     print 'Hello' 

class Person(object): pass 

# we can't change the original classes, so we replace them 
class newFriendly: pass 
newFriendly.__dict__ = dict(Friendly.__dict__) 
Friendly = newFriendly 
class newPerson: pass 
newPerson.__dict__ = dict(Person.__dict__) 
Person = newPerson 

p = Person() 
Person.__bases__ = (Friendly,) 
p.hello() # prints "Hello" 

我們知道,這是可能的。涼。但我們永遠不會使用它!

13

我一直在爲此而努力過了,被你的解決方案很感興趣,但是Python 3需要它離我們:

AttributeError: attribute '__dict__' of 'type' objects is not writable 

其實我有一個裝飾正當理由需要替換的(單)裝飾類的超類。這需要太長的描述來包含在這裏(我嘗試過,但無法達到合理的長度和有限的複雜性 - 它出現在許多Python應用程序使用基於Python的企業服務器的環境中不同的應用程序需要稍微不同的一些代碼的變化)

本頁和其他人喜歡它的討論提供了分配給__bases__的問題只發生在沒有定義超類的類上(即,它的唯一超類是目的)。我能夠定義其超我需要更換一個微不足道的類是子類來解決這個問題(對於Python的2.7和3.2):

## T is used so that the other classes are not direct subclasses of object, 
## since classes whose base is object don't allow assignment to their __bases__ attribute. 

class T: pass 

class A(T): 
    def __init__(self): 
     print('Creating instance of {}'.format(self.__class__.__name__)) 

## ordinary inheritance 
class B(A): pass 

## dynamically specified inheritance 
class C(T): pass 

A()     # -> Creating instance of A 
B()     # -> Creating instance of B 
C.__bases__ = (A,) 
C()     # -> Creating instance of C 

## attempt at dynamically specified inheritance starting with a direct subclass 
## of object doesn't work 
class D: pass 

D.__bases__ = (A,) 
D() 

## Result is: 
##  TypeError: __bases__ assignment: 'A' deallocator differs from 'object' 
3

蝙蝠權,搞亂所有的注意事項類層次結構動態地生效。

但是,如果必須這樣做,顯然,對於新樣式類來說,在"deallocator differs from 'object" issue when modifying the __bases__ attribute附近有一個黑客攻擊。

可以定義一個類的對象

class Object(object): pass 

從派生的類內置元類type。 就是這樣,現在你的新風格類可以修改__bases__沒有任何問題。

在我的測試中,這實際上工作得非常好,因爲它及其派生類的所有現有(更改繼承之前)實例都感受到更改的效果,包括它們的mro得到更新。

1

我需要此其中一個解決方案:

  • 作品與兩個Python 2的(> = 2.7)和Python 3(> = 3.2)。
  • 讓我們在動態地導入一個依賴關係後改變類的基礎。
  • 讓我們從單元測試代碼改變類的基礎。
  • 適用於具有自定義元類的類型。
  • 仍然允許unittest.mock.patch按預期運行。

這就是我想出了:

def ensure_class_bases_begin_with(namespace, class_name, base_class): 
    """ Ensure the named class's bases start with the base class. 

     :param namespace: The namespace containing the class name. 
     :param class_name: The name of the class to alter. 
     :param base_class: The type to be the first base class for the 
      newly created type. 
     :return: ``None``. 

     Call this function after ensuring `base_class` is 
     available, before using the class named by `class_name`. 

     """ 
    existing_class = namespace[class_name] 
    assert isinstance(existing_class, type) 

    bases = list(existing_class.__bases__) 
    if base_class is bases[0]: 
     # Already bound to a type with the right bases. 
     return 
    bases.insert(0, base_class) 

    new_class_namespace = existing_class.__dict__.copy() 
    # Type creation will assign the correct ‘__dict__’ attribute. 
    del new_class_namespace['__dict__'] 

    metaclass = existing_class.__metaclass__ 
    new_class = metaclass(class_name, tuple(bases), new_class_namespace) 

    namespace[class_name] = new_class 

像這樣來使用該應用程序中:

# foo.py 

# Type `Bar` is not available at first, so can't inherit from it yet. 
class Foo(object): 
    __metaclass__ = type 

    def __init__(self): 
     self.frob = "spam" 

    def __unicode__(self): return "Foo" 

# … later … 
import bar 
ensure_class_bases_begin_with(
     namespace=globals(), 
     class_name=str('Foo'), # `str` type differs on Python 2 vs. 3. 
     base_class=bar.Bar) 

使用這樣從內部單元測試代碼:

# test_foo.py 

""" Unit test for `foo` module. """ 

import unittest 
import mock 

import foo 
import bar 

ensure_class_bases_begin_with(
     namespace=foo.__dict__, 
     class_name=str('Foo'), # `str` type differs on Python 2 vs. 3. 
     base_class=bar.Bar) 


class Foo_TestCase(unittest.TestCase): 
    """ Test cases for `Foo` class. """ 

    def setUp(self): 
     patcher_unicode = mock.patch.object(
       foo.Foo, '__unicode__') 
     patcher_unicode.start() 
     self.addCleanup(patcher_unicode.stop) 

     self.test_instance = foo.Foo() 

     patcher_frob = mock.patch.object(
       self.test_instance, 'frob') 
     patcher_frob.start() 
     self.addCleanup(patcher_frob.stop) 

    def test_instantiate(self): 
     """ Should create an instance of `Foo`. """ 
     instance = foo.Foo() 
0

如果您需要在運行時更改現有類,上述答案很好。但是,如果您只是想創建一個由其他類繼承的新類,則會有更簡潔的解決方案。我從https://stackoverflow.com/a/21060094/3533440得到了這個想法,但我認爲下面的例子更好地說明了一個合理的用例。

def make_default(Map, default_default=None): 
    """Returns a class which behaves identically to the given 
    Map class, except it gives a default value for unknown keys.""" 
    class DefaultMap(Map): 
     def __init__(self, default=default_default, **kwargs): 
      self._default = default 
      super().__init__(**kwargs) 

     def __missing__(self, key): 
      return self._default 

    return DefaultMap 

DefaultDict = make_default(dict, default_default='wug') 

d = DefaultDict(a=1, b=2) 
assert d['a'] is 1 
assert d['b'] is 2 
assert d['c'] is 'wug' 

糾正我,如果我錯了,但這種策略似乎非常可讀的給我,我會在生產代碼中使用它。這與OCaml中的仿函數非常相似。

+1

不太確定這與問題有關,因爲問題實際上是關於在運行時動態更改基類的問題。無論如何,直接從'''Map''直接繼承並根據需要重寫方法的優點是什麼(很像標準'''collections.defaultdict'的操作)?正如它所表明的,make_default只能返回一種類型的東西,所以爲什麼不直接使用''DefaultMap'''作爲頂級標識符而不是調用'''make_default'''來獲取類來實例化? – 2016-01-05 16:18:29

+0

感謝您的反饋! (1)我在嘗試做動態繼承時登陸這裏,所以我想我可以幫助那些遵循同樣道路的人。 (2)我相信可以使用'make_default'來創建一些其他類型的字典類('Map')的默認版本。你不能直接從'Map'繼承,因爲'Map'直到運行時才被定義。在這種情況下,你可能想直接從'dict'繼承,但是我們可以想象,可能會出現這樣的情況:在運行之前,您不知道類似於字典的類是如何繼承的。 – fredcallaway 2016-01-05 18:07:21

相關問題