¿Hay una función de identidad incorporada en Python?

145

Me gustaría señalar una función que no hace nada:

def identity(*args)
    return args

mi caso de uso es algo como esto

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

Por supuesto, podría usar lo identitydefinido anteriormente, pero un dispositivo incorporado ciertamente funcionaría más rápido (y evitaría errores introducidos por mí mismo).

Aparentemente, mapy se filterusa Nonepara la identidad, pero esto es específico para sus implementaciones.

>>> _=None
>>> _("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
rds
fuente
66
¿Qué quieres decir con map and filter use None for the identity?
Matt Fenwick
15
@MattFenwick:map(None, [1, 2, 3])
Greg Hewgill
66
Echa un vistazo al valor de retorno. Su variable args será una secuencia de (en este escenario) un valor, por lo tanto, omita el asterisco en la declaración o descomprímalo antes de regresar.
Dirk
11
@GregHewgill: Lamentablemente, eso no funciona en Python 3.x.
Ethan Furman el
66
@GregHewgill Mi mal. Tomé eso del documento después de buscar en Google. Pero el documento de Python2.x siempre es lo primero ...
rds

Respuestas:

99

Investigando un poco más, no hay ninguno, se solicitó una característica en el número 1673203 y Raymond Hettinger dijo que no habrá :

Es mejor dejar que las personas escriban sus propios pases triviales y piensen en los costos de firma y tiempo.

Entonces, una mejor manera de hacerlo es en realidad (una lambda evita nombrar la función):

_ = lambda *args: args
  • ventaja: toma cualquier número de parámetros
  • desventaja: el resultado es una versión en caja de los parámetros

O

_ = lambda x: x
  • ventaja: no cambia el tipo del parámetro
  • desventaja: toma exactamente 1 parámetro posicional
rds
fuente
13
Tenga en cuenta que esta no es una función de identidad.
Marcin
1
@ Marcin Gracias por el comentario. He agregado ventajas / desventajas de los dos para no engañar a nadie. Y ahora, realmente creo que debería haber habido una función integrada que acepta cualquier número de parámetros y es una verdadera identidad :)
rds
77
Buena respuesta. Sin embargo, ¿qué devolvería una verdadera función de identidad al tomar múltiples parámetros?
Marcin
55
@Marcin: Tampoco, solo siguiendo lo que él preguntó en su pregunta.
Ethan Furman
44
Sí, gracias, tengo una lambda x: xfunción de identidad trivial que funciona para un parámetro de cadena. @ Marcin Ojalá pudiera hacer lambda *args: *args:-)
rds
28

Una función de identidad, como se define en https://en.wikipedia.org/wiki/Identity_function , toma un solo argumento y lo devuelve sin cambios:

def identity(x):
    return x

Lo que están pidiendo para cuando usted dice que quiere la firma def identity(*args)es no estrictamente una función de identidad, ya que desea que se lleve varios argumentos. Está bien, pero luego se encuentra con un problema, ya que las funciones de Python no devuelven múltiples resultados, por lo que debe encontrar una manera de agrupar todos esos argumentos en un valor de retorno.

La forma habitual de devolver "valores múltiples" en Python es devolver una tupla de los valores, técnicamente ese es un valor de retorno, pero puede usarse en la mayoría de los contextos como si fueran valores múltiples. Pero hacer eso aquí significa que obtienes

>>> 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,)

Y solucionar ese problema rápidamente genera otros problemas, como lo han demostrado las diversas respuestas aquí.

Entonces, en resumen, no hay una función de identidad definida en Python porque:

  1. La definición formal (una función de argumento único) no es tan útil, y es trivial de escribir.
  2. En general, extender la definición a múltiples argumentos no está bien definido, y es mucho mejor definir su propia versión que funcione de la manera que lo necesita para su situación particular.

Para su caso preciso,

def dummy_gettext(message):
    return message

es casi seguro lo que desea: una función que tiene la misma convención de llamada y retorno que gettext.gettext, que devuelve su argumento sin cambios, y está claramente nombrada para describir lo que hace y dónde está destinado a ser utilizado. Me sorprendería mucho si el rendimiento fuera una consideración crucial aquí.

Paul Moore
fuente
No veo a qué respuestas se refiere con "solucionar ese problema da otros problemas, como han mostrado las respuestas". Específicamente, es suficiente para usar id= lambda *args: args if len(args)>1 else args[0].
Max
21

el tuyo funcionará bien. Cuando se arregla el número de parámetros, puede usar una función anónima como esta:

lambda x: x
tback
fuente
8
Usted puede hacer esto con varargs también: lambda *args: args. Es realmente una elección estilística.
Me gusta más el segundo, ya que requiere cualquier cantidad de argumentos.
rds
44
@delnan @rds: la *argsversión tiene un tipo de retorno diferente, por lo que no son equivalentes incluso para el caso de argumento único.
Marcin
8
@delnan: Dijiste que es una elección estilística, lo que implica incorrectamente que no hay diferencia en la semántica de las dos formas.
Marcin
1
@ Marcin: Es desafortunado si lo implicaba. Me refería a la elección entre defy lambdapara funciones tan simples.
7

