36 votes

Quelle est la relation entre le modèle de données Python et les fonctions intégrées?

Comme je l'ai lu Python réponses sur Stack Overflow, je continue de voir certaines personnes indiquant aux utilisateurs d' utiliser les données du modèle spécial méthodes ou attributs directement.

Je vois alors contredire conseils (parfois de moi-même) en disant de ne pas le faire, et au lieu d'utiliser builtin les fonctions et les opérateurs directement.

Pourquoi est-ce? Quelle est la relation entre le "dsous" les méthodes et les attributs de l'Python, modèle de données et les fonctions internes?

Quand suis-je censé utiliser les noms spéciaux?

45voto

Aaron Hall Points 7381

Quelle est la relation entre le Python, modèle de données et fonctions internes?

  • Les objets internes et les opérateurs utilisent le modèle de données sous-jacente des méthodes ou des attributs.
  • Les objets internes et les opérateurs ont plus un comportement élégant et sont en général plus compatible.
  • Les méthodes spéciales de datamodel sont sémantiquement non-public des interfaces.
  • Les objets internes et les opérateurs de langage sont spécifiquement destinés à être l'interface utilisateur pour le comportement mis en œuvre par des méthodes spéciales.

Ainsi, vous devez vous préférez utiliser les fonctions internes et les opérateurs de l'emplacement possible sur les méthodes et les attributs du modèle de données.

La sémantique interne de l'Api sont plus susceptibles de changer, que les interfaces publiques. Alors que Python n'est pas réellement envisager quoi que ce soit "privé" et expose les internes, qui ne veut pas dire que c'est une bonne idée de l'abus que l'accès. Cette façon de faire comporte les risques suivants:

  • Vous pouvez trouver que vous avez plus de modifications importantes lors de la mise à niveau de votre exécutable Python ou de passer à d'autres implémentations de Python (comme PyPy, IronPython, ou Jython, ou certains autres imprévus de la mise en œuvre.)
  • Vos collègues vont probablement penser mal de vos compétences linguistiques et conscience, et le considérer comme un code-odeur, vous et le reste de votre code pour un examen plus approfondi.
  • Les fonctions internes sont faciles à intercepter comportement pour. En utilisant des méthodes spéciales directement les limites de la puissance de votre Python pour l'introspection et le débogage.

En profondeur

Les fonctions internes et les opérateurs d'invoquer des méthodes spéciales et l'utilisation des attributs particuliers dans le Python datamodel. Ils sont les lisible et maintenable placage qui cache la structure interne des objets. En général, les utilisateurs doivent utiliser les objets internes et les opérateurs dans la langue, par opposition à l'appel des méthodes spéciales ou en utilisant les attributs spéciaux directement.

Les fonctions internes et les opérateurs peuvent aussi avoir de repli ou plus élégant de comportement que les plus primitifs datamodel des méthodes spéciales. Par exemple:

  • next(obj, default) permet de fournir une valeur par défaut au lieu de lever StopIteration quand un itérateur s'épuise, alors qu' obj.__next__() ne le sont pas.
  • str(obj) fallsback d' obj.__repr__() lorsque obj.__str__() n'est pas disponible - considérant que l'appelant obj.__str__() directement poserait un attribut d'erreur.
  • obj != other fallsback d' not obj == other en Python 3 lorsque n __ne__ - appelant obj.__ne__(other) ne serait pas profiter de cette.

