2017-07-25 7 views
14

J'ai une classe personnalisée,Existe-t-il un moyen de renvoyer une valeur personnalisée pour min et max en Python?

class A: 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 

La classe n'est pas itérables ou indexables ou quelque chose comme ça. Si cela est possible, j'aimerais le garder comme ça. Est-il possible d'avoir quelque chose comme le travail suivant?

>>> x = A(1, 2) 
>>> min(x) 
1 
>>> max(x) 
2 

Ce qui me fait penser à ce sujet est que min et max sont répertoriés comme « Opérations de séquence communes » dans le docs. Puisque range est considéré comme un type de séquence par les mêmes docs, je pensais qu'il devait y avoir une sorte d'optimisation possible pour range, et que je pourrais peut-être en profiter.

Peut-être qu'il existe une méthode magique que je ne connais pas qui permettrait cela?

+0

Cet autre message peut vous aider, même si je ne suis pas complètement sûr qu'il fonctionnerait avec min/max; Cela vaut peut-être la peine d'essayer, cependant: https://stackoverflow.com/questions/7875911/how-to-implement-a-minimal-class-that-behaves-like-a-sequence-in-python –

+0

@depperm in cette instance je crois que 'a' sera passé comme' self'. Utiliser 'self' est juste une convention. – cssko

+1

voulez-vous dire que cela fonctionne sans définir '__iter__' /' __next__'? Je ne suis pas sûr de comprendre votre confusion. –

Répondre

20

Oui. Lorsque min prend un argument, il suppose qu'il est itératif, itère sur lui et prend la valeur minimale. Donc,

class A: 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 
    def __iter__(self): 
     yield self.a 
     yield self.b 

Devrait fonctionner. Remarque: Si vous ne voulez pas utiliser __iter__, je ne connais pas le moyen de le faire. Vous voulez probablement créer votre propre fonction min, qui appelle une méthode __min__ s'il y en a une dans l'argument auquel elle est transmise et appelle l'ancienne min sinon.

oldmin = min 
def min(*args) 
    if len(args) == 1 and hasattr(args[0], '__min__'): 
    return args[0].__min__() 
    else: 
    return oldmin(*args) 
+6

[PEP 8 interdit] (https://www.python.org/dev/peps/pep-0008/) la création de vos propres méthodes dunder. Utilisez '__min' à la place. –

+1

Correction: '_min' car' __min' est rogné par le nom. –

+4

Qu'est-ce qui m'a manqué, pourquoi ne pouvez-vous pas simplement appeler la méthode 'min' à la place de toute variante de soulignement? Il ne semble pas être une méthode "magique" ou quoi que ce soit, c'est juste une méthode qui sera appelée ailleurs en dehors de la bibliothèque standard. – Mephy

6

Depuis range est considéré comme un type de séquence par les mêmes documents, je pense qu'il doit y avoir une sorte d'optimisation qui est possible pour range, et que peut-être que je pourrais en profiter .

Il n'y a pas d'optimisation en cours pour les gammes et il n'y a pas de méthodes magiques spécialisés pour min/max.

Si vous Peek the implementation for min/max vous verrez que après quelques analyse des arguments est fait, a call to iter(obj) (c.-à-obj.__iter__()) est fait pour saisir un itérateur:

it = PyObject_GetIter(v); 
if (it == NULL) { 
    return NULL; 
} 

puis calls to next(it) (c.-à-it.__next__) sont effectuées dans un boucle pour saisir des valeurs pour les comparaisons:

while ((item = PyIter_Next(it))) { 
    /* Find min/max */ 

est-il possible d'avoir quelque chose comme les travaux suivants?

Non, si vous voulez utiliser le min * intégré, la seule option que vous avez est l'implémentation du protocole itérateur.


* Par rapiéçage min, vous pouvez bien-sûr, faire faire tout ce que vous voulez. Évidemment, au coût d'exploitation dans Pythonland.Si, cependant, vous pensez pouvoir utiliser certaines optimisations, je vous suggère de créer une méthode min plutôt que de redéfinir le min intégré.

En outre, si vous avez seulement ints comme variables d'instance et ne vous dérange pas un autre appel, vous pouvez toujours utiliser vars pour saisir le instance.__dict__ puis fournir c'est .values()-min:

>>> x = A(20, 4) 
>>> min(vars(x).values()) 
4 
+0

Cela a du sens, décevant comme il est. Je vais poser une autre question sur la façon d'identifier l'appelant afin que je puisse lever une exception si quelqu'un d'autre que 'min' ou' max' essaye d'attraper l'itérateur. J'espère que cela ne vous dérange pas de donner des points à l'autre gars. –

+0

@MadPhysicist Je ne pense pas que vous ayez besoin de poster une autre question pour ça. À moins que quelqu'un lisant le commentaire ici ne soit d'accord avec moi, je pense que si vous faites quelque chose comme ça pour vérifier cela devrait suffire: https://pastebin.com/AFNGcWHw – idjaw

+1

@MadPhysicist: Impossible de faire cela. (Le fait que vous essayez même est un drapeau rouge "reconsidérer vos décisions de conception") – user2357112

6

Il ne sont pas __min__ et __max__ méthodes spéciales *. C'est un peu dommage car range a vu quelques pretty nice optimizations in Python 3. Vous pouvez le faire:

>>> 1000000000000 in range(1000000000000) 
False 

Mais ne pas essayer sauf si vous voulez attendre longtemps:

>>> max(range(1000000000000)) 

créer vos propres Cependant min/fonctions max est une assez bonne idée, comme l'a suggéré par Lærne.

Voici comment je le ferais. MISE À JOUR: supprimé le nom de Dunder __min__ en faveur de _min, tel que recommandé par PEP 8:

inventent jamais ces noms; ne les utiliser comme indiqué

code:

from functools import wraps 

oldmin = min 

@wraps(oldmin) 
def min(*args, **kwargs) 
    try: 
     v = oldmin(*args, **kwargs) 
    except Exception as err: 
     err = err 
    try: 
     arg, = args 
     v = arg._min() 
    except (AttributeError, ValueError): 
     raise err 
    try: 
     return v 
    except NameError: 
     raise ValueError('Something weird happened.') 

Je pense que cette façon est peut-être un peu mieux car il gère certains cas de coin autre réponse n'a pas pris en compte.

Notez qu'un objet itératif avec une méthode _min sera toujours consommé par oldmin comme d'habitude, mais la valeur de retour est remplacée par la méthode spéciale. Cependant, si la méthode _min nécessite que l'itérateur soit toujours disponible pour la consommation, il faudra le modifier, car l'itérateur est d'abord consommé par oldmin.

Notez également que si la méthode __min est simplement mis en œuvre en appelant oldmin, les choses vont encore fonctionner correctement (même si l'itérateur a été consommé, ce qui est dû au fait oldmin soulève une ValueError dans ce cas).

* De telles méthodes sont souvent appelées «magiques», mais ce n'est pas la terminologie préférée.

+5

'__min' est rogné par un nom, donc c'est vraiment gênant de nommer une méthode. – user2357112

+1

@ user2357112 Ouais c'est vrai. Je vais le changer en juste '_min'. –