Esiste una funzione di identità integrata in Python?


145

Vorrei indicare una funzione che non fa nulla:

def identity(*args)
    return args

il mio caso d'uso è qualcosa del genere

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

Certo, potrei usare il identitysopra definito, ma un built-in sarebbe sicuramente più veloce (ed eviterebbe i bug introdotti dal mio).

Apparentemente, mape filterusare Noneper l'identità, ma questo è specifico per le loro implementazioni.

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

6
Cosa intendi con map and filter use None for the identity?
Matt Fenwick,

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

6
Controlla il valore di ritorno. La variabile args sarà una sequenza di (in questo scenario) un valore, quindi ometti l'asterisco nella dichiarazione o decomprimilo prima di restituirlo.
Dirk,

11
@GregHewgill: Purtroppo, non funziona in Python 3.x.
Ethan Furman,

6
@GregHewgill Mio male. L'ho preso dal documento dopo aver cercato su Google. Ma il documento Python2.x viene sempre per primo ...
rds

Risposte:


99

Facendo qualche ricerca in più, non c'è nessuno, una funzione è stata chiesta nel numero 1673203 E da Raymond Hettinger ha detto che non ci saranno :

Meglio lasciare che le persone scrivano i loro pass-through banali e pensare alla firma e ai costi del tempo.

Quindi un modo migliore per farlo è in realtà (un lambda evita di nominare la funzione):

_ = lambda *args: args
  • vantaggio: accetta qualsiasi numero di parametri
  • svantaggio: il risultato è una versione in box dei parametri

O

_ = lambda x: x
  • vantaggio: non cambia il tipo di parametro
  • svantaggio: accetta esattamente 1 parametro posizionale

13
Si noti che questa non è una funzione di identità.
Marcin,

1
@Marcin Grazie per l'osservazione. Ho aggiunto vantaggi / svantaggi dei due per non fuorviare nessuno. E ora, credo davvero che ci dovrebbe essere una funzione integrata che accetta un numero qualsiasi di parametri ed è una vera identità :)
rds

7
Bella risposta. Tuttavia, cosa restituirebbe una vera funzione di identità quando si assumono più parametri?
Marcin,

5
@Marcin: Nemmeno, basta seguire quello che ha chiesto nella sua domanda.
Ethan Furman,

4
Sì, grazie, ho una banale lambda x: xfunzione di identità che funziona per un parametro stringa. @Marcin Vorrei poter fare lambda *args: *args:-)
rds

28

Una funzione di identità, come definita in https://en.wikipedia.org/wiki/Identity_function , accetta un singolo argomento e lo restituisce invariato:

def identity(x):
    return x

Quello che stai chiedendo quando dici che vuoi la firma non lodef identity(*args) è strettamente una funzione di identità, poiché vuoi che prenda più argomenti. Va bene, ma poi si verifica un problema poiché le funzioni Python non restituiscono più risultati, quindi è necessario trovare un modo per stipare tutti questi argomenti in un valore restituito.

Il solito modo di restituire "valori multipli" in Python è di restituire una tupla di valori: tecnicamente si tratta di un valore di ritorno, ma può essere utilizzato nella maggior parte dei contesti come se fosse più valori. Ma farlo qui significa che ottieni

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

E risolvere questo problema rapidamente dà altri problemi, come hanno dimostrato le varie risposte qui.

Quindi, in sintesi, non esiste una funzione di identità definita in Python perché:

  1. La definizione formale (una funzione a argomento singolo) non è così utile ed è banale da scrivere.
  2. L'estensione della definizione a più argomenti non è ben definita in generale e è molto meglio definire la propria versione che funziona come serve per la propria situazione particolare.

Per il tuo caso preciso,

def dummy_gettext(message):
    return message

è quasi certamente quello che vuoi - una funzione che ha la stessa convenzione di richiamo e il ritorno gettext.gettext, che restituisce il suo argomento invariato e che è chiaramente chiamato per descrivere ciò che fa e dove è destinato a essere utilizzato. Sarei piuttosto scioccato se la performance fosse una considerazione cruciale qui.


Non vedo a quali risposte ti riferisci con "la risoluzione di questo problema dà altri problemi, come hanno mostrato le risposte". In particolare, è abbastanza da usare id= lambda *args: args if len(args)>1 else args[0].
Max

21

il tuo funzionerà benissimo. Quando il numero di parametri è fisso puoi usare una funzione anonima come questa:

lambda x: x

8
Si può fare questo con varargs troppo: lambda *args: args. È davvero una scelta stilistica.

Mi piace di più il secondo, poiché richiede un numero qualsiasi di argomenti.
RDS

4
@delnan @rds: la *argsversione ha un tipo di ritorno diverso, quindi non sono equivalenti nemmeno per il caso a argomento singolo.
Marcin,

8
@delnan: hai detto che è una scelta stilistica, il che implica erroneamente che non vi è alcuna differenza nella semantica delle due forme.
Marcin,

