Есть ли встроенная функция идентичности в Python?


145

Я хотел бы указать на функцию, которая ничего не делает:

def identity(*args)
    return args

мой случай использования что-то вроде этого

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

Конечно, я мог бы использовать identityвышеописанное, но встроенный мог бы работать быстрее (и избежать ошибок, связанных с моими).

Видимо, mapи filterиспользуют Noneдля идентификации, но это специфично для их реализации.

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

6
Что вы имеете в виду map and filter use None for the identity?
— Мэтт Фенвик

15
@MattFenwick:map(None, [1, 2, 3])
— Грег Хьюгилл

6
Проверьте возвращаемое значение. Ваша переменная args будет последовательностью (в этом сценарии) одного значения, поэтому либо пропустите звездочку в объявлении, либо распакуйте ее перед возвратом.
— Дирк

11
@GregHewgill: К сожалению, это не работает в Python 3.x.
— Итан Фурман

6
@GregHewgill Мой плохой. Я взял это из документа после поиска в Google. Но документ Python2.x всегда приходит первым ...
— Rds

Ответы:


99

Если провести еще какое-то исследование, то ничего не было, вопрос был задан в выпуске 1673203, а Рэймонд Хеттингер сказал, что не будет :

Лучше позволить людям писать свои собственные тривиальные проходы и думать о подписи и временных затратах.

Таким образом, лучший способ сделать это на самом деле (лямбда-код избегает именования функции):

_ = lambda *args: args
  • преимущество: принимает любое количество параметров
  • недостаток: в результате получается коробочная версия параметров

ИЛИ

_ = lambda x: x
  • преимущество: не меняет тип параметра
  • недостаток: принимает ровно 1 позиционный параметр

13
Обратите внимание, что это не функция идентичности.
— Марцин

1
@ Марчин Спасибо за замечание. Я добавил преимущества / недостатки обоих, чтобы никого не вводить в заблуждение. И теперь, я действительно считаю, что должна была быть встроенная функция, которая принимает любое количество параметров и является истинной идентичностью :)
— rds

7
Хороший ответ. Однако что будет возвращать истинная функция тождества при получении нескольких параметров?
— Марцин

5
@ Марчин: Ни то, ни другое, просто исходя из того, что он спросил в своем вопросе.
— Итан Фурман

4
Да, спасибо, у меня есть тривиальная lambda x: xфункция идентичности, которая работает для одного строкового параметра. @Marcin Я хотел бы сделать lambda *args: *args:-)
— Rds

28

Функция идентификации, как определено в https://en.wikipedia.org/wiki/Identity_function , принимает один аргумент и возвращает его без изменений:

def identity(x):
    return x

То, о чем вы просите, когда говорите, что хотите подпись def identity(*args), не является строго идентичной функцией, поскольку вы хотите, чтобы она принимала несколько аргументов. Это нормально, но затем вы сталкиваетесь с проблемой, поскольку функции Python не возвращают несколько результатов, поэтому вы должны найти способ объединить все эти аргументы в одно возвращаемое значение.

Обычный способ возврата «множественных значений» в Python - это возврат кортежа значений - технически это одно возвращаемое значение, но его можно использовать в большинстве контекстов, как если бы это были множественные значения. Но, делая это здесь, вы получаете

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

И решение этой проблемы быстро дает другие проблемы, как показали различные ответы здесь.

Итак, в итоге, в Python не определена функция идентификации, потому что:

  1. Формальное определение (функция с одним аргументом) не так уж полезно, и написать его тривиально.
  2. Расширение определения до нескольких аргументов в целом не является четко определенным, и вам гораздо лучше определить свою собственную версию, которая работает так, как вам нужно для вашей конкретной ситуации.

Для вашего точного случая,

def dummy_gettext(message):
    return message

почти наверняка то, что вы хотите - функция, которая имеет то же соглашение о вызовах и return gettext.gettext, что и возвращает свой аргумент без изменений и имеет четкие имена для описания того, что она делает и где она предназначена для использования. Я был бы очень шокирован, если бы производительность была решающим фактором здесь.


Я не понимаю, к каким ответам вы относитесь, «решение этой проблемы дает другие проблемы, как показали ответы». Именно этого достаточно для использования id= lambda *args: args if len(args)>1 else args[0].
— Макс

21

у тебя будет нормально работать. Когда число параметров фиксировано, вы можете использовать анонимную функцию, например:

lambda x: x

8
Вы можете сделать это с переменным числом аргументов тоже: lambda *args: args. Это действительно стилистический выбор.

Мне больше нравится второй, так как он принимает любое количество аргументов.
— Rds

4
@delnan @rds - *argsверсия имеет другой тип возвращаемого значения, поэтому они не эквивалентны даже для случая с одним аргументом.
— Марцин

