web-dev-qa-db-fra.com

Quel est le but des classes internes de python?

Les classes internes/imbriquées de Python me déroutent. Y a-t-il quelque chose qui ne peut être accompli sans eux? Si oui, quelle est cette chose?

81
Geo

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

Avantages de la classe intérieure:

  • Regroupement logique de classes: si une classe n'est utile qu'à une autre classe, il est logique de l'intégrer dans cette classe et de conserver les deux ensemble. L'imbrication de telles "classes d'assistance" rend leur paquet plus rationalisé.
  • Encapsulation accrue: considérons deux classes de niveau supérieur A et B où B a besoin d'accéder aux membres de A qui seraient autrement déclarés privés. En cachant la classe B au sein de la classe A, les membres de A peuvent être déclarés privés et B peut y accéder. De plus, B lui-même peut être caché du monde extérieur.
  • Code plus lisible et maintenable: L'imbrication de petites classes dans les classes de niveau supérieur place le code plus près de l'endroit où il est utilisé.

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

71
Aziz

Y a-t-il quelque chose qui ne peut être accompli sans eux?

Non, ils sont absolument équivalents à la définition de la classe normalement au niveau supérieur, puis à la copie d'une référence dans la classe externe.

Je ne pense pas qu’il y ait une raison particulière pour que les classes imbriquées soient "autorisées", sinon il n’a aucun sens particulier de les "interdire" explicitement non plus.

Si vous recherchez une classe qui existe dans le cycle de vie de l'objet externe/propriétaire et a toujours une référence à une instance de la classe externe (les classes internes comme le fait Java), les classes imbriquées de Python ne sont pas ce genre de choses. 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

(Cela utilise les décorateurs de classe, qui sont nouveaux dans Python 2.6 et 3.0. Sinon, vous devriez dire «Inner = innerclass (Inner)» après la définition de la classe.)

47
bobince

Vous devez comprendre quelque chose pour pouvoir comprendre cela. Dans la plupart des langages, les définitions de classe sont des directives données au compilateur. C'est-à-dire que la classe est créée avant l'exécution du programme. 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 au moment de l'exécution, exactement comme celle-ci:

x = y + z

Cela signifie que non seulement vous pouvez créer des classes au sein d'autres classes, vous pouvez créer des classes où vous le souhaitez. Considérons ce code:

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

Ainsi, l'idée d'une "classe interne" 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 arrivé ici . Mais essentiellement, l’idée de base est que cela simplifie la grammaire de la langue.

25
Jason Baker

Cours de nidification dans les cours:

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

  • Les classes imbriquées peuvent créer un couplage rendant les tests plus difficiles.

  • En Python, vous pouvez mettre plus d'une classe dans un fichier/module, contrairement à Java. La classe reste donc proche de la classe de niveau supérieur et peut même avoir le nom de classe préfixé par un "_" pour aider à signifier que les autres ne devraient pas l'être. En l'utilisant.

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, ce qui vous permet de créer dynamiquement une classe telle que la métaprogrammation de modèle en C++.

13
Ed.

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 chaînée et que je doive créer une classe de nœuds pour gérer les nœuds. J'ai deux choix: créer la classe Node dans la classe DoublyLinkedList ou créer la classe Node en dehors de la classe DoublyLinkedList. Dans ce cas, je préfère le premier choix, car la classe Node n’a de sens que dans la classe DoublyLinkedList. Bien qu'il n'y ait aucun avantage à cacher ou à encapsuler, il existe un avantage de groupement de pouvoir dire que la classe Node fait partie de la classe DoublyLinkedList.

4
user411279

Le principal cas d’utilisation pour lequel j’utilise ceci est la prévention de la prolifération de petits modules et afin d’empêcher la pollution des espaces de noms lorsque des modules séparés ne sont pas nécessaires. Si j'étends une classe existante, mais que cette classe existante doit référencer une autre sous-classe qui devrait toujours lui être couplée. Par exemple, je peux avoir un module utils.py qui contient de nombreuses classes d’aide, qui ne sont pas nécessairement couplées entre elles, mais je souhaite renforcer le couplage pour certaines de ces classes d’aide. Par exemple, lorsque 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) 

Alors 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 l'héritage de 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, par convention, vous voulez que utils soit composé de classes pour l’extensibilité.

Voici un autre cas d'utilisation: je veux une fabrique de mutables dans ma classe OuterClass 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 au décorateur @staticmethod que vous utiliseriez sinon pour une fonction d'usine.

0
cowbert

J'ai utilisé les classes internes de Python pour créer des sous-classes délibérément boguées dans les fonctions unittest (c'est-à-dire dans def test_something():) afin de me rapprocher de la couverture de test à 100% (par exemple, tester très rarement des instructions de journalisation en remplaçant certaines méthodes).

Rétrospectivement, cela ressemble à la réponse de Ed https://stackoverflow.com/a/722036/1101109

Ces classes internes devraient sortir du cadre et être prêtes pour le ramassage des ordures une fois que toutes les références à celles-ci ont été supprimées. Par exemple, prenons le fichier inner.py suivant:

class A(object):
    pass

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

Les résultats suivants sont curieux 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__()
[]

Astuce - N'essayez pas de faire cela avec les modèles Django, qui semblaient conserver d'autres références (mises en cache?) À mes classes de buggy.

Donc, en général, je ne recommanderais pas d’utiliser des classes internes pour ce type d’utilisation, sauf si vous tenez vraiment à la couverture de test à 100% et ne pouvez pas utiliser d’autres méthodes. Bien que je pense que cela soit agréable de savoir que si vous utilisez __subclasses__(), cela peut parfois être pollué par des classes internes. Quoi qu’il en soit, si vous avez suivi jusque-là, je pense que nous sommes plutôt au cœur de Python, dunderscores privés et autres.

0
pzrq