1
@Marcin: È un peccato se lo insinuassi. Intendevo scegliere tra defe lambdaper funzioni così semplici.

7

Non esiste una funzione di identità integrata in Python. Un'imitazione della funzione di Haskellid sarebbe:

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

Esempio di utilizzo:

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

Dal momento identityche non fa altro che restituire gli argomenti forniti, non penso che sia più lento di un'implementazione nativa.


È la costruzione della chiamata stessa che richiede tempo, indipendentemente da ciò che fai dopo che l'installazione è stata completata.
Chepner,

@chepner Potresti spiegare più in dettaglio cosa intendi? Anche una chiamata a una funzione nativa deve essere costruita, giusto? Questa costruzione viene eseguita più rapidamente di una costruzione di chiamata in una funzione non nativa?
SergiyKolesnikov il

1
Una chiamata a una funzione definita dall'utente è almeno tanto costosa quanto una chiamata a una funzione incorporata, e probabilmente lo è ancora di più perché una volta chiamata la funzione definita dall'utente, qualsiasi altra cosa può invocare più definite dall'utente o integrate- nelle funzioni.
Chepner,

6

No, non c'è.

Si noti che il tuo identity:

  1. è equivalente a lambda * args: args
  2. Inscatolerà i suoi argomenti - cioè

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

Quindi, potresti voler usare lambda arg: argse vuoi una vera funzione d'identità.

NB: Questo esempio oscurerà la idfunzione integrata (che probabilmente non userai mai).


1
Nota che id è una funzione integrata e questo frammento lo sovrascriverà.
Arnie97,

@ Arnie97 Fair! Mi sono dimenticatoid
Marcin,

4

Se la velocità non ha importanza, questo dovrebbe gestire tutti i casi:

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

Esempi di utilizzo:

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 di una funzione a argomento singolo

gettext.gettext(caso esempio uso del PO) accetta un singolo argomento, message. Se uno ha bisogno di uno stub per questo, non c'è motivo di tornare al [message]posto di message( def identity(*args): return args). Quindi entrambi

_ = lambda message: message

def _(message):
    return message

si adatta perfettamente.

... ma un built-in sarebbe sicuramente più veloce (ed eviterebbe i bug introdotti dal mio).

I bug in un caso così banale sono appena rilevanti. Per un argomento di tipo predefinito, diciamo str, possiamo usare str()se stesso come una funzione di identità (a causa della stringa interna mantiene anche l'identità dell'oggetto, vedi idnota sotto) e confronta le sue prestazioni con la soluzione 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

È possibile una micro-ottimizzazione. Ad esempio, il seguente codice Cython :

test.pyx

cpdef str f(str message):
    return message

Poi:

$ 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

Funzione di identità dell'oggetto incorporata

Non confondere una funzione di identità con la idfunzione integrata che restituisce l '"identità" di un oggetto (che significa un identificatore univoco per quel particolare oggetto piuttosto che il valore di quell'oggetto, rispetto ==all'operatore), il suo indirizzo di memoria in CPython.


Uno speedup del 40% "non sembra valerne la pena"? Nei casi in cui l'identità funziona come "filtro predefinito" per una funzione che viene eseguita, ad esempio, una volta per canale su un'immagine di 10.000x10.000 pixel (forse non tutti i giorni ma certamente non insolita), questa è la differenza tra 25 e 9 secondi di tempo di esecuzione! Indipendentemente da ciò, grazie per l'esempio di Cython.
9999 anni

@ 9999years sono d'accordo. Ho rimosso il commento di dignità. Grazie anche per aver migliorato la risposta. Ho apportato alcune modifiche minori alla tua.
saaj,

Se hai un'immagine di 10.000x10.000 pixel, ti consiglio vivamente di utilizzare operazioni vettoriali con qualcosa di simile a numpy. Sarà molto più veloce, usa molta meno memoria e non richiede la scrittura di codice cython.
anthonybell,

-2

Il thread è piuttosto vecchio. Ma volevo ancora pubblicare questo.

È possibile creare un metodo di identità per argomenti e oggetti. Nell'esempio seguente, ObjOut è un'identità per ObjIn. Tutti gli altri esempi precedenti non hanno trattato i 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}

questo sembra un riferimento, se è così, allora da dove viene?
Jeff Puckett,

@JeffPuckettII Non ho seguito la tua domanda. Stai chiedendo se il nuovo oggetto è un riferimento?
Sud,

hai usato un momento saliente per "È possibile costruire un'identità ..." che implica un riferimento da un'altra fonte. Se queste sono le tue parole, suggerirei di non evidenziarlo come una citazione. davvero non è un grosso problema. ma se questa è una citazione da un'altra fonte, allora dovresti includere un riferimento ad essa.
Jeff Puckett,

Come si risponde alla domanda originale map(identity, [1, 2, 3])restituita [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())-> Risultato: ([1, 2, 3],)
Sud
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.