回答:
http://www.geekinterview.com/question_details/64739からの引用:
内部クラスの利点:
- クラスの論理的なグループ化:クラスが他の1つのクラスだけに役立つ場合、そのクラスに埋め込み、2つをまとめることが論理的です。このような「ヘルパークラス」をネストすると、パッケージがより効率的になります。
- カプセル化の増加:2つのトップレベルのクラスAとBを考えます。Bは、そうでなければプライベートとして宣言されるAのメンバーにアクセスする必要があります。クラスAのクラスBを非表示にすることで、メンバーをプライベートに宣言し、Bがそれらにアクセスできます。さらに、B自体を外部の世界から隠すことができます。
- より読みやすく、保守可能なコード:最上位クラス内に小さなクラスをネストすると、コードが使用される場所にコードが近くなります。
主な利点は組織です。内部クラスで達成できることは、それらがなくても達成できます。
DataLoader
、CacheMiss
例外をスローする可能性のあるクラスについて考えます。例外をメインクラスの下にネストすると、例外DataLoader.CacheMiss
をインポートしDataLoader
ながら、例外を使用できます。
それらなしでは成し遂げられないものはありますか?
いいえ。これらは通常、最上位でクラスを定義し、それへの参照を外部クラスにコピーすることと完全に同等です。
ネストされたクラスが「許可」されている特別な理由はないと思いますが、明示的に「許可しない」ことは特に意味がありません。
外部/所有者オブジェクトのライフサイクル内に存在し、常に外部クラスのインスタンスへの参照(Javaと同様に内部クラス)を持っているクラスを探している場合、Pythonのネストされたクラスはそうではありません。しかし、あなたはそのようなものをハックすることができます:
import weakref, new
class innerclass(object):
"""Descriptor for making inner classes.
Adds a property 'owner' to the inner class, pointing to the outer
owner instance.
"""
# Use a weakref dict to memoise previous results so that
# instance.Inner() always returns the same inner classobj.
#
def __init__(self, inner):
self.inner= inner
self.instances= weakref.WeakKeyDictionary()
# Not thread-safe - consider adding a lock.
#
def __get__(self, instance, _):
if instance is None:
return self.inner
if instance not in self.instances:
self.instances[instance]= new.classobj(
self.inner.__name__, (self.inner,), {'owner': instance}
)
return self.instances[instance]
# Using an inner class
#
class Outer(object):
@innerclass
class Inner(object):
def __repr__(self):
return '<%s.%s inner object of %r>' % (
self.owner.__class__.__name__,
self.__class__.__name__,
self.owner
)
>>> o1= Outer()
>>> o2= Outer()
>>> i1= o1.Inner()
>>> i1
<Outer.Inner inner object of <__main__.Outer object at 0x7fb2cd62de90>>
>>> isinstance(i1, Outer.Inner)
True
>>> isinstance(i1, o1.Inner)
True
>>> isinstance(i1, o2.Inner)
False
(これは、Python 2.6および3.0の新機能であるクラスデコレーターを使用します。それ以外の場合は、クラス定義の後に「Inner = innerclass(Inner)」と言う必要があります。)
self
余分な作業を必要としないアウター(通常はインナーのself
ように別の識別子を使用するなどinnerself
)を使用し、それを介してアウターインスタンスにアクセスできます。
WeakKeyDictionary
この例でa を使用しても、実際にはキーがガベージコレクションされることはありません。これは、値がowner
属性を通じてそれぞれのキーを強く参照しているためです。
これを理解するために頭を回さなければならないことがあります。ほとんどの言語では、クラス定義はコンパイラーへのディレクティブです。つまり、プログラムが実行される前にクラスが作成されます。Pythonでは、すべてのステートメントが実行可能です。つまり、このステートメントは:
class foo(object):
pass
次のように、実行時に実行されるステートメントです。
x = y + z
つまり、他のクラス内にクラスを作成できるだけでなく、どこにでもクラスを作成できます。このコードを考えてみましょう:
def foo():
class bar(object):
...
z = bar()
したがって、「内部クラス」の概念は、実際には言語構造ではありません。それはプログラマーの構造です。グイドは、これがここでどのようにして生じたのかについて非常に良い要約を持っています。しかし、基本的に、基本的な考え方は、これにより言語の文法が簡素化されるということです。
クラス内のクラスのネスト:
ネストされたクラスはクラス定義を膨らませるので、何が起こっているのかわかりにくくなります。
ネストされたクラスは、テストをより困難にするカップリングを作成する可能性があります。
Pythonでは、Javaとは異なり、ファイル/モジュールに複数のクラスを配置できます。そのため、クラスは依然としてトップレベルのクラスに近いままであり、クラス名の前に「_」を付けて、他のクラスがそれを使用して。
ネストされたクラスが役立つことがわかる場所は関数内です
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
クラスは関数から値をキャプチャし、C ++でテンプレートメタプログラミングのようなクラスを動的に作成できるようにします
ネストされたクラスに対する引数は理解していますが、場合によってはそれらを使用する場合があります。二重リンクリストクラスを作成していて、ノードを維持するためのノードクラスを作成する必要があるとします。DoublyLinkedListクラス内にNodeクラスを作成するか、DoublyLinkedListクラス外にNodeクラスを作成するかの2つの選択肢があります。NodeクラスはDoublyLinkedListクラス内でのみ意味があるので、この場合は最初の選択肢を選びます。非表示/カプセル化の利点はありませんが、NodeクラスがDoublyLinkedListクラスの一部であると言えるというグループ化の利点があります。
Node
クラスが、作成する可能性のある他のタイプのリンクリストクラスには役に立たないと想定している場合です。
Node
、の名前空間の下にありDoublyLinkedList
、そうすることは論理的に理にかなっています。これは Pythonicです。「名前空間は非常に重要なアイデアの1つです。もっと多くのことをしましょう!」
それらなしでは成し遂げられないものはありますか?もしそうなら、それは何ですか?
何かがあります簡単にせずに実行することができません:関連クラスの継承は。
以下は、関連するクラスA
とミニマリストの例B
です。
class A(object):
class B(object):
def __init__(self, parent):
self.parent = parent
def make_B(self):
return self.B(self)
class AA(A): # Inheritance
class B(A.B): # Inheritance, same class name
pass
このコードは、非常に合理的で予測可能な動作をもたらします。
>>> type(A().make_B())
<class '__main__.A.B'>
>>> type(A().make_B().parent)
<class '__main__.A'>
>>> type(AA().make_B())
<class '__main__.AA.B'>
>>> type(AA().make_B().parent)
<class '__main__.AA'>
場合はB
、トップレベルのクラスだった、あなたは書くことができなかったself.B()
方法ではmake_B
なく、単に書きますB()
ので、失う動的結合に適切なクラスにします。
この構造では、クラスA
の本体でクラスを参照してはならないことに注意してくださいB
。これが、parent
クラスに属性を導入する動機ですB
。
もちろん、この動的バインディングは、クラスの面倒でエラーが発生しやすいインストルメンテーションを犠牲にして、内部クラスなしで再作成できます。
私がこれを使用する主なユースケースは、小さなモジュールの急増の防止と、個別のモジュールが不要な場合の名前空間の汚染の防止です。既存のクラスを拡張しているが、その既存のクラスは、常にそれに結合されている別のサブクラスを参照する必要がある場合。たとえば、utils.py
モジュールに多くのヘルパークラスが含まれていて、必ずしも一緒に結合されていない場合がありますが、一部のヘルパークラスの結合を強化したいと考えています。たとえば、https://stackoverflow.com/a/8274307/2718295を実装すると
:utils.py
:
import json, decimal
class Helper1(object):
pass
class Helper2(object):
pass
# Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats
class DecimalJSONEncoder(json.JSONEncoder):
class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched
def __init__(self, obj):
self._obj = obj
def __repr__(self):
return '{:f}'.format(self._obj)
def default(self, obj): # override JSONEncoder.default
if isinstance(obj, decimal.Decimal):
return self._repr_decimal(obj)
# else
super(self.__class__, self).default(obj)
# could also have inherited from object and used return json.JSONEncoder.default(self, obj)
その後、次のことができます。
>>> from utils import DecimalJSONEncoder
>>> import json, decimal
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234'),
... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder)
{"key2": "key2_value", "key_1": 1.12345678901234}
もちろん、json.JSONEnocder
完全に継承を避け、default()をオーバーライドすることもできます。
:
import decimal, json
class Helper1(object):
pass
def json_encoder_decimal(obj):
class _repr_decimal(float):
...
if isinstance(obj, decimal.Decimal):
return _repr_decimal(obj)
return json.JSONEncoder(obj)
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder)
'{"key1": 1.12345678901234}'
しかし、場合utils
によっては、単なる慣例として、拡張性のためにクラスで構成する必要があります。
次に、別のユースケースを示します。OuterClass内のミュータブルのファクトリーを呼び出さずに必要copy
です。
class OuterClass(object):
class DTemplate(dict):
def __init__(self):
self.update({'key1': [1,2,3],
'key2': {'subkey': [4,5,6]})
def __init__(self):
self.outerclass_dict = {
'outerkey1': self.DTemplate(),
'outerkey2': self.DTemplate()}
obj = OuterClass()
obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4)
assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6]
このパターン@staticmethod
は、ファクトリ関数に使用するデコレータよりも好みます。
前に示した2つの方法は機能的に同じです。ただし、微妙な違いがあり、どちらかを選択したい場合があります。
方法1:ネストされたクラスの定義
(= "ネストされたクラス")
class MyOuter1:
class Inner:
def show(self, msg):
print(msg)
方法2:モジュールレベルの内部クラスが外部クラスにアタッチされている場合
(= "参照された内部クラス")
class _InnerClass:
def show(self, msg):
print(msg)
class MyOuter2:
Inner = _InnerClass
アンダースコアは、PEP8に準拠するために使用されます。「内部インターフェイス(パッケージ、モジュール、クラス、関数、属性、またはその他の名前)の前に単一のアンダースコアを付ける必要があります。」
以下のコードスニペットは、「ネストされたクラス」と「参照された内部クラス」の機能の類似点を示しています。これらは、内部クラスインスタンスの型をコードチェックする場合と同じように動作します。言うまでもなく、はおよびとm.inner.anymethod()
同様に動作しますm1
m2
m1 = MyOuter1()
m2 = MyOuter2()
innercls1 = getattr(m1, 'Inner', None)
innercls2 = getattr(m2, 'Inner', None)
isinstance(innercls1(), MyOuter1.Inner)
# True
isinstance(innercls2(), MyOuter2.Inner)
# True
type(innercls1()) == mypackage.outer1.MyOuter1.Inner
# True (when part of mypackage)
type(innercls2()) == mypackage.outer2.MyOuter2.Inner
# True (when part of mypackage)
「ネストされたクラス」と「参照された内部クラス」の違いを以下に示します。それらは大きくありませんが、これらに基づいてどちらか一方を選択したい場合があります。
「ネストされたクラス」を使用すると、「参照される内部クラス」を使用するよりもコードをカプセル化できます。モジュール名前空間のクラスはグローバル変数です。ネストされたクラスの目的は、モジュール内の混乱を減らし、内部クラスを外部クラス内に配置することです。
no-one *がを使用from packagename import *
している場合、たとえば、コード補完/インテリセンスを備えたIDEを使用する場合は、モジュールレベルの変数の量が少ないと便利です。
* そうですか?
Djangoのドキュメントでは、モデルのメタデータに内部クラスMetaを使用するように指示しています。フレームワークのユーザーにclass Foo(models.Model)
with inner を書くように指示する方が少し明確です* class Meta
。
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
代わりに、「書き込みのclass _Meta
書き込み、その後、class Foo(models.Model)
とMeta = _Meta
」。
class _Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
class Ox(models.Model):
Meta = _Meta
horn_length = models.IntegerField()
「ネストされたクラス」アプローチを使用すると、コードはネストされた箇条書きリストを読み取ることができますが、「参照された内部クラス」メソッドを使用すると、上にスクロールして、_Meta
その「子アイテム」(属性)を表示するための定義を確認する必要があります。
「参照された内部クラス」メソッドは、コードのネストレベルが大きくなるか、行が他の理由で長い場合に、より読みやすくなります。
*もちろん、好みの問題
これは大したことではありませんが、完全を期すためです。内部クラスの存在しない属性にアクセスすると、わずかに異なる例外が発生します。セクション2の例を続ける:
innercls1.foo()
# AttributeError: type object 'Inner' has no attribute 'foo'
innercls2.foo()
# AttributeError: type object '_InnerClass' has no attribute 'foo'
これはtype
、内部クラスのsが
type(innercls1())
#mypackage.outer1.MyOuter1.Inner
type(innercls2())
#mypackage.outer2._InnerClass
私はPythonの内部クラスを使用def test_something():
して、100%のテストカバレッジに近づけるために、ユニットテスト関数内(つまり内)でバグのあるサブクラスを意図的に作成しました(たとえば、一部のメソッドをオーバーライドすることで非常にまれにトリガーされるロギングステートメントをテストします)。
振り返ってみると、エドの答えに似ていますhttps://stackoverflow.com/a/722036/1101109
このような内部クラスはスコープから外れ、それらへのすべての参照が削除されると、ガベージコレクションの準備が整います。たとえば、次のinner.py
ファイルを見てください。
class A(object):
pass
def scope():
class Buggy(A):
"""Do tests or something"""
assert isinstance(Buggy(), A)
OSX Python 2.7.6で以下の奇妙な結果が得られます。
>>> from inner import A, scope
>>> A.__subclasses__()
[]
>>> scope()
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A, scope
>>> from inner import A
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A
>>> import gc
>>> gc.collect()
0
>>> gc.collect() # Yes I needed to call the gc twice, seems reproducible
3
>>> from inner import A
>>> A.__subclasses__()
[]
ヒント-Djangoモデルで続行しないでください。これは、バグのあるクラスへの他の(キャッシュされた)参照を保持しているようです。
したがって、一般に、このような目的で内部クラスを使用することはお勧めしません。ただし、実際に100%のテストカバレッジを評価し、他のメソッドを使用できない場合を除きます。を使用する場合、内部クラスによって汚染される場合__subclasses__()
があることを認識しておくのは良いことだと思います。どちらにしても、ここまでたどっていれば、現時点では、Python、プライベートダンダースコアなど、かなり深いところにいると思います。
.__subclasses__()
でスコープの外に出たときに、内部クラスがガベージコレクターとどのように相互作用するかを理解するために単に使用していることが、より明確になると思います。それは視覚的に投稿を支配しているように見えるので、最初の1〜3段落はもう少し拡張するに値します。