Czy w Pythonie jest wbudowana funkcja tożsamości?

145

Chciałbym wskazać funkcję, która nic nie robi:

def identity(*args)
    return args

mój przypadek użycia jest podobny do tego

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

Oczywiście mógłbym skorzystać ze identityzdefiniowanego powyżej, ale wbudowany z pewnością działałby szybciej (i unikałby błędów wprowadzonych przeze mnie).

Najwyraźniej mapi filterużywaj Nonedla tożsamości, ale jest to specyficzne dla ich implementacji.

>>> _=None
>>> _("hello")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
rds
źródło
6
Co masz na myśli map and filter use None for the identity?
Matt Fenwick,
15
@MattFenwick:map(None, [1, 2, 3])
Greg Hewgill
6
Sprawdź zwracaną wartość. Twoja zmienna args będzie sekwencją (w tym scenariuszu) jednej wartości, więc pomiń gwiazdkę w deklaracji lub rozpakuj ją przed zwróceniem.
Dirk,
11
@GregHewgill: Niestety, to nie działa w Pythonie 3.x.
Ethan Furman,
6
@GregHewgill My bad. Wziąłem to od doktora po googlowaniu. Ale dokument Python2.x zawsze jest na pierwszym miejscu ...
rds

Odpowiedzi:

99

Robiąc więcej badań, nie ma żadnych, zapytano o funkcję w numerze 1673203 A Raymond Hettinger powiedział, że nie będzie :

Lepiej pozwolić ludziom pisać własne, trywialne przejścia i myśleć o podpisie i kosztach czasu.

Więc lepszym sposobem na zrobienie tego jest faktycznie (lambda pozwala uniknąć nazywania funkcji):

_ = lambda *args: args
  • zaleta: przyjmuje dowolną liczbę parametrów
  • Wada: wynikiem jest pudełkowa wersja parametrów

LUB

_ = lambda x: x
  • zaleta: nie zmienia typu parametru
  • wada: przyjmuje dokładnie 1 parametr pozycyjny
rds
źródło
13
Zauważ, że nie jest to funkcja tożsamości.
Marcin
1
@Marcin Dzięki za uwagę. Dodałem zalety / wady tych dwóch, aby nikogo nie wprowadzać w błąd. A teraz naprawdę wierzę, że powinna istnieć wbudowana funkcja, która akceptuje dowolną liczbę parametrów i jest prawdziwą tożsamością :)
rds
7
Niezła odpowiedź. Co jednak zwracałaby funkcja prawdziwej tożsamości, pobierając wiele parametrów?
Marcin
5
@Marcin: Ani też, po prostu postępując zgodnie z tym, o co zadał w swoim pytaniu.
Ethan Furman,
4
Tak, dzięki, mam trywialną lambda x: xfunkcję tożsamości, która działa dla jednego parametru ciągu. @Marcin Szkoda, że ​​nie mogę lambda *args: *args:-)
rds
28

Funkcja tożsamości, zgodnie z definicją podaną w https://en.wikipedia.org/wiki/Identity_function , pobiera pojedynczy argument i zwraca go w niezmienionej postaci:

def identity(x):
    return x

To, o co prosisz, mówiąc, że chcesz, aby podpis, niedef identity(*args) jest ściśle funkcją tożsamości, ponieważ chcesz, aby przyjmowała wiele argumentów. W porządku, ale potem napotykasz problem, ponieważ funkcje Pythona nie zwracają wielu wyników, więc musisz znaleźć sposób na upchnięcie wszystkich tych argumentów w jedną zwracaną wartość.

Zwykłym sposobem zwracania „wielu wartości” w Pythonie jest zwrócenie krotki wartości - technicznie rzecz biorąc, jest to jedna zwracana wartość, ale można jej używać w większości kontekstów tak, jakby była to wiele wartości. Ale zrobienie tego tutaj oznacza, że ​​otrzymasz

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

Szybkie rozwiązanie tego problemu daje inne problemy, jak pokazały różne odpowiedzi.

Podsumowując, w Pythonie nie ma zdefiniowanej funkcji tożsamości, ponieważ:

  1. Definicja formalna (funkcja z jednym argumentem) nie jest tak użyteczna i jest prosta do napisania.
  2. Rozszerzenie definicji na wiele argumentów nie jest ogólnie dobrze zdefiniowane, a znacznie lepiej jest zdefiniować własną wersję, która działa tak, jak tego potrzebujesz w konkretnej sytuacji.

Dla Twojego konkretnego przypadku,

def dummy_gettext(message):
    return message

jest prawie na pewno tym, czego potrzebujesz - funkcją, która ma taką samą konwencję wywoływania i zwraca jak gettext.gettext, która zwraca swój argument niezmieniony i jest wyraźnie nazwana, aby opisać, co robi i gdzie ma być używana. Byłbym zszokowany, gdyby wydajność była tutaj kluczowym czynnikiem.

Paul Moore
źródło
Nie wiem, do jakich odpowiedzi odnosisz się, mówiąc „naprawienie tego problemu daje inne problemy, jak pokazały odpowiedzi”. W szczególności wystarczy użyć id= lambda *args: args if len(args)>1 else args[0].
Maksymalnie
21

twój będzie działał dobrze. Gdy liczba parametrów jest ustalona, ​​możesz użyć funkcji anonimowej, takiej jak ta:

lambda x: x
tback
źródło
8
Można to zrobić z varargs też: lambda *args: args. To naprawdę stylistyczny wybór.
Drugie podoba mi się bardziej, ponieważ wymaga dowolnej liczby argumentów.
rds
4
@delnan @rds - *argswersja ma inny typ zwracania, więc nie są one równoważne nawet w przypadku pojedynczego argumentu.
Marcin
8
@delnan: Powiedziałeś, że jest to wybór stylistyczny, co błędnie sugeruje, że nie ma różnicy w semantyce tych dwóch form.
Marcin
1
@Marcin: Szkoda, że ​​to zasugerowałem. Miałem na myśli wybór między defi lambdadla takich prostych funkcji.
7

