この記事 の既存のクラスコレクションにクラスを追加することにより、いくつかのPythonコードの継承階層を動的に変更する__bases__
の使用法を示すスニペットがあります。継承元のクラスわかりました、それは読みにくいです、コードはおそらくより明確です:
class Friendly:
def hello(self):
print 'Hello'
class Person: pass
p = Person()
Person.__bases__ = (Friendly,)
p.hello() # prints "Hello"
つまり、Person
はソースレベルでFriendly
を継承しませんが、この継承関係はPersonクラスの__bases__
attributeの変更により実行時に動的に追加されます。ただし、Friendly
とPerson
を(オブジェクトから継承して)新しいスタイルクラスに変更すると、次のエラーが発生します。
TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object'
これに関する少しのグーグルは、実行時の継承階層の変更に関して、新しいスタイルのクラスと古いスタイルのクラスの間で いくつかの非互換性を示す のようです。具体的には: 「新しいスタイルのクラスオブジェクトは、bases属性」への割り当てをサポートしていません 。
私の質問は、おそらく__mro__
属性を使用して、Python 2.7+)の新しいスタイルのクラスを使用して、上記のFriendly/Personサンプルを動作させることは可能ですか?
免責事項:これは不明瞭なコードであることを完全に認識しています。実際の製品コードでは、このようなトリックは読めないことに境界を置く傾向があることを完全に理解しています。これは純粋に思考実験であり、ファンジーがPythonが多重継承に関連する問題をどのように扱うかについて何かを学ぶために
繰り返しますが、これは通常行うべきことではなく、情報提供のみを目的としています。
Pythonインスタンスオブジェクトのメソッドを探すには、そのオブジェクトを定義するクラスの___mro__
_属性によって決定されます([〜# 〜] m [〜#〜]ethod[〜#〜] r [〜#〜]esolution[〜#〜] o [〜#〜]rder属性)。したがって、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
呼び出しの詳細が明確でない場合は、「Pythonのメタクラスとは何か」に関する e-satisの優れた答え をお読みください。
私もこれに苦労しており、あなたのソリューションに興味をそそられましたが、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'
私は結果を保証することはできませんが、このコードは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"
これが可能であることを知っています。クール。しかし、決して使用しません!
すぐに、クラス階層を動的にいじることのすべての警告が有効になります。
しかし、それを行う必要がある場合、明らかに、新しいスタイルクラスの"deallocator differs from 'object" issue when modifying the __bases__ attribute
を回避するハックがあります。
クラスオブジェクトを定義できます
class Object(object): pass
組み込みメタクラスtype
からクラスを派生します。これで、新しいスタイルクラスで__bases__
を問題なく変更できるようになりました。
私のテストでは、これは実際にすべての既存の(継承を変更する前の)インスタンスとその派生クラスが、mro
の更新を含む変更の影響を感じたので非常にうまく機能しました。
このためのソリューションが必要でした:
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()
上記の回答は、実行時に既存のクラスを変更する必要がある場合に適しています。ただし、他のクラスによって継承される新しいクラスを作成するだけの場合は、よりクリーンなソリューションがあります。 https://stackoverflow.com/a/21060094/353344 からこのアイデアを得ましたが、以下の例は正当なユースケースをよりよく示していると思います。
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のファンクターに非常に似ています。