wake-up-neo.com

Was ist der Zweck von Pythons inneren Klassen?

Pythons innere/verschachtelte Klassen verwirren mich. Gibt es etwas, was ohne sie nicht zu erreichen ist? Wenn ja, was ist das für ein Ding?

81
Geo

Zitiert aus http://www.geekinterview.com/question_details/64739 :

Vorteile der inneren Klasse:

  • Logische Gruppierung von Klassen: Wenn eine Klasse nur für eine andere Klasse nützlich ist, ist es logisch, sie in diese Klasse einzubetten und die beiden zusammen zu halten. Durch das Schachteln solcher "Hilfsklassen" wird das Paket rationalisiert.
  • Erhöhte Kapselung: Betrachten Sie zwei Klassen A und B der obersten Ebene, in denen B Zugriff auf Mitglieder von A benötigt, die sonst als privat deklariert würden. Durch Ausblenden der Klasse B in der Klasse A A können Mitglieder als privat deklariert werden und B kann darauf zugreifen. Außerdem kann B selbst vor der Außenwelt versteckt werden.
  • Besser lesbarer, wartbarer Code: Durch das Schachteln kleiner Klassen in Klassen der obersten Ebene wird der Code näher an den Verwendungsort gesetzt.

Der Hauptvorteil ist die Organisation. Alles, was mit inneren Klassen erreicht werden kann, kann ohne sie erreicht werden.

71
Aziz

Gibt es etwas, das ohne sie nicht möglich ist?

Nein. Sie sind absolut gleichbedeutend damit, die Klasse normalerweise auf oberster Ebene zu definieren und dann einen Verweis darauf in die äußere Klasse zu kopieren.

Ich glaube nicht, dass es einen besonderen Grund dafür gibt, warum verschachtelte Klassen "erlaubt" sind, außer dass es keinen besonderen Sinn macht, sie explizit zu "verbieten".

Wenn Sie nach einer Klasse suchen, die im Lebenszyklus des Outer/Owner-Objekts vorhanden ist und immer einen Verweis auf eine Instanz der Outer-Klasse enthält - innere Klassen, wie es Java tut -, dann sind die verschachtelten Klassen von Python sind nicht das Ding. Aber Sie können etwas hacken wie das Ding:

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

(Hierfür werden Klassendekoratoren verwendet, die in Python 2.6 und 3.0 neu sind. Andernfalls müsste nach der Klassendefinition "Inner = Innerclass (Inner)" angegeben werden.)

47
bobince

Es gibt etwas, das Sie benötigen, um Ihren Kopf zu wickeln, um dies verstehen zu können. In den meisten Sprachen sind Klassendefinitionen Direktiven an den Compiler. Das heißt, die Klasse wird erstellt, bevor das Programm ausgeführt wird. In python sind alle Anweisungen ausführbar. Das bedeutet, dass diese Aussage:

class foo(object):
    pass

ist eine Anweisung, die genau wie diese zur Laufzeit ausgeführt wird:

x = y + z

Dies bedeutet, dass Sie nicht nur Klassen innerhalb anderer Klassen erstellen können, sondern auch Klassen, die Sie an beliebiger Stelle erstellen können. Betrachten Sie diesen Code:

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

Daher ist die Idee einer "inneren Klasse" nicht wirklich ein Sprachkonstrukt; es ist ein Programmierer-Konstrukt. Guido hat eine sehr gute Zusammenfassung, wie dies zu hier kam. Die grundlegende Idee ist jedoch, dass dies die Grammatik der Sprache vereinfacht.

25
Jason Baker

Klassen in Klassen schachteln:

  • Verschachtelte Klassen blähen die Klassendefinition auf, wodurch es schwieriger wird zu sehen, was los ist.

  • Verschachtelte Klassen können eine Kopplung erstellen, die das Testen schwieriger macht.

  • In Python können Sie im Gegensatz zu Java mehr als eine Klasse in eine Datei/ein Modul einfügen, sodass die Klasse immer noch in der Nähe der obersten Klasse bleibt und dem Klassennamen sogar ein "_" vorangestellt werden kann, um zu signalisieren, dass andere Klassen dies nicht sollten es benutzen.

Der Ort, an dem sich verschachtelte Klassen als nützlich erweisen können, ist innerhalb von Funktionen

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

