Есть ли встроенная функция идентичности в 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
tback
источник
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ничего не делает, кроме возврата заданных аргументов, я не думаю, что это медленнее, чем была бы нативная реализация.

SergiyKolesnikov
источник
Само построение вызова требует времени, независимо от того, что вы делаете после завершения этой настройки.
Чепнер
@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функцию (которую вы, вероятно, никогда не будете использовать).

Marcin
источник
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.

SAAJ
источник
Ускорение 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}
Sud
источник
это похоже на ссылку, если так, то откуда она?
Джефф Пукетт
@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