Existe-t-il une fonction d'identité intégrée en python?


145

Je voudrais signaler une fonction qui ne fait rien:

def identity(*args)
    return args

mon cas d'utilisation est quelque chose comme ça

try:
    gettext.find(...)
    ...
    _ = gettext.gettext
else:
    _ = identity

Bien sûr, je pourrais utiliser la identitydéfinition ci-dessus, mais un intégré fonctionnerait certainement plus rapidement (et éviterait les bogues introduits par moi-même).

Apparemment, mapet à filterutiliser Nonepour l'identité, mais cela est spécifique à leurs implémentations.

>>> _=None
>>> _("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

6
Que voulez-vous dire map and filter use None for the identity?
Matt Fenwick

15
@MattFenwick:map(None, [1, 2, 3])
Greg Hewgill

6
Vérifiez la valeur de retour. Votre variable args sera une séquence (dans ce scénario) d'une valeur, donc soit omettez l'astérisque dans la déclaration, soit décompressez-la avant de la renvoyer.
Dirk

11
@GregHewgill: Malheureusement, cela ne fonctionne pas dans Python 3.x.
Ethan Furman

6
@GregHewgill Mon mauvais. J'ai pris cela du doc ​​après avoir cherché sur Google. Mais la documentation Python2.x vient toujours en premier ...
rds

Réponses:


99

Pour faire plus de recherche, il n'y en a pas, une fonctionnalité a été posée dans le numéro 1673203 Et de Raymond Hettinger a déclaré qu'il n'y en aurait pas :

Mieux vaut laisser les gens écrire leurs propres pass-through triviaux et réfléchir aux coûts de signature et de temps.

Donc, une meilleure façon de le faire est en fait (un lambda évite de nommer la fonction):

_ = lambda *args: args
  • avantage: prend n'importe quel nombre de paramètres
  • inconvénient: le résultat est une version en boîte des paramètres

OU

_ = lambda x: x
  • avantage: ne change pas le type du paramètre
  • inconvénient: prend exactement 1 paramètre de position

13
Notez que ce n'est pas une fonction d'identité.
Marcin

1
@Marcin Merci pour la remarque. J'ai ajouté des avantages / inconvénients des deux afin de ne tromper personne. Et maintenant, je crois vraiment qu'il aurait dû y avoir une fonction intégrée qui accepte n'importe quel nombre de paramètres et est une véritable identité :)
rds

7
Bonne réponse. Cependant, que renverrait une véritable fonction d'identité lors de la prise de plusieurs paramètres?
Marcin

5
@Marcin: Ni l'un ni l'autre, juste ce qu'il a demandé dans sa question.
Ethan Furman

4
Oui merci, j'ai une lambda x: xfonction d'identité triviale qui fonctionne pour un paramètre de chaîne. @Marcin j'aimerais pouvoir faire lambda *args: *args:-)
rds

28

Une fonction d'identité, telle que définie dans https://en.wikipedia.org/wiki/Identity_function , prend un seul argument et le renvoie inchangé:

def identity(x):
    return x

Ce que vous demandez lorsque vous dites que vous voulez la signature def identity(*args)n'est pas strictement une fonction d'identité, car vous voulez qu'elle prenne plusieurs arguments. C'est bien, mais vous rencontrez un problème car les fonctions Python ne renvoient pas plusieurs résultats, vous devez donc trouver un moyen de regrouper tous ces arguments dans une seule valeur de retour.

La manière habituelle de renvoyer des "valeurs multiples" en Python est de renvoyer un tuple des valeurs - techniquement, c'est une valeur de retour mais elle peut être utilisée dans la plupart des contextes comme s'il s'agissait de plusieurs valeurs. Mais faire cela ici signifie que vous obtenez

>>> def mv_identity(*args):
...     return args
...
>>> mv_identity(1,2,3)
(1, 2, 3)
>>> # So far, so good. But what happens now with single arguments?
>>> mv_identity(1)
(1,)

Et résoudre ce problème donne rapidement d'autres problèmes, comme l'ont montré les différentes réponses ici.

Donc, en résumé, il n'y a pas de fonction d'identité définie dans Python car:

  1. La définition formelle (une fonction à un seul argument) n'est pas si utile et est simple à écrire.
  2. Étendre la définition à plusieurs arguments n'est pas bien défini en général, et il vaut mieux définir votre propre version qui fonctionne comme vous en avez besoin pour votre situation particulière.

Pour votre cas précis,

def dummy_gettext(message):
    return message

est presque certainement ce que vous voulez - une fonction qui a la même convention d'appel et le même retour que gettext.gettext, qui retourne son argument inchangé, et est clairement nommée pour décrire ce qu'elle fait et où elle est destinée à être utilisée. Je serais assez choqué si la performance était une considération cruciale ici.


Je ne vois pas à quelles réponses vous faites allusion "la résolution de ce problème donne d'autres problèmes, comme les réponses l'ont montré". Plus précisément, il suffit de l'utiliser id= lambda *args: args if len(args)>1 else args[0].
Max

21

le vôtre fonctionnera bien. Lorsque le nombre de paramètres est fixe, vous pouvez utiliser une fonction anonyme comme celle-ci:

lambda x: x

8
Vous pouvez le faire avec varargs aussi: lambda *args: args. C'est vraiment un choix stylistique.

J'aime mieux le second, car il prend n'importe quel nombre d'arguments.
rds

4
@delnan @rds - la *argsversion a un type de retour différent, donc ils ne sont pas équivalents même pour le cas à argument unique.
Marcin

8
@delnan: Vous avez dit que c'est un choix stylistique, ce qui implique à tort qu'il n'y a pas de différence dans la sémantique des deux formes.
Marcin

1
@Marcin: C'est malheureux si je l'ai laissé entendre. Je voulais dire le choix entre defet lambdapour des fonctions aussi simples.

7

Il n'y a pas de fonction d'identité intégrée dans Python. Une imitation de la fonction de Haskellid serait:

identity = lambda x, *args: (x,) + args if args else x

Exemple d'utilisation:

identity(1)
1
identity(1,2)
(1, 2)

Puisqu'il identityne fait rien d'autre que renvoyer les arguments donnés, je ne pense pas que ce soit plus lent qu'une implémentation native ne le serait.


C'est la construction de l'appel lui-même qui prend du temps, peu importe ce que vous faites une fois la configuration terminée.
chepner

@chepner Pourriez-vous expliquer plus en détail ce que vous voulez dire? Un appel à une fonction native doit également être construit, non? Cette construction est-elle effectuée plus rapidement qu'une construction d'appel à une fonction non native?
SergiyKolesnikov

1
Un appel à une fonction définie par l'utilisateur est au moins aussi coûteux qu'un appel à une fonction intégrée, et probablement plus parce qu'une fois que vous avez appelé la fonction définie par l'utilisateur, tout autre élément peut invoquer plus défini par l'utilisateur ou intégré. dans les fonctions.
chepner

6

Non, il n'y en a pas.

Notez que votre identity:

  1. équivaut à lambda * args: args
  2. Va boxer ses arguments - ie

    In [6]: id = lambda *args: args
    
    In [7]: id(3)
    Out[7]: (3,)

Donc, vous pouvez utiliser lambda arg: arg si vous voulez une véritable fonction d'identité.

NB: Cet exemple va masquer la idfonction intégrée (que vous n'utiliserez probablement jamais).


1
Notez que id est une fonction intégrée et que cet extrait de code l'écrasera.
Arnie97

@ Arnie97 Fair! J'ai oubliéid
Marcin

4

Si la vitesse n'a pas d'importance, cela devrait gérer tous les cas:

def identity(*args, **kwargs):
    if not args:
        if not kwargs:
            return None
        elif len(kwargs) == 1:
            return  next(iter(kwargs.values()))
        else:
            return (*kwargs.values(),)
    elif not kwargs:
        if len(args) == 1:
            return args[0]
        else:
            return args
    else:
        return (*args, *kwargs.values())

Exemples d'utilisation:

print(identity())
None
$identity(1)
1
$ identity(1, 2)
(1, 2)
$ identity(1, b=2)
(1, 2)
$ identity(a=1, b=2)
(1, 2)
$ identity(1, 2, c=3)
(1, 2, 3)

1

Stub d'une fonction à un seul argument

gettext.gettext(l'exemple de cas d' utilisation OP) accepte un seul argument, message. Si on a besoin d'un stub pour cela, il n'y a aucune raison de revenir [message]au lieu de message( def identity(*args): return args). Ainsi les deux

_ = lambda message: message

def _(message):
    return message

s'adapter parfaitement.

... mais un intégré fonctionnerait certainement plus rapidement (et éviterait les bogues introduits par moi-même).

Les bogues dans un cas aussi trivial sont à peine pertinents. Pour un argument de type prédéfini, disons str, nous pouvons nous utiliser str()comme fonction d'identité (en raison de la chaîne interne, il conserve même l'identité de l'objet, voir la idnote ci-dessous) et comparer ses performances avec la solution lambda:

$ python3 -m timeit -s "f = lambda m: m" "f('foo')"
10000000 loops, best of 3: 0.0852 usec per loop
$ python3 -m timeit "str('foo')"
10000000 loops, best of 3: 0.107 usec per loop

Une micro-optimisation est possible. Par exemple, le code Cython suivant:

test.pyx

cpdef str f(str message):
    return message

Ensuite:

$ pip install runcython3
$ makecython3 test.pyx
$ python3 -m timeit -s "from test import f" "f('foo')"
10000000 loops, best of 3: 0.0317 usec per loop

Fonction d'identité d'objet intégrée

Ne confondez pas une fonction d'identité avec la idfonction intégrée qui renvoie `` l'identité '' d'un objet (c'est-à-dire un identifiant unique pour cet objet particulier plutôt que la valeur de cet objet, par rapport à l' ==opérateur), son adresse mémoire en CPython.


Une accélération de 40% "ne semble pas trop en valoir la peine"? Dans les cas où l'identité fonctionne comme un "filtre par défaut" pour une fonction qui s'exécute, par exemple, une fois par canal sur une image de 10 000 x 10 000 pixels (peut-être pas tous les jours mais certainement pas rare), c'est la différence entre 25 et 9 secondes de temps d'exécution! Quoi qu'il en soit, merci pour l'exemple Cython.
9999 ans le

@ 9999 ans, je suis d'accord. J'ai supprimé le commentaire sur la valeur. Merci également d'avoir amélioré la réponse. J'ai apporté quelques modifications mineures en plus des vôtres.
saaj

Si vous avez une image de 10 000 x 10 000 pixels, je vous recommande vivement d'utiliser des opérations vectorisées en utilisant quelque chose comme numpy. Il sera beaucoup plus rapide, utilisera beaucoup moins de mémoire et ne nécessitera pas d'écrire de code cython.
anthonybell

-2

Le fil est assez vieux. Mais je voulais toujours publier ceci.

Il est possible de créer une méthode d'identité pour les arguments et les objets. Dans l'exemple ci-dessous, ObjOut est une identité pour ObjIn. Tous les autres exemples ci-dessus n'ont pas traité de dict ** kwargs.

class test(object):
    def __init__(self,*args,**kwargs):
        self.args = args
        self.kwargs = kwargs
    def identity (self):
        return self

objIn=test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n')
objOut=objIn.identity()
print('args=',objOut.args,'kwargs=',objOut.kwargs)

#If you want just the arguments to be printed...
print(test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n').identity().args)
print(test('arg-1','arg-2','arg-3','arg-n',key1=1,key2=2,key3=3,keyn='n').identity().kwargs)

$ py test.py
args= ('arg-1', 'arg-2', 'arg-3', 'arg-n') kwargs= {'key1': 1, 'keyn': 'n', 'key2': 2, 'key3': 3}
('arg-1', 'arg-2', 'arg-3', 'arg-n')
{'key1': 1, 'keyn': 'n', 'key2': 2, 'key3': 3}

cela ressemble à une référence, si oui, d'où vient-elle?
Jeff Puckett

@JeffPuckettII Je n'ai pas suivi votre question. Demandez-vous si le nouvel objet est une référence?
Sud

vous avez utilisé un surlignage de blockquote pour "Il est possible de construire une identité ..." qui implique une référence d'une autre source. Si ce sont vos propres mots, alors je suggérerais de ne pas les mettre en évidence comme une citation. vraiment pas un gros problème. mais s'il s'agit d'une citation d'une autre source, vous devez y inclure une référence.
Jeff Puckett

Comment répondez-vous aux map(identity, [1, 2, 3])retours de questions d'origine [1, 2, 3]?
rds

class test1(object): def __init__(self,*args,**kwargs): self.args = args self.kwargs = kwargs def identity (self): return self.args print(test1([1,2,3]).identity())-> Résultat: ([1, 2, 3],)
Sud
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.