(Builtin, les fonctions peuvent être facilement éclipsé, si nécessaire ou souhaitable, sur un module de portée mondiale ou l' builtins module, pour personnaliser le comportement.)

Cartographie les objets internes et les opérateurs pour le modèle de données

Voici une cartographie, avec des notes, des fonctions internes et des opérateurs pour les méthodes et les attributs qu'ils utilisent ou de retour de noter que la règle habituelle est que la fonction builtin généralement des cartes à une méthode spéciale du même nom, mais ce n'est pas assez conséquente pour justifier de donner à cette carte ci-dessous:

builtins/     special methods/
operators  -> datamodel               NOTES (fb == fallback)

repr(obj)     obj.__repr__()          provides fb behavior for str
str(obj)      obj.__str__()           fb to __repr__ if no __str__
bytes(obj)    obj.__bytes__()         Python 3 only
unicode(obj)  obj.__unicode__()       Python 2 only
format(obj)   obj.__format__()        format spec optional.
hash(obj)     obj.__hash__()
bool(obj)     obj.__bool__()          Python 3, fb to __len__
bool(obj)     obj.__nonzero__()       Python 2, fb to __len__
dir(obj)      obj.__dir__()
vars(obj)     obj.__dict__            does not include __slots__
type(obj)     obj.__class__           type actually bypasses __class__ -
                                      overriding __class__ will not affect type
help(obj)     obj.__doc__             help uses more than just __doc__
len(obj)      obj.__len__()           provides fb behavior for bool
iter(obj)     obj.__iter__()          fb to __getitem__ w/ indexes from 0 on
next(obj)     obj.__next__()          Python 3
next(obj)     obj.next()              Python 2
reversed(obj) obj.__reversed__()      fb to __len__ and __getitem__
other in obj  obj.__contains__(other) fb to __iter__ then __getitem__
obj == other  obj.__eq__(other)
obj != other  obj.__ne__(other)       fb to not obj.__eq__(other) in Python 3
obj < other   obj.__lt__(other)       get >, >=, <= with @functools.total_ordering
complex(obj)  obj.__complex__()
int(obj)      obj.__int__()
float(obj)    obj.__float__()
round(obj)    obj.__round__()
abs(obj)      obj.__abs__()

L' operator module length_hint qui a une solution de secours mis en œuvre par une méthode particulière, si __len__ n'est pas mis en œuvre:

length_hint(obj)  obj.__length_hint__() 

En Pointillé Recherches

En pointillé recherches contextuelles. Sans méthode particulière de mise en œuvre, de regarder d'abord dans la hiérarchie de classes de descripteurs de données (comme les propriétés et les slots), puis dans l'instance __dict__ (pour les variables d'instance), puis dans la hiérarchie de classe pour les non-descripteurs de données (comme des méthodes). Des méthodes spéciales pour mettre en œuvre les comportements suivants:

obj.attr      obj.__getattr__('attr')       provides fb if dotted lookup fails
obj.attr      obj.__getattribute__('attr')  preempts dotted lookup
obj.attr = _  obj.__setattr__('attr', _)    preempts dotted lookup
del obj.attr  obj.__delattr__('attr')       preempts dotted lookup

Les descripteurs de

Les descripteurs sont un peu avancé - n'hésitez pas à passer ces entrées et revenir plus tard - rappeler le descripteur de l'instance se trouve dans la hiérarchie de classe (comme des méthodes, des logements et des propriétés). Un descripteur de données met en œuvre, soit __set__ ou __delete__:

obj.attr        descriptor.__get__(obj, type(obj)) 
obj.attr = val  descriptor.__set__(obj, val)
del obj.attr    descriptor.__delete__(obj)

Lorsque la classe est instanciée (défini) le descripteur de méthode __set_name__ est appelé si un descripteur pour informer le descripteur de son nom d'attribut. (Ce qui est nouveau en Python 3.6.) cls est le même que type(obj) - dessus, et 'attr' représente le nom de l'attribut:

class cls:
    @descriptor_type
    def attr(self): pass # -> descriptor.__set_name__(cls, 'attr') 

Les éléments (indice de notation)

L'indice de notation est également contextuelle:

obj[name]         -> obj.__getitem__(name)
obj[name] = item  -> obj.__setitem__(name, item)
del obj[name]     -> obj.__delitem__(name)

Un cas particulier pour les sous-classes d' dict, __missing__ est appelé si __getitem__ ne trouve pas la clé:

obj[name]         -> obj.__missing__(name)  

Les opérateurs

Il existe également des méthodes pour +, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, | opérateurs, par exemple:

obj + other   ->  obj.__add__(other), fallback to other.__radd__(obj)
obj | other   ->  obj.__or__(other), fallback to other.__ror__(obj)

et les opérateurs augmentée d'affectation, +=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=, par exemple:

obj += other  ->  obj.__iadd__(other)
obj |= other  ->  obj.__ior__(other)

et les opérations unaires:

+obj          ->  obj.__pos__()
-obj          ->  obj.__neg__()
~obj          ->  obj.__invert__()

Contexte Les Gestionnaires

Un gestionnaire de contexte définit __enter__, qui est appelé à entrer dans le bloc de code (sa valeur de retour, généralement de soi, est un alias avec as), et __exit__, qui est garanti pour être appelé en quittant le bloc de code, à l'exception de l'information.

with obj as cm:     ->  cm = obj.__enter__()
    raise Exception('message')
->  obj.__exit__(Exception, Exception('message'), traceback_object)

Si __exit__ obtient une exception, puis renvoie une valeur fausse, il sur-relance au départ la méthode.

Si aucune exception, __exit__ reçoit None pour ces trois arguments au lieu de cela, et la valeur de retour est dénuée de sens:

with obj:           ->  obj.__enter__()
    pass
->  obj.__exit__(None, None, None)

Certains Métaclasse Méthodes Spéciales

De même, les classes peuvent avoir des méthodes spéciales (à partir de leur metaclasses) qui prennent en charge les classes de base abstraites:

isinstance(obj, cls) -> cls.__instancecheck__(obj)
issubclass(sub, cls) -> cls.__subclasscheck__(sub)

Une importante vente à emporter est que, bien que les fonctions internes telles que next et bool ne changent pas entre Python 2 et 3, sous-jacents à la mise en œuvre noms sont en train de changer.

Ainsi, en utilisant les objets internes offre aussi plus de la compatibilité ascendante.

Quand suis-je censé utiliser les noms spéciaux?

En Python, les noms qui commencent par des caractères de soulignement sont sémantiquement non-public des noms pour les utilisateurs. Le trait de soulignement est le créateur de la façon de dire, "hands-off, ne les touchez pas."

Ce n'est pas seulement culturel, mais il est aussi en Python du traitement de l'API. Quand un paquet __init__.py utilise import * de fournir une API à partir d'un sous-paquetage, si le sous-paquetage ne fournit pas un __all__,, elle exclut les noms qui commencent par des caractères de soulignement. Le sous-paquetage de l' __name__ seraient également exclus.

IDE auto-complétion des outils sont mélangés dans leur prise en compte des noms qui commencent par un caractère de soulignement non-public. Cependant, j'apprécie beaucoup de ne pas voir l' __init__, __new__, __repr__, __str__, __eq__, etc. (ni créés par l'utilisateur non-public des interfaces) quand je tape le nom d'un objet et une durée.

J'ai donc affirmer:

La spéciale "dsous" méthodes ne sont pas une partie de l'interface publique. Éviter de les utiliser directement.

Donc, quand les utiliser?

Les principaux cas d'utilisation est lors de la mise en œuvre de votre propre objet personnalisé ou sous-classe d'une builtin objet.

Essayez de ne les utiliser que lorsque c'est absolument nécessaire. Voici quelques exemples:

Utiliser l' __name__ attribut spécial sur les fonctions ou classes

Lorsque nous ajoutons une fonction, il est généralement une fonction wrapper en retour, qui cache des informations utiles sur la fonction. Nous devons utiliser la @wraps(fn) décorateur pour nous assurer de ne pas perdre cette information, mais si nous avons besoin du nom de la fonction, nous devons utiliser l' __name__ attribut directement:

from functools import wraps

def decorate(fn): 
    @wraps(fn)
    def decorated(*args, **kwargs):
        print('calling fn,', fn.__name__) # exception to the rule
        return fn(*args, **kwargs)
    return decorated

De même, je ne le suivant quand j'ai besoin du nom de la classe de l'objet dans une méthode (utilisée, par exemple, un __repr__):

def get_class_name(self):
    return type(self).__name__
          # ^          # ^- must use __name__, no builtin e.g. name()
          # use type, not .__class__

En utilisant les attributs spéciaux pour écrire des classes personnalisées ou sous-classé les builtins

Lorsque l'on veut définir un comportement, nous devons utiliser les données, les noms de modèles.

Cela a du sens, puisque nous sommes les réalisateurs, ces attributs ne sont pas privés de nous.

class Foo(object):
    # required to here to implement == for instances:
    def __eq__(self, other):      
        # but we still use == for the values:
        return self.value == other.value
    # required to here to implement != for instances:
    def __ne__(self, other): # docs recommend for Python 2.
        # use the higher level of abstraction here:
        return not self == other  

Cependant, même dans ce cas, nous n'utilisons pas d' self.value.__eq__(other.value) ou not self.__eq__(other) (voir ma réponse ici pour preuve que celui-ci peut entraîner un comportement inattendu.) Au lieu de cela, nous devrions utiliser le niveau d'abstraction supérieur.

Un autre point à qui nous devons utiliser les noms de méthode, c'est quand nous sommes dans un enfant de mise en œuvre, et souhaitez déléguer à la société mère. Par exemple:

class NoisyFoo(Foo):
    def __eq__(self, other):
        print('checking for equality')
        # required here to call the parent's method
        return super(NoisyFoo, self).__eq__(other) 

Conclusion

Des méthodes spéciales permettent aux utilisateurs de mettre en œuvre l'interface pour objet des éléments internes.

Utiliser les fonctions internes et les opérateurs partout où vous le pouvez. Utiliser uniquement des méthodes spéciales où il n'y a pas de documentation API publique.

12voto

Stefan Pochmann Points 5022

Je vais vous montrer certains de l'utilisation que vous apparemment ne pense pas que, des commentaires sur les exemples que vous nous avez montré, et à l'encontre de la vie privée demande à partir de votre propre réponse.


Je suis d'accord avec votre propre réponse que, par exemple, len(a) doit être utilisé, pas a.__len__(). Je l'avais mis comme ceci: len il existe donc nous pouvons l'utiliser, et __len__ il existe donc len pouvez l'utiliser. Ou cependant qui fonctionne vraiment en interne, depuis len(a) peut effectivement être beaucoup plus rapide, au moins par exemple pour les listes et les chaînes de caractères:

>>> timeit('len(a)', 'a = [1,2,3]', number=10**8)
4.22549770486512
>>> timeit('a.__len__()', 'a = [1,2,3]', number=10**8)
7.957335462257106

>>> timeit('len(s)', 's = "abc"', number=10**8)
4.1480574509332655
>>> timeit('s.__len__()', 's = "abc"', number=10**8)
8.01780160432645

Mais outre la définition de ces méthodes dans mes classes, pour utilisation par les fonctions internes et les opérateurs, il m'arrive de les utiliser également comme suit:

Disons que j'ai besoin de donner une fonction de filtre pour une certaine fonction, et je veux utiliser un ensemble s comme le filtre. Je ne vais pas créer une fonction supplémentaire lambda x: x in s ou def f(x): return x in s. Pas de. J'ai déjà une parfaitement bien la fonction que je peux utiliser: l'ensemble de l' __contains__ méthode. C'est plus simple et plus directe. Et encore plus vite, comme indiqué ici (ignorer que je l'enregistrer en tant que f ici, c'est juste pour ce moment de démonstration):

>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = s.__contains__', number=10**8)
6.473739433621368
>>> timeit('f(2); f(4)', 's = {1, 2, 3}; f = lambda x: x in s', number=10**8)
19.940786514456924
>>> timeit('f(2); f(4)', 's = {1, 2, 3}\ndef f(x): return x in s', number=10**8)
20.445680107760325

Ainsi, alors que je n'ai pas l' appeler directement les méthodes magiques comme s.__contains__(x), j'ai l'occasion de passer quelque part, some_function_needing_a_filter(s.__contains__). Et je pense que c'est parfaitement bien, et mieux que le lambda/def alternative.


Mes pensées sur les exemples que vous nous avez montré:

  • Exemple 1: on lui a Demandé comment obtenir la taille d'une liste, il répondit items.__len__(). Même sans aucun raisonnement. Mon verdict: C'est tout simplement faux. Devrait être len(items).
  • Exemple 2: Ne mentionner d[key] = value le premier! Et puis, ajoute d.__setitem__(key, value) avec le raisonnement "si votre clavier est manquant les touches "crochets", qui s'applique rarement et dont je doute était sérieux. Je pense que c'était juste le pied dans la porte pour le dernier point, en précisant que c'est la façon dont nous pouvons soutenir le crochet de syntaxe dans nos propres classes. Qui elle tourne le dos à une suggestion d'utiliser les crochets.
  • Exemple 3: Suggère obj.__dict__. Mauvais, comme l' __len__ exemple. Mais je soupçonne qu'il ne savait pas du tout vars(obj), et je peux le comprendre, en tant que vars est moins commun, connu et le nom ne diffèrent de la "dict" en __dict__.
  • Exemple 4: Suggère __class__. Devrait être type(obj). Je soupçonne que c'est similaire à l' __dict__ histoire, même si je pense que type qui est plus connue.

À propos de la vie privée: Dans votre réponse, vous dites ces méthodes sont "sémantiquement privé". Je suis en total désaccord. Simple et double principaux traits de soulignement sont pour, mais pas le modèle de données spéciale "dsous/magie" méthodes avec double pointe+de fuite des traits de soulignement.

  • Les deux choses que vous utiliser comme arguments sont de l'importation de comportement et d'IDE de l'auto-complétion. Mais l'importation et ces méthodes sont différents, et l'un IDE que j'ai essayé (le populaire PyCharm) n'est pas d'accord avec vous. J'ai créé une classe/objet avec les méthodes d' _foo et __bar__ et puis elle n'a pas d'offrir _foo mais n' offrent __bar__. Et quand j'ai utilisé les deux méthodes de toute façon, PyCharm ne m'avertit à propos de _foo (il appelle un "membre protégé"), pas sur __bar__.
  • PEP 8 dit "faible "usage interne" " indicateur explicite le seul trait de soulignement, et explicitement pour double pointe souligne qu'il mentionne le nom d'amputation et, plus tard, explique que c'est pour les "attributs que vous ne voulez pas les sous-classes à utiliser". Mais le commentaire à propos de double de leader+de fuite souligne de ne pas dire quelque chose comme ça.
  • Le modèle de données de la page vous-même le lien, dit que ces méthode spéciale noms sont "Python approche de la surcharge d'opérateur". Rien de confidentialité. Les mots privée/vie privée/protégé ne semblent même pas n'importe où sur la page.

    Je recommande également la lecture de cet article par Andrew Montalenti sur ces méthodes, soulignant que "La dsous convention est un espace de noms réservés pour le core Python équipe" et "jamais, Jamais, d'inventer votre propre dunders" parce que "Le cœur de Python équipe réservés un peu laid espace de noms pour eux-mêmes". Qui tous les matches de la PEP 8 de l'instruction "ne Jamais inventer [dsous/magie] noms; seuls les utiliser comme documenté". Je pense qu'Andrew est sur place - c'est juste une vilaine espace de noms de l'équipe de base. Et c'est pour l'effet de la surcharge d'opérateur, et non pas sur la vie privée (pas Andrew point, mais le mien et le modèle de données de la page).

En outre Andrew article j'ai aussi vérifié plusieurs plus sur ces "magique"/"dsous" méthodes, et je l'ai trouvé aucun d'entre eux de parler de la vie privée à tous. Ce n'est pas ce dont il s'agit.

Encore une fois, nous devrions utiliser len(a), pas a.__len__(). Mais pas à cause de la vie privée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X