web-dev-qa-db-ja.com

Pythonの内部クラスの目的は何ですか?

Pythonの内部/ネストされたクラスは私を混乱させます。それらなしでは達成できないことはありますか?もしそうなら、それは何ですか?

83
Geo

http://www.geekinterview.com/question_details/64739 から引用:

内部クラスの利点:

  • クラスの論理的なグループ化:クラスが他の1つのクラスのみに有用である場合、そのクラスにそれを埋め込み、2つをまとめるのが論理的です。このような「ヘルパークラス」をネストすると、パッケージがより合理化されます。
  • カプセル化の増加:2つのトップレベルクラスAおよびBを検討します。Bは、プライベートと宣言されるAのメンバーにアクセスする必要があります。クラスA内でクラスBを非表示にすることで、Aのメンバーをプライベートと宣言し、Bがそれらにアクセスできます。さらに、B自体を外界から隠すことができます。
  • 読みやすく保守しやすいコード:最上位クラス内に小さなクラスをネストすると、コードが使用される場所の近くに配置されます。

主な利点は組織です。内部クラスで達成できることはすべて、それらなしで達成できます

74
Aziz

それらなしでは達成できないことはありますか?

いいえ。通常はトップレベルでクラスを定義し、そのクラスへの参照を外部クラスにコピーすることとまったく同じです。

ネストされたクラスが「許可」される特別な理由はないと思いますが、明示的に「禁止」することも特に意味がありません。

外部/所有者オブジェクトのライフサイクル内に存在し、常に外部クラスのインスタンスへの参照を持っているクラスを探している場合—内部クラスJavaはそれを行う– then Pythonのネストされたクラスはそうではありませんが、何かをハックすることができますlikeそのこと:

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)」と言う必要があります。)

47
bobince

これを理解するために頭を包む必要があるものがあります。ほとんどの言語では、クラス定義はコンパイラーに対するディレクティブです。つまり、プログラムが実行される前にクラスが作成されます。 Pythonでは、すべてのステートメントが実行可能です。つまり、このステートメントは次のことを意味します。

class foo(object):
    pass

次のような実行時に実行されるステートメントです。

x = y + z

これは、他のクラス内にクラスを作成できるだけでなく、任意の場所にクラスを作成できることを意味します。次のコードを検討してください。

def foo():
    class bar(object):
        ...
    z = bar()

したがって、「内部クラス」という考え方は、実際には言語構成ではありません。それはプログラマーの構造です。 Guidoには、これがどのように発生したかについて非常に良い要約があります here 。しかし基本的に、これは言語の文法を簡素化するという基本的な考え方です。

25
Jason Baker

クラス内のクラスのネスト:

  • ネストされたクラスはクラス定義を肥大化し、何が起こっているかを見にくくします。

  • ネストされたクラスは、テストをより困難にするカップリングを作成できます。

  • Python Javaとは異なり、ファイル/モジュールに複数のクラスを配置できます。そのため、クラスはトップレベルのクラスに近いままで、クラス名の前に「_」を付けることもできます。他の人が使用すべきではないことを示すのに役立ちます。

ネストされたクラスが有用であると証明できる場所は関数内です

def some_func(a, b, c):
   class SomeClass(a):
      def some_method(self):
         return b
   SomeClass.__doc__ = c
   return SomeClass

クラスは関数から値をキャプチャし、C++でのテンプレートメタプログラミングのようなクラスを動的に作成できます。

13
Ed.

ネストされたクラスに対する引数は理解していますが、場合によってはそれらを使用する場合があります。二重リンクリストクラスを作成し、ノードを維持するためのノードクラスを作成する必要があると想像してください。 DoublyLinkedListクラス内にNodeクラスを作成する、またはDoublyLinkedListクラス外にNodeクラスを作成します。 NodeクラスはDoublyLinkedListクラス内でのみ意味があります。隠蔽/カプセル化の利点はありませんが、NodeクラスはDoublyLinkedListクラスの一部です。

5
user411279

私がこれを使用する主な使用例は、個別のモジュールが不要な場合に名前空間の汚染を防ぐために、小さなモジュールandの拡散を防ぐことです。既存のクラスを拡張しているが、その既存のクラスは、常にそれに結合する必要がある別のサブクラスを参照する必要がある場合。たとえば、多くのヘルパークラスが含まれるutils.pyモジュールがあり、必ずしも一緒に結合されているわけではありませんが、someこれらのヘルパークラス。たとえば、 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をクラスで構成したい場合があります。

別のユースケースは次のとおりです。copyを呼び出さずに、OuterClassのmutablesのファクトリが必要です。

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デコレーターよりも好みです。

0
cowbert

私はPythonの内部クラスを使用して、ユニットテスト関数内に意図的にバグのあるサブクラスを作成しました(つまり、def test_something():内)、100%のテストカバレッジに近づけます(たとえば、いくつかのメソッドをオーバーライドして非常にまれにトリガーされたロギングステートメントをテストします)。

振り返ってみると、Edの答えに似ています https://stackoverflow.com/a/722036/1101109

そのような内部クラスshouldはスコープから外れ、それらへのすべての参照が削除されるとガベージコレクションの準備ができます。たとえば、次の_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__()を使用すると、内部クラスによってsometimes汚染される可能性があることに注意してください。どちらにしても、ここまで進んだなら、私たちはPython、プライベートアンダースコア、その他すべてに深く関わっていると思います。

0
pzrq