Existe uma função de identidade interna em python?


145

Eu gostaria de apontar para uma função que não faz nada:

def identity(*args)
    return args

meu caso de uso é algo como isto

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

Obviamente, eu poderia usar o identitydefinido acima, mas um built-in certamente funcionaria mais rápido (e evitaria bugs introduzidos por mim).

Aparentemente, mape filteruse Nonepara a identidade, mas isso é específico para suas implementações.

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

6
Como assim map and filter use None for the identity?
Matt Fenwick

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

6
Confira o valor de retorno. Sua variável args será uma sequência de (neste cenário) um valor, portanto, omita o asterisco na declaração ou descompacte-o antes de retornar.
Dirk Dirk

11
@GregHewgill: Infelizmente, isso não funciona no Python 3.x.
Ethan Furman

6
@GregHewgill My bad. Tirei isso do documento depois de pesquisar no Google. Mas o documento Python2.x sempre vem em primeiro lugar ...
rds 06/01

Respostas:


99

Fazendo mais algumas pesquisas, não há, um recurso foi solicitado na edição 1673203 E de Raymond Hettinger disse que não haverá :

Melhor deixar que as pessoas escrevam suas próprias passagens triviais e pensem nos custos de assinatura e tempo.

Portanto, a melhor maneira de fazer isso é (um lambda evita nomear a função):

_ = lambda *args: args
  • vantagem: leva qualquer número de parâmetros
  • desvantagem: o resultado é uma versão em caixa dos parâmetros

OU

_ = lambda x: x
  • vantagem: não altera o tipo do parâmetro
  • desvantagem: leva exatamente 1 parâmetro posicional

13
Observe que isso não é uma função de identidade.
Marcin

1
@ Marcin Obrigado pela observação. Eu adicionei vantagens / desvantagens dos dois para não enganar ninguém. E agora, eu realmente acredito que deve ter sido uma função interna que aceita qualquer número de parâmetros e é uma verdadeira identidade :)
RDS

7
Boa resposta. No entanto, o que uma verdadeira função de identidade retornaria ao usar vários parâmetros?
Marcin

5
@ Marcin: Nem, apenas seguindo o que ele pediu em sua pergunta.
Ethan Furman

4
Sim, obrigado, eu tenho uma lambda x: xfunção de identidade trivial que funciona para um parâmetro de string. @Marcin Eu gostaria de poder fazer lambda *args: *args:-)
rds

28

Uma função de identidade, conforme definida em https://en.wikipedia.org/wiki/Identity_function , pega um único argumento e o retorna inalterado:

def identity(x):
    return x

O que você está pedindo quando diz que deseja que a assinatura nãodef identity(*args) seja estritamente uma função de identidade, pois deseja que ela receba vários argumentos. Tudo bem, mas você encontra um problema, pois as funções Python não retornam vários resultados, portanto, é necessário encontrar uma maneira de agrupar todos esses argumentos em um único valor de retorno.

A maneira usual de retornar "múltiplos valores" no Python é retornar uma tupla dos valores - tecnicamente esse é um valor de retorno, mas pode ser usado na maioria dos contextos como se fossem múltiplos valores. Mas fazer isso aqui significa que você obtém

>>> 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 corrigir esse problema rapidamente fornece outros problemas, como as várias respostas aqui mostraram.

Portanto, em resumo, não há função de identidade definida no Python porque:

  1. A definição formal (uma função de argumento único) não é tão útil e é trivial para escrever.
  2. Estender a definição para vários argumentos não é bem definido em geral, e é muito melhor definir sua própria versão que funciona da maneira que você precisa para sua situação específica.

Para o seu caso preciso,

def dummy_gettext(message):
    return message

é quase certamente o que você deseja - uma função que tem a mesma convenção de chamada e retorno que gettext.gettext, que retorna seu argumento inalterado, e é claramente nomeada para descrever o que faz e onde deve ser usada. Eu ficaria muito chocado se o desempenho fosse uma consideração crucial aqui.


Não vejo as respostas a que você se refere como "corrigir esse problema gera outros problemas, como as respostas mostraram". Especificamente, é o suficiente para usar id= lambda *args: args if len(args)>1 else args[0].
Max

21

o seu funcionará bem. Quando o número de parâmetros é corrigido, você pode usar uma função anônima como esta:

lambda x: x

8
Você pode fazer isso com varargs também: lambda *args: args. É realmente uma escolha estilística.

Gosto mais do segundo, já que é preciso qualquer número de argumentos.
Rds

4
@delnan @rds - a *argsversão possui um tipo de retorno diferente, portanto, eles não são equivalentes nem para o caso de argumento único.
Marcin