No hay una función de identidad incorporada en Python. Una imitación de la función de Haskellid sería:

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

Ejemplo de uso:

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

Como identityno hace nada excepto devolver los argumentos dados, no creo que sea más lento de lo que sería una implementación nativa.

SergiyKolesnikov
fuente
Es la construcción de la llamada en sí lo que lleva tiempo, independientemente de lo que haga después de que se complete la configuración.
chepner
@chepner ¿Podría explicar con más detalle lo que quiere decir? También se debe construir una llamada a una función nativa, ¿verdad? ¿Esta construcción se realiza más rápido que una construcción de llamada a una función no nativa?
SergiyKolesnikov
1
Una llamada a una función definida por el usuario es al menos tan caro como una llamada a una función integrada de, y probablemente más así porque una vez que han llamado la función definida por el usuario, todo lo demás se puede invocar más o definida por el usuario incorporada en funciones
chepner
6

No, no hay

Tenga en cuenta que su identity:

  1. es equivalente a lambda * args: args
  2. Encajonará sus args, es decir

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

Por lo tanto, es posible que desee utilizar lambda arg: arg si desea una verdadera función de identidad.

Nota: Este ejemplo sombreará la idfunción incorporada (que probablemente nunca usará).

Marcin
fuente
1
Tenga en cuenta que id es una función incorporada, y este fragmento lo sobrescribirá.
Arnie97
@ Arnie97 Fair! Me olvidé deid
Marcin
4

Si la velocidad no importa, esto debería manejar todos los casos:

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())

Ejemplos de uso:

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)
Binyan Hu
fuente
1

Trozo de una función de argumento único

gettext.gettext(caso de ejemplo el uso de la OP) acepta un solo argumento, message. Si uno necesita un trozo, no hay razón para regresar en [message]lugar de message( def identity(*args): return args). Así ambos

_ = lambda message: message

def _(message):
    return message

encaja perfectamente.

... pero un incorporado ciertamente correría más rápido (y evitaría errores introducidos por el mío).

Los errores en un caso tan trivial apenas son relevantes. Para un argumento de tipo predefinido, digamos str, podemos str()usarnos como una función de identidad (debido a la cadena interna , incluso conserva la identidad del objeto, vea la idnota a continuación) y compare su rendimiento con la solución 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

Una micro optimización es posible. Por ejemplo, el siguiente código de Cython :

test.pyx

cpdef str f(str message):
    return message

Luego:

$ 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

Función de identidad de objeto incorporada

No confunda una función de identidad con la idfunción incorporada que devuelve la 'identidad' de un objeto (es decir, un identificador único para ese objeto en particular en lugar del valor de ese objeto, en comparación con el ==operador), su dirección de memoria en CPython.

saaj
fuente
¿Una aceleración del 40% "no parece valer demasiado la pena"? En los casos en que la identidad funciona como un "filtro predeterminado" para una función que se ejecuta, por ejemplo, una vez por canal en una imagen de 10,000x10,000 píxeles (quizás no todos los días, pero ciertamente no es raro), esa es la diferencia entre 25 y 9 segundos de tiempo de ejecución! De todos modos, gracias por el ejemplo de Cython.
9999 años
@ 9999 años, estoy de acuerdo. He eliminado el comentario de dignidad. También gracias por mejorar la respuesta. He hecho algunos cambios menores además de los tuyos.
saaj
Si tiene una imagen de 10,000x10,000 píxeles, le recomendaría encarecidamente el uso de operaciones vectorizadas usando algo como numpy. Será mucho más rápido, usará menos memoria y no requerirá escribir código cython.
anthonybell
-2

El hilo es bastante viejo. Pero todavía quería publicar esto.

Es posible construir un método de identidad tanto para argumentos como para objetos. En el siguiente ejemplo, ObjOut es una identidad para ObjIn. Todos los otros ejemplos anteriores no han tratado con 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}
Sud
fuente
esto parece una referencia, si es así, ¿de dónde es?
Jeff Puckett
@JeffPuckettII No seguí tu pregunta. ¿Estás preguntando si el nuevo objeto es una referencia?
Sud
utilizó un resaltado entre comillas para "Es posible construir una identidad ..." que implica una referencia de otra fuente. Si estas son sus propias palabras, sugeriría no resaltarlas como una cita. Realmente no es un gran problema. pero si se trata de una cita de otra fuente, debe incluir una referencia a ella.
Jeff Puckett
¿Cómo responde a la pregunta original map(identity, [1, 2, 3])devuelve [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())-> Resultado: ([1, 2, 3],)
Sud