Quel est le but des classes internes de python?

les classes internes / imbriquées de Python me déconcertent. Est-il quelque chose qui ne peut être accompli sans eux? Si oui, quelle est cette chose?

80
demandé sur Geo 2009-04-06 01:13:28

7 réponses

Cité de http://www.geekinterview.com/question_details/64739 :

avantages de la classe intérieure:

  • regroupement logique des classes : si une classe n'est utile que pour une autre classe, il est logique de l'intégrer dans cette classe et de maintenir les deux ensembles. L'imbrication de telles" classes d'assistants " rend leur paquet plus simple.
  • encapsulation accrue : considérer deux classes de premier niveau A et B où B a besoin d'accéder aux membres de A qui autrement seraient déclarés privés. En cachant la Classe B dans la classe a, les membres peuvent être déclarés privés et B peut y accéder. En outre B lui-même peut être caché du monde extérieur.
  • plus lisible, maintenable code : L'imbrication de petites classes dans les classes de haut niveau place le code plus près de l'endroit où il est utiliser.

Le principal avantage est l'organisation. Tout ce qui peut être accompli avec les classes intérieures peut être accompli sans eux.

70
répondu Aziz 2009-04-05 21:35:08

Est-il quelque chose qui ne peut être accompli sans eux?

Pas de. Ils sont tout à fait équivalents à définir la classe normalement au niveau supérieur, puis Copier une référence à elle dans la classe extérieure.

Je ne pense pas qu'il y ait de raison particulière pour que les classes imbriquées soient ‘autorisées’, sauf qu'il n'y a pas de sens particulier à les ‘rejeter’ explicitement non plus.

si vous cherchez un classe qui existe dans le cycle de vie de l'objet externe/propriétaire, et qui a toujours une référence à une instance de la classe externe - classes internes comme Java le fait – alors les classes imbriquées de Python ne sont pas cette chose. Mais vous pouvez pirater quelque chose comme cette chose:

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

(ceci utilise des décorateurs de classe, qui sont nouveaux en Python 2.6 et 3.0. Sinon, il faudrait dire" Inner= innerclass(Inner) " après la définition de la classe.)

46
répondu bobince 2009-04-06 16:34:14

il y a quelque chose que vous devez enrouler votre tête autour pour être en mesure de comprendre cela. Dans la plupart des langues, les définitions de classe sont des directives au compilateur. C'est-à-dire, la classe est créée avant que le programme est exécuté. En python, toutes les instructions sont exécutables. Cela signifie que cette déclaration:

class foo(object):
    pass

est une instruction qui est exécutée à l'exécution comme celle-ci:

x = y + z

Cela signifie que non seulement vous pouvez créer classes dans d'autres classes, vous pouvez créer des classes où vous voulez. Considérez ce code:

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

ainsi, l'idée d'une "classe intérieure" n'est pas vraiment une construction de langage; c'est une construction de programmeur. Guido a un très bon résumé de la façon dont cela est venu sur ici . Mais essentiellement, l'idée de base est que cela simplifie la grammaire de la langue.

24
répondu Jason Baker 2009-04-06 16:38:01

classes de nidification dans les classes:

  • les classes imbriquées gonflent la définition de la classe, ce qui rend plus difficile de voir ce qui se passe.

  • les classes imbriquées peuvent créer un couplage qui rendrait les tests plus difficiles.

  • en Python vous pouvez mettre plus d'une classe dans un fichier/module, contrairement à Java, de sorte que la classe reste encore proche de la classe supérieure et pourrait même avoir le nom de classe préfixé avec un " _ " pour aider à signifier que d'autres ne devraient pas l'utiliser.

l'endroit où les classes imbriquées peuvent s'avérer utiles est dans les fonctions

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

la classe capture les valeurs de la fonction vous permettant de créer dynamiquement une classe comme le template metaprogramming en C++

12
répondu Ed. 2009-04-07 13:18:59

je comprends les arguments contre les classes imbriquées, mais il y a des raisons de les utiliser dans certaines occasions. Imaginez que je crée une classe de liste doublement liée, et j'ai besoin de créer une classe de noeud pour maintenir les noeuds. J'ai deux choix, créer une classe de noeuds dans la classe DoublyLinkedList, ou créer la classe de noeuds en dehors de la classe DoublyLinkedList. Je préfère le premier choix dans ce cas, parce que la classe Node n'est significative qu'à l'intérieur de la classe DoublyLinkedList. Il n'y a pas Cacher/encapsuler avantage, il y a un avantage de regroupement de pouvoir dire que la classe de Noeud fait partie de la classe DoublyLinkedList.

4
répondu user411279 2012-04-29 22:52:04

j'ai utilisé les classes internes de Python pour créer délibérément des sous-classes buggées à l'intérieur des fonctions unittest (i.e. à l'intérieur de def test_something(): ) afin de me rapprocher de la couverture de 100% des tests (par exemple, les tests ont très rarement déclenché des instructions de journalisation en contournant certaines méthodes).

rétrospectivement, C'est similaire à la réponse D'Ed https://stackoverflow.com/a/722036/1101109

de telles classes intérieures devraient sortir de portée et être prêt pour le ramassage des ordures une fois que toutes les références à eux ont été enlevées. Par exemple, prendre le suivant inner.py fichier:

class A(object):
    pass

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

j'obtiens les curieux résultats suivants sous 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__()
[]

indice-n'essayez pas de faire ça avec les modèles Django, qui semblent garder les autres (en cache?) références à mes cours de buggy.

donc en général, je ne recommande pas d'utiliser les classes intérieures pour ce genre de but à moins que vous ne valorisez vraiment 100% de couverture de test et ne pouvez pas utiliser d'autres méthodes. Bien que je pense qu'il est agréable d'être conscient que si vous utilisez le __subclasses__() , qu'il peut parfois obtenir pollué par les classes intérieures. Dans tous les cas, si vous avez suivi jusqu'ici, je pense que nous sommes assez profondément dans Python à ce point, privé dunderscores et tout.

0
répondu pzrq 2017-05-23 12:10:09

le cas d'utilisation principal pour lequel je l'utilise est la prévention de la prolifération de petits modules et pour prévenir la pollution de l'espace de noms lorsque des modules séparés ne sont pas nécessaires. Si j'élargis une classe existante, mais cette classe existante doit renvoyer à une autre sous-classe qui devrait toujours être couplée à elle. Par exemple, je peux avoir un module utils.py qui contient de nombreuses classes d'aide, qui ne sont pas nécessairement couplées ensemble, mais je veux renforcer le couplage pour certains de ces classes d'assistance. Par exemple, quand j'implémente 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) 

Ensuite, nous pouvons:

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

bien sûr, nous aurions pu éviter d'hériter json.JSONEnocder et simplement remplacer 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}'

mais parfois juste pour les conventions, vous voulez que utils soit composé de classes pour l'extensibilité.

voici un autre cas d'utilisation: je veux une usine pour les mutables dans ma classe extérieure sans avoir à invoquer 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]

je préfère Ce modèle plutôt que le décorateur @staticmethod que vous utiliseriez autrement pour une fonction d'usine.

0
répondu cowbert 2017-10-25 23:31:12