8
@ delnan: Você disse que é uma escolha estilística, o que implica incorretamente que não há diferença na semântica das duas formas.
Marcin

1
@ Marcin: É lamentável se eu implicar isso. Eu quis dizer a escolha entre defe lambdapara funções tão simples.

7

Não há uma função de identidade interna no Python. Uma imitação da função de Haskellid seria:

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

Exemplo de uso:

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

Como identitynão faz nada, exceto retornar os argumentos fornecidos, não acho que seja mais lento do que uma implementação nativa seria.


É a construção da chamada em si que leva tempo, independentemente do que você faz após a conclusão da instalação.
Chepner 7/08

@chepner Você poderia explicar com mais detalhes o que você quer dizer? Uma chamada para uma função nativa também deve ser construída, certo? Essa construção é feita mais rapidamente do que uma construção de chamada para uma função não nativa?
SergiyKolesnikov 26/03

1
Uma chamada para uma função definida pelo usuário é pelo menos tão caro como uma chamada para um built-in função, e provavelmente mais, porque depois de ter chamado a função definida pelo usuário, qualquer outra coisa que possa invocar mais ou definido pelo usuário incorporado em funções.
chepner 26/03

6

Não, não existe.

Observe que seu identity:

  1. é equivalente a lambda * args: args
  2. Colocará seus argumentos - ie

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

Portanto, você pode usar lambda arg: argse desejar uma verdadeira função de identidade.

Nota: Este exemplo irá ocultar a idfunção interna (que você provavelmente nunca usará).


1
Observe que id é uma função interna e esse trecho substitui-o.
Arnie97 28/08/19

@ Arnie97 Fair! Eu esqueciid
Marcin

4

Se a velocidade não importa, isso deve lidar com todos os 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())

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

1

Esboço de uma função de argumento único

gettext.gettext(o caso de uso de exemplo do OP) aceita um único argumento message,. Se alguém precisar de um esboço, não há motivo para retornar em [message]vez de message( def identity(*args): return args). Assim, ambos

_ = lambda message: message

def _(message):
    return message

se encaixam perfeitamente.

... mas um built-in certamente funcionaria mais rápido (e evitaria bugs introduzidos por mim).

Erros em um caso tão trivial são pouco relevantes. Para um argumento de tipo predefinido, digamos str, podemos usar- str()se como uma função de identidade (por causa da cadeia de caracteres interna, ela ainda mantém a identidade do objeto, veja a idnota abaixo) e comparar seu desempenho com a solução 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

Uma micro-otimização é possível. Por exemplo, o seguinte código Cython :

test.pyx

cpdef str f(str message):
    return message

Então:

$ 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

Função de identidade de objeto incorporada

Não confunda uma função de identidade com a idfunção interna que retorna a 'identidade' de um objeto (ou seja, um identificador exclusivo para esse objeto em particular, e não o valor desse objeto, em comparação com o ==operador), seu endereço de memória no CPython.


Uma aceleração de 40% "não parece valer a pena"? Nos casos em que a identidade opera como um "filtro padrão" para uma função que é executada, digamos, uma vez por canal em uma imagem de 10.000x10.000 pixels (talvez não todos os dias, mas certamente não incomum), essa é a diferença entre 25 e 9 segundos de tempo de execução! Independentemente disso, obrigado pelo exemplo do Cython.
9999years 06/07/19

@ 9999years Eu concordo. Eu removi o comentário de valor. Também obrigado por melhorar a resposta. Fiz algumas pequenas alterações em cima da sua.
saaj

Se você tem uma imagem de 10.000 x 10.000 pixels, recomendo fortemente o uso de operações vetorizadas usando algo como numpy. Será muito mais rápido, usará menos memória e não exigirá a gravação de código cython.
Anthonybell

-2

O tópico é bem antigo. Mas ainda queria postar isso.

É possível criar um método de identidade para argumentos e objetos. No exemplo abaixo, ObjOut é uma identidade para ObjIn. Todos os outros exemplos acima não lidaram com 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}

isso parece uma referência; se sim, de onde é?
precisa

@JeffPuckettII Não segui sua pergunta. Você está perguntando se o novo objeto é uma referência?
Sud

você usou um destaque de citação para "É possível construir uma identidade ...", o que implica uma referência de outra fonte. Se estas são suas próprias palavras, sugiro não destacá-las como uma citação. realmente não é grande coisa. mas se for uma citação de outra fonte, inclua uma referência a ela.
Jeff Puckett

Como você responde à pergunta original map(identity, [1, 2, 3])retorna [1, 2, 3]?
rds 11/05

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.