8
@delnan: Вы сказали, что это стилистический выбор, который неверно подразумевает, что нет никакой разницы в семантике двух форм.
— Марцин

1
@ Марчин: К сожалению, если я это намекаю. Я имел ввиду выбор между defи lambdaдля таких простых функций.

7

В Python нет встроенной функции идентификации. Имитация Хаскеля idфункции будет выглядеть так :

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

Пример использования:

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

Поскольку identityничего не делает, кроме возврата заданных аргументов, я не думаю, что это медленнее, чем была бы нативная реализация.


Само построение вызова требует времени, независимо от того, что вы делаете после завершения этой настройки.
— Чепнер

@chepner Не могли бы вы объяснить более подробно, что вы имеете в виду? Вызов нативной функции тоже должен быть построен, верно? Эта конструкция выполняется быстрее, чем конструкция вызова не нативной функции?
— СергейКолесников

1
Вызов пользовательской функции является, по меньшей мере, таким же дорогим, как и вызов встроенной функции, и, вероятно, тем более, потому что, как только вы вызвали пользовательскую функцию, все остальное может вызывать больше пользовательских или встроенных функций. в функции.
— Чепнер

6

Нет, нет

Обратите внимание, что ваш identity:

  1. эквивалентно лямбда * args: args
  2. Буду боксировать свои арги - т.е.

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

Итак, вы можете использовать, lambda arg: argесли вы хотите истинную функцию идентичности.

NB. Этот пример будет скрывать встроенную idфункцию (которую вы, вероятно, никогда не будете использовать).


1
Обратите внимание, что id - это встроенная функция, и этот фрагмент перезапишет ее.
— Arnie97

@ Arnie97 Fair! Я забыл оid
— Марцин

4

Если скорость не имеет значения, это должно обрабатывать все случаи:

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

Примеры использования:

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

Заглушка функции с одним аргументом

gettext.gettext(пример использования OP) принимает один аргумент message. Если для этого нужна заглушка, нет смысла возвращаться [message]вместо message( def identity(*args): return args). Таким образом, оба

_ = lambda message: message

def _(message):
    return message

подходит идеально.

... но встроенный, конечно, будет работать быстрее (и избежать ошибок, введенных мной).

Ошибки в таком тривиальном случае едва ли актуальны. Например, для аргумента предопределенного типа strмы можем использовать str()себя в качестве функции тождественности (из-за интернирования строк он даже сохраняет тождество объекта, см. idПримечание ниже) и сравнивать его производительность с лямбда-решением:

$ 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

Возможна микрооптимизация. Например, следующий код Cython :

test.pyx

cpdef str f(str message):
    return message

Затем:

$ 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

Встроенная функция идентификации объекта

Не путайте функцию тождественности со idвстроенной функцией, которая возвращает «тождество» объекта (то есть уникальный идентификатор для этого конкретного объекта, а не значение этого объекта по сравнению с ==оператором), его адрес памяти в CPython.


Ускорение 40% "не кажется слишком стоящим"? В тех случаях, когда идентификатор работает как «фильтр по умолчанию» для функции, которая запускается, скажем, один раз на канал на изображении с разрешением 10 000 x 100 000 (возможно, не каждый день, но, конечно, не редко), это разница между 25 и 9 секунды выполнения времени! Независимо от того, спасибо за пример Cython.
— 9999лет

@ 9999лет я согласен. Я удалил комментарий достоинства. Также спасибо за улучшение ответа. Я сделал несколько небольших изменений в дополнение к вашему.
— Saaj

Если у вас есть изображение размером 10 000x10 000 пикселей, я настоятельно рекомендую использовать векторизованные операции с использованием чего-то вроде numpy. Это будет намного быстрее, будет использовать меньше памяти и не потребует написания кода на Cython.
— Anthonybell

-2

Нить довольно старая. Но все же хотел опубликовать это.

Можно создать метод идентификации для аргументов и объектов. В приведенном ниже примере ObjOut является идентификатором ObjIn. Все остальные примеры выше не имели отношения к 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}

это похоже на ссылку, если так, то откуда она?
— Джефф Пукетт

@JeffPuckettII Я не поняла твой вопрос. Вы спрашиваете, является ли новый объект ссылкой?
— Суд

Вы использовали выделение цитаты для «Можно создать личность ...», что подразумевает ссылку из другого источника. Если это ваши собственные слова, то я бы предложил не выделять это как цитату. на самом деле не имеет большого значения. но если это цитата из другого источника, то вы должны включить ссылку на нее.
— Джефф Пукетт

Как вы ответите на первоначальный вопрос map(identity, [1, 2, 3])возвращается [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())-> Результат: ([1, 2, 3],)
— Sud
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.