Les avantages d'avoir une fonction statique comme len (), max () et min () par rapport aux appels de méthode hérités

StackOverflow https://stackoverflow.com/questions/1628222

  •  06-07-2019
  •  | 
  •  

Question

Je suis un débutant en python et je ne sais pas pourquoi Python a implémenté les fonctions len (obj), max (obj) et min (obj) en tant que fonctions statiques (je suis du langage java) par rapport à obj.len ( ), obj.max () et obj.min ()

quels sont les avantages et les inconvénients (autres que l'incohérence évidente) de len () ... par rapport aux appels de méthode?

pourquoi guido a-t-il choisi ceci plutôt que les appels de méthode? (cela aurait pu être résolu en python3 si nécessaire, mais cela n'a pas été changé en python3, donc il doit y avoir de bonnes raisons ... j'espère)

merci !!

Était-ce utile?

La solution

Le gros avantage est que les fonctions intégrées (et les opérateurs) peuvent appliquer une logique supplémentaire le cas échéant, au-delà du simple appel des méthodes spéciales. Par exemple, min peut examiner plusieurs arguments et appliquer les contrôles d'inégalité appropriés, ou accepter un seul argument itérable et procéder de la même manière. abs lorsqu'il est appelé sur un objet sans méthode spéciale __ abs __ peut essayer de comparer ledit objet avec 0 et d'utiliser la méthode du signe de changement d'objet si nécessaire (bien que ce ne soit pas le cas actuellement); et ainsi de suite.

Par conséquent, pour des raisons de cohérence, toutes les opérations avec une applicabilité étendue doivent toujours passer par les éléments intégrés et / ou les opérateurs. Il incombe à ces éléments intégrés de rechercher et d'appliquer les méthodes spéciales appropriées (sur un ou plusieurs des arguments). , utilisez une autre logique, le cas échéant, etc.

Un exemple où ce principe n'a pas été correctement appliqué (mais que l'incohérence a été corrigée dans Python 3) est "étape par étape un itérateur suivant": dans la version 2.5 et les versions antérieures, vous deviez définir et appeler le non-spécialement nommé < code> next sur l'itérateur. Dans 2.6 et les versions ultérieures, vous pouvez le faire correctement: l’objet itérateur définit __ next __ , le nouveau next intégré peut l’appeler et s’appliquer. une logique supplémentaire, par exemple pour fournir une valeur par défaut (en 2.6, vous pouvez toujours le faire à l'ancienne, pour des raisons de compatibilité, bien que vous ne puissiez plus utiliser 3. * ).

Autre exemple: considérons l'expression x + y . Dans un langage orienté objet traditionnel (capable de n’affecter que le type de l’argument le plus à gauche - comme Python, Ruby, Java, C ++, C #, & amp; c) si x a une valeur dans le type et y est de votre propre nouveau type, vous n’avez malheureusement pas de chance si le langage insiste pour déléguer toute la logique à la méthode de type (x) qui implémente l'addition (en supposant que le langage autorise la surcharge de l'opérateur; -).

En Python, l'opérateur + (et de la même manière, bien sûr, le operator.add intégré, si c'est ce que vous préférez) essaie le code __ add __ et si celui-ci ne sait pas quoi faire avec y , tente alors le __ radd __ du type de y. Vous pouvez ainsi définir vos types sachant s’ajouter aux entiers, aux flottants, aux complexes, etc., ainsi que ceux sachant s’ajouter de tels types numériques intégrés (vous pouvez les coder de telle sorte que x + y et y + x fonctionnent correctement, lorsque y est une instance de votre nouveau type de fantaisie et que x est une instance de type numérique intégré).

"Fonctions génériques" (comme dans PEAK) sont une approche plus élégante (permettant toute substitution basée sur une combinaison de types, jamais avec le focus fou monomane sur les arguments les plus à gauche que la POO encourage! -), mais (a) ils n'ont malheureusement pas été acceptés pour Python 3, et (b) ils exigent bien sûr que la fonction générique soit exprimée de manière autonome (il serait absolument fou de devoir considérer la fonction comme "appartenant" à n'importe quel type, où Le POINT entier peut être remplacé ou surchargé différemment en fonction de la combinaison arbitraire des types de plusieurs arguments! -). Quiconque a déjà programmé en Common Lisp, Dylan ou PEAK sait de quoi je parle ;-).