W Pythonie nie ma wbudowanej funkcji tożsamości. Imitacja Haskell jest idfunkcją byłoby:

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

Przykładowe użycie:

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

Ponieważ identitynie robi nic poza zwracaniem podanych argumentów, nie sądzę, aby było to wolniejsze niż natywna implementacja.

SergiyKolesnikov
źródło
To sama konstrukcja połączenia wymaga czasu, niezależnie od tego, co zrobisz po zakończeniu konfiguracji.
chepner
@chepner Czy mógłbyś bardziej szczegółowo wyjaśnić, co masz na myśli? Należy również skonstruować wywołanie funkcji natywnej, prawda? Czy ta konstrukcja jest wykonywana szybciej niż konstrukcja wywołania funkcji innej niż natywna?
SergiyKolesnikov
1
Wywołanie funkcji zdefiniowanej przez użytkownika jest co najmniej tak kosztowne, jak wywołanie funkcji wbudowanej, a prawdopodobnie bardziej, ponieważ po wywołaniu funkcji zdefiniowanej przez użytkownika cokolwiek innego może wywołać więcej funkcji zdefiniowanych przez użytkownika lub wbudowanych w funkcjach.
chepner
6

Nie, nie ma.

Zwróć uwagę, że identity:

  1. jest równoważne lambda * args: args
  2. Będzie przechowywać swoje argumenty - tj

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

Więc możesz chcieć użyć, lambda arg: argjeśli chcesz prawdziwej funkcji tożsamości.

NB: Ten przykład będzie przesłaniał wbudowaną idfunkcję (której prawdopodobnie nigdy nie użyjesz).

Marcin
źródło
1
Zauważ, że id jest funkcją wbudowaną i ten fragment kodu ją zastąpi.
Arnie97
@ Arnie97 Fair! Zapomniałem oid
Marcin
4

Jeśli prędkość nie ma znaczenia, powinno to obsługiwać wszystkie przypadki:

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

Przykłady użycia:

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
źródło
1

Skrót funkcji jednoargumentowej

gettext.gettext(przypadek Przykład Opisany OP) przyjmuje jeden argument message. Jeśli potrzebujesz do tego odgałęzienia, nie ma powodu, aby zwracać [message]zamiast message( def identity(*args): return args). Tak więc obie

_ = lambda message: message

def _(message):
    return message

pasuje idealnie.

... ale wbudowany z pewnością działałby szybciej (i unikałby błędów wprowadzonych przeze mnie).

Błędy w tak trywialnym przypadku są mało istotne. Dla argumentu predefiniowanego typu, powiedzmy str, możemy użyć str()samego siebie jako funkcji tożsamości (z powodu internowania ciągu znaków zachowuje nawet tożsamość obiektu, patrz iduwaga poniżej) i porównać jego wydajność z rozwiązaniem 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

Możliwa jest mikro-optymalizacja. Na przykład następujący kod Cython :

test.pyx

cpdef str f(str message):
    return message

Następnie:

$ 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

Wbudowana funkcja tożsamości obiektu

Nie myl funkcji tożsamości z funkcją idwbudowaną, która zwraca „tożsamość” obiektu (co oznacza unikalny identyfikator dla tego konkretnego obiektu, a nie wartość tego obiektu w porównaniu z ==operatorem), jego adres pamięci w CPythonie.

saaj
źródło
Przyspieszenie 40% „nie wydaje się tego warte”? W przypadkach, gdy tożsamość działa jako „filtr domyślny” dla funkcji, która działa, powiedzmy, raz na kanał na obrazie 10 000x10 000 pikseli (być może nie codziennie, ale z pewnością nie jest rzadkością), jest to różnica między 25 a 9 sekundy czasu wykonania! Mimo wszystko dziękuję za przykład Cythona.
9999 lat
@ 9999lat Zgadzam się. Usunąłem komentarz godności. Dziękuję również za poprawienie odpowiedzi. Wprowadziłem kilka drobnych zmian oprócz Twoich.
saaj
Jeśli masz obraz o rozdzielczości 10 000 x 10 000 pikseli, zdecydowanie zalecam użycie operacji wektoryzacji za pomocą czegoś takiego jak numpy. Będzie znacznie szybszy, zużyje o wiele mniej pamięci i nie będzie wymagał pisania kodu Cython.
anthonybell,
-2

Wątek jest dość stary. Ale nadal chciałem to opublikować.

Możliwe jest zbudowanie metody tożsamości zarówno dla argumentów, jak i obiektów. W poniższym przykładzie ObjOut jest tożsamością dla ObjIn. Wszystkie inne przykłady powyżej nie zajmowały się 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
źródło
wygląda to na odniesienie, jeśli tak, to skąd pochodzi?
Jeff Puckett
@JeffPuckettII Nie odpowiedziałem na twoje pytanie. Pytasz, czy nowy obiekt jest odniesieniem?
Sud
użyłeś podświetlenia w postaci cytatu blokowego dla „Możliwe jest zbudowanie tożsamości ...”, co oznacza odniesienie z innego źródła. Jeśli to są twoje własne słowa, proponuję nie podkreślać tego jako cytatu. naprawdę nic wielkiego. ale jeśli jest to cytat z innego źródła, należy dołączyć do niego odniesienie.
Jeff Puckett
Jak można odpowiedzieć na oryginalne pytanie map(identity, [1, 2, 3])powraca [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())-> Wynik: ([1, 2, 3],)
Sud