Чи є вбудована функція ідентичності в 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
Перевірте повернене значення. Ваша змінна аргумента буде послідовністю (у цьому сценарії) одного значення, тому або опустіть зірочку в декларації, або розпакуйте її для повернення.
Дірк

11
@GregHewgill: На жаль, це не працює в Python 3.x.
Етан Фурман

6
@GregHewgill Моє погано. Я взяв це у doc після googling. Але доктор Python2.x завжди стає першим ...
rds

Відповіді:


99

Проводячи ще кілька досліджень, їх немає, про особливість задали у випуску 1673203. І від Реймонда Хеттінгера сказали, що не буде :

Краще дозволити людям писати власні тривіальні пропуски та думати про витрати на підпис та час.

Тож кращий спосіб зробити це насправді (лямбда уникає називати функцію):

_ = lambda *args: args
  • перевага: приймає будь-яку кількість параметрів
  • недолік: результат - коробкова версія параметрів

АБО

_ = lambda x: x
  • перевага: не змінює тип параметра
  • недолік: приймає рівно 1 позиційний параметр

13
Зауважте, що це не функція ідентичності.
Марцін

1
@Marcin Дякую за зауваження. Я додав переваги / недоліки цих двох, щоб нікого не вводити в оману. І тепер я дійсно вважаю, що повинна була бути вбудована функція, яка приймає будь-яку кількість параметрів і є справжньою ідентичністю :)
rds

7
Гарна відповідь. Однак, що поверне справжню функцію ідентичності, приймаючи кілька параметрів?
Марцін

5
@Marcin: Ні, просто йдучи тим, що він задав у своєму питанні.
Етан Фурман

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

майже напевно, що ви хочете - функція, яка має таку саму умову виклику та повернення 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
@Marcin: Прикро, якщо я це мав на увазі. Я мав на увазі вибір між 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якщо хочете справжню функцію ідентичності.

Примітка: Цей приклад затінить вбудовану idфункцію (яку, ймовірно, ви ніколи не будете використовувати).


1
Зауважте, що id - це вбудована функція, і цей фрагмент замінить його.
Arnie97

@ Arnie97 Ярмарок! Я забув про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(Приклад випадок використання ФП в) приймає один аргумент, 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х10 000 пікселів (можливо, не щодня, але, звичайно, не рідкість), це різниця між 25 і 9 секунди часу виконання! Незалежно, дякую за приклад Cython.
9999років

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

Якщо у вас є зображення 10,000x10,000 пікселів, я настійно рекомендую використовувати векторизовані операції, використовуючи щось на кшталт numpy. Це буде набагато швидше, використовувати менший об'єм пам'яті, і не вимагати написання коду цитону.
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],)
Суд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.