Ainsi, les fonctions et les opérateurs autonomes ne sont que LA bonne façon de procéder (même si le manque de fonctions génériques, en Python dépouillé, supprime une partie de l'élégance inhérente). , c’est toujours un mélange raisonnable d’élégance et de praticité! -).

Autres conseils

Il met l'accent sur les capacités d'un objet, pas sur ses méthodes ou son type. Les capacités sont déclarées par "helper". des fonctions telles que __ iter __ et __ len __ , mais elles ne constituent pas l'interface. L’interface se trouve dans les fonctions intégrées, ainsi que dans les opérateurs intégrés tels que + et [] pour l’indexation et le découpage en tranches.

Parfois, il ne s'agit pas d'une correspondance un à un: par exemple, iter (obj) renvoie un itérateur pour un objet et fonctionnera même si __ iter __ n'est pas défini. S'il n'est pas défini, il vérifie si l'objet définit __ getitem __ et renvoie un itérateur accédant à l'objet par rapport à l'objet (comme un tableau).

Cela va de pair avec Dything en Python, nous ne nous soucions que de ce que nous pouvons faire avec un objet, pas que ce soit d'un type particulier.

En fait, ils ne sont pas "statiques". méthodes dans la façon dont vous pensez à eux. Ce sont des fonctions intégrées qui ne font que juste aliaser certaines méthodes sur des objets python qui implémentent eux.

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

Celles-ci sont toujours disponibles pour être appelées, que l'objet les implémente ou non. Le but est d'avoir une certaine cohérence. Au lieu d’une classe ayant une méthode appelée length () et une autre appelée size (), la convention consiste à implémenter len et à laisser les appelants y accéder toujours par le plus lisible len (obj) au lieu d’obj. methodThatDoesQuelquechoseCommon

Je pensais que la raison en était que ces opérations de base pouvaient être effectuées sur des itérateurs dotés de la même interface que des conteneurs. Cependant, cela ne marche pas avec len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... échoue avec TypeError. len () ne consommera pas et ne comptera pas un itérateur; cela ne fonctionne qu'avec les objets qui ont un appel __ len __ .

Donc, en ce qui me concerne, len () ne devrait pas exister. C'est beaucoup plus naturel de dire obj.len que len (obj), et beaucoup plus cohérent avec le reste de la langue et la bibliothèque standard. Nous ne disons pas append (lst, 1); nous disons lst.append (1). Avoir une méthode globale distincte pour length est un cas particulier étrange et incohérent, et mange un nom très évident dans l'espace de noms global, ce qui est une très mauvaise habitude de Python.

Cela n’est pas lié à la frappe de canard; vous pouvez dire getattr (obj, "len") pour décider si vous pouvez utiliser len sur un objet aussi facilement et de manière bien plus cohérente que vous ne pouvez utiliser getattr (obj , "__ len __", .

Tout ceci étant dit, à mesure que les verrues de langage disparaissent - pour ceux qui le considèrent comme une verrue - c’est très facile à vivre.

D'autre part, min et max fonctionnent sur des itérateurs, ce qui leur donne une utilisation en dehors de tout objet particulier. C’est simple, je vais donc donner un exemple:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

Cependant, il n'existe aucune méthode __ min __ ou __ max __ pour remplacer son comportement. Il n'existe donc aucun moyen cohérent de rechercher efficacement des conteneurs triés. Si un conteneur est trié sur la même clé que celle que vous recherchez, min / max sont des opérations O (1) au lieu de O (n), et le seul moyen de l'exposer consiste à utiliser une méthode différente et incohérente. (Cela pourrait être corrigé dans la langue relativement facilement, bien sûr.)

Pour faire suite à un autre problème, il empêche l'utilisation de la liaison de méthode de Python. À titre d’exemple simple et artificiel, vous pouvez le faire pour fournir une fonction permettant d’ajouter des valeurs à une liste:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

et cela fonctionne sur toutes les fonctions membres. Vous ne pouvez pas faire cela avec min, max ou len, car ce ne sont pas des méthodes de l'objet sur lequel elles opèrent. Au lieu de cela, vous devez recourir à functools.partial, solution de contournement maladroite de deuxième classe commune à d’autres langues.

Bien sûr, il s’agit d’un cas peu commun; mais ce sont les cas rares qui nous parlent de la cohérence d'une langue.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top