Die Klasse erfasst die Werte der Funktion, sodass Sie eine Klasse wie die Vorlagen-Metaprogrammierung in C++ dynamisch erstellen können

13
Ed.

Ich verstehe die Argumente gegen verschachtelte Klassen, aber es gibt Gründe, warum sie in einigen Fällen verwendet werden. Stellen Sie sich vor, ich erstelle eine doppelt verknüpfte Listenklasse, und ich muss eine Knotenklasse erstellen, um die Knoten zu verwalten. Ich habe zwei Möglichkeiten, die Node-Klasse in der DoublyLinkedList-Klasse zu erstellen oder die Node-Klasse außerhalb der DoublyLinkedList-Klasse zu erstellen. Ich bevorzuge die erste Wahl, da die Node-Klasse nur innerhalb der DoublyLinkedList-Klasse von Bedeutung ist. Es gibt zwar keinen Vorteil für das Ausblenden/Einkapseln, aber es gibt einen Gruppierungsvorteil, wenn man sagen kann, dass die Klasse Node Teil der Klasse DoublyLinkedList ist.

4
user411279

Der Hauptanwendungsfall, für den ich dies verwende, ist die Verhinderung der Verbreitung kleiner Module und, um die Namensraumverschmutzung zu verhindern, wenn separate Module nicht benötigt werden. Wenn ich eine vorhandene Klasse erweitere, muss diese vorhandene Klasse jedoch auf eine andere Unterklasse verweisen, die immer mit ihr verbunden sein sollte. Ich habe beispielsweise ein utils.py-Modul mit vielen Hilfsklassen, die nicht unbedingt miteinander gekoppelt sind, aber ich möchte die Kopplung für einige dieser Hilfsklassen verstärken. Zum Beispiel, wenn ich https://stackoverflow.com/a/8274307/2718295 implementiere

: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) 

Dann können wir:

>>> 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}

Natürlich könnten wir json.JSONEnocder nicht ganz auf das Erben verzichten und einfach default () überschreiben:

:

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}'

Aber manchmal nur für Konventionen möchten Sie, dass utils aus Klassen für Erweiterbarkeit zusammengesetzt ist.

Hier ist ein weiterer Anwendungsfall: Ich möchte eine Factory für Mutables in meiner OuterClass, ohne copy aufrufen zu müssen:

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]

Ich bevorzuge dieses Muster dem @staticmethod-Dekorator, den Sie sonst für eine Werksfunktion verwenden würden.

0
cowbert

Ich habe die inneren Klassen von Python verwendet, um absichtlich fehlerhafte Unterklassen innerhalb von unittest-Funktionen (d. H. In def test_something():) zu erstellen, um näher an die 100% ige Testabdeckung heranzukommen (z. B. das Testen sehr selten ausgelöste Protokollierungsanweisungen durch Überschreiben einiger Methoden).

Im Nachhinein ähnelt es Eds Antwort https://stackoverflow.com/a/722036/1101109

Solche inneren Klassen sollten außerhalb des Geltungsbereichs liegen und für die Garbage Collection bereit sein, sobald alle Verweise auf sie entfernt wurden. Nehmen Sie zum Beispiel die folgende inner.py-Datei:

class A(object):
    pass

def scope():
    class Buggy(A):
        """Do tests or something"""
    assert isinstance(Buggy(), A)

Ich erhalte unter OSX Python 2.7.6 die folgenden interessanten Ergebnisse:

>>> 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__()
[]

Hinweis - Versuchen Sie nicht, dies mit Django-Modellen zu tun, die andere (zwischengespeicherte?) Verweise auf meine fehlerhaften Klassen zu enthalten schienen.

Im Allgemeinen würde ich die Verwendung innerer Klassen für diesen Zweck nicht empfehlen, es sei denn, Sie schätzen diese Testabdeckung von 100% wirklich ein und können keine anderen Methoden verwenden. Obwohl ich denke, es ist schön zu wissen, dass, wenn Sie die __subclasses__() verwenden, manchmal von inneren Klassen verschmutzt werden kann. Wenn Sie so weit gegangen sind, denke ich, sind wir zu diesem Zeitpunkt ziemlich tief in Python verwickelt, private Dunderscores und alle.

0
pzrq