Python中是否有内置的身份函数?

184

我想指向一个什么也不做的函数:

def identity(*args)
    return args

我的使用场景类似于这样

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

当然,我可以使用上面定义的identity,但内置函数肯定会更快(而且避免了由自己引入的错误)。

显然,mapfilter使用None作为identity,但这是特定于它们的实现。

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

8
“map和filter使用None作为标识符”是什么意思? - Matt Fenwick
20
map(None, [1, 2, 3]) 的翻译是将函数 map() 应用于参数 [1, 2, 3],其中第一个参数为 None - Greg Hewgill
6
请检查返回值。在这种情况下,您的args变量将成为一个序列,并且只有一个值,因此要么在声明中省略星号,要么在返回之前解包它。 - Dirk
13
很遗憾,在Python 3.x中这不起作用。 - Ethan Furman
6
抱歉,我从谷歌搜索后的文档中获取了这个信息。但Python 2.x文档始终排在第一位... - rds
显示剩余6条评论
10个回答

124

经过进一步的研究,没有这样的功能。在问题1673203中曾经有人提出过这个功能,但来自Raymond Hettinger的说法是不会添加此功能:

最好让用户自己编写简单的“传递函数”并考虑签名和时间成本。

因此,实际上一个更好的方法是(lambda避免了命名函数):

_ = lambda *args: args
  • 优点:可以接受任意数量的参数
  • 缺点:结果是参数的装箱版本

或者

_ = lambda x: x
  • 优点:不会改变参数的类型
  • 缺点:只接受一个位置参数

22
注意,这不是一个恒等函数。 - Marcin
2
@Marcin 谢谢你的评论。我已经添加了两者的优缺点,以免误导任何人。现在,我真的相信应该有一个内置函数,可以接受任意数量的参数,并且是真正的身份证明 :) - rds
10
好的回答。不过,当一个真正的恒等函数接收多个参数时会返回什么? - Marcin
5
@Marcin:没有,只是根据他在问题中提出的内容回答。 - Ethan Furman
4
谢谢,我有一个简单的 lambda x: x 身份函数可以用于一个字符串参数。@Marcin 我希望我能做到 lambda *args: *args :-) - rds
显示剩余7条评论

52

身份函数是指按照https://zh.wikipedia.org/wiki/身份函数的定义,接受一个参数并返回未经修改的参数本身:

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]即可。 - Max
6
在您提出“_=lambda *args: args if len(args)>1 else args[0]”的建议后,如果调用“_((1,2))”(仅使用一个元组参数!),将得到“(1,2)”的结果,与调用“_(1,2)”(使用两个整数参数)得到的结果相同。因此,您的函数不是单射的:您无法从输出中确定输入是什么。这对于一个恒等函数来说是非常不明确的状态。(它应该是双射的,其中包括单射性。) - Peter Thomassen

25

你的代码可以正常工作。当参数数量是固定的时候,你可以使用如下的匿名函数:

lambda x: x

9
你也可以使用可变参数来实现:lambda *args: args。这实际上是一种风格选择。 - user395760
我更喜欢第二个,因为它可以接受任意数量的参数。 - rds
8
@delnan @rds - “*args”版本的返回类型不同,因此即使针对单个参数的情况,它们也不是等价的。 - Marcin
@Marcin:我知道它们并不等价,但两个版本都可以用lambdadef编写。这就是我的全部观点。 - user395760
10
你说这是一种风格上的选择,这会错误地暗示这两种形式在语义上没有区别。 - Marcin
2
@Marcin:如果我给人留下了这样的印象,那真是不幸。我指的是在处理如此简单的函数时,在“def”和“lambda”之间做出选择。 - user395760

13

Python中没有内置的身份函数。一个模仿Haskell的id函数的方法是:

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

使用示例:

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

由于identity除了返回给定的参数之外不会执行任何操作,因此我认为它不会比本地实现更慢。


无论你在设置完成后做什么,都需要时间来构建调用本身。 - chepner
@chepner,您能否详细解释一下您的意思?调用本地函数也必须进行构建,对吗?这种构建比调用非本地函数的构建更快吗? - SergiyKolesnikov
1
调用用户定义的函数至少与调用内置函数一样昂贵,而且可能更昂贵,因为一旦调用了用户定义的函数,它可能会调用更多的用户定义或内置函数。 - chepner
到了2023年4月,没有任何参数调用 identity() 函数将会失败;以下代码将修复该问题:identity = lambda x=None, *args: (x,) + args if args else x - ankostis
@ankostis 这取决于我们如何定义恒等函数。目前实现的方式是,它总是需要一个参数。否则,它是未定义的(即失败)。 - SergiyKolesnikov

7
没有,没有相关内容。
请注意您的身份标识:
  1. is equivalent to lambda *args: args
  2. Will box its args - i.e.

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

如果您想要一个真正的身份函数,可以使用lambda arg: arg

注意:这个例子将会覆盖内置的id函数(您可能永远不会使用它)。


2
请注意,id是一个内置函数,这段代码将覆盖它。 - Arnie97
@Arnie97 好的!我忘记了 id - Marcin

5
如果速度不重要,这种方式应该可以处理所有情况:
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]而不是messagedef identity(*args): return args)。因此两者都可以。

_ = lambda message: message

def _(message):
    return message

完美贴合。

...但内置的肯定会运行得更快(并避免我自己引入的错误)。

在这种微不足道的情况下出现错误几乎无关紧要。对于预定义类型的参数,比如 str,我们可以使用 str() 本身作为一个恒等函数(由于 string interning 它甚至保留对象标识,请参见下面的 id 注释),并将其性能与 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

可以进行微小的优化。例如,以下 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,000x10,000像素图像上每个通道只运行一次(也许不是每天都会发生,但肯定不是罕见的情况),这就是执行时间从25秒缩短到9秒的差异!无论如何,感谢您提供Cython示例。 - 9999years
@9999years 我同意。我已经删除了有关价值的评论。同时感谢您对答案的改进。在您的基础上,我进行了一些微小的修改。 - saaj
如果您有一张10,000x10,000像素的图像,我强烈建议使用类似于numpy的向量化操作。这将会更快,使用更少的内存,并且不需要编写cython代码。 - anthonybell

1

这个主题中有很多好的答案和讨论。我只想指出,在 OP 的情况下,如果在 identity 函数中只有一个参数,那么在编译方面,使用 lambda 还是定义函数并没有区别(在这种情况下,你应该定义函数以保持符合 PEP8 的规范)。字节码在功能上是相同的:

import dis
function_method = compile("def identity(x):\n    return x\ny=identity(Type('x', (), dict()))", "foo", "exec")
dis.dis(function_method)
  1           0 LOAD_CONST               0 (<code object identity at 0x7f52cc30b030, file "foo", line 1>)
              2 LOAD_CONST               1 ('identity')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (identity)

  3           8 LOAD_NAME                0 (identity)
             10 LOAD_NAME                1 (Type)
             12 LOAD_CONST               2 ('x')
             14 LOAD_CONST               3 (())
             16 LOAD_NAME                2 (dict)
             18 CALL_FUNCTION            0
             20 CALL_FUNCTION            3
             22 CALL_FUNCTION            1
             24 STORE_NAME               3 (y)
             26 LOAD_CONST               4 (None)
             28 RETURN_VALUE

Disassembly of <code object identity at 0x7f52cc30b030, file "foo", line 1>:
  2           0 LOAD_FAST                0 (x)
              2 RETURN_VALUE

和 lambda

import dis
lambda_method = compile("identity = lambda x: x\ny=identity(Type('x', (), dict()))", "foo", "exec")
dis.dis(lambda_method)
  1           0 LOAD_CONST               0 (<code object <lambda> at 0x7f52c9fbbd20, file "foo", line 1>)
              2 LOAD_CONST               1 ('<lambda>')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (identity)

  2           8 LOAD_NAME                0 (identity)
             10 LOAD_NAME                1 (Type)
             12 LOAD_CONST               2 ('x')
             14 LOAD_CONST               3 (())
             16 LOAD_NAME                2 (dict)
             18 CALL_FUNCTION            0
             20 CALL_FUNCTION            3
             22 CALL_FUNCTION            1
             24 STORE_NAME               3 (y)
             26 LOAD_CONST               4 (None)
             28 RETURN_VALUE

Disassembly of <code object <lambda> at 0x7f52c9fbbd20, file "foo", line 1>:
  1           0 LOAD_FAST                0 (x)
              2 RETURN_VALUE

0

补充所有答案:

请注意,在Python stdlib中有一个隐含的约定,其中HOF将其key参数函数默认为恒等函数,将None解释为这样。

例如:sortedheapq.mergemaxmin等。

因此,考虑您的HOF期望key遵循相同的模式并不是坏主意。

也就是说,不要这样做:

def my_hof(x, key=lambda _: _):
   ...

这是完全正确的。

你可以写:

def my_hof(x, key=None):
    if key is None: key = lambda _: _
    ...

如果你想的话。


1
提醒一下,我相当确定 key = lambda _: _ if key is None 不是有效的 Python 语法,正确的写法应该是 if key is None: key = lamda _: _ - Harold Cooper
1
@HaroldCooper 你说得对。在使用 if 后缀时需要一个 else。而且 lambda 表达式本身就包含了所有的 if 语句。另一种方法是 key = (lambda _: _) if key is None else None。但我更喜欢你提出的前缀形式。我正在编辑答案。 - jgomo3

-2

这个帖子已经很老了,但我仍然想发表一下我的看法。

可以为参数和对象构建身份方法。在下面的示例中,ObjOut是ObjIn的身份标识。所有其他示例都没有处理字典**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}

这似乎是一个参考资料,如果是的话,那么它来自哪里? - Jeff Puckett
@JeffPuckettII 我没有理解你的问题。你是在问新对象是否是一个引用吗? - Sud
你在“可以建立身份认证…”这句话中使用了引用块的高亮,这暗示了它来自其他来源。如果这是你自己的话,我建议不要将其突出显示为引用。这并不是什么大问题。但如果这是来自其他来源的引用,则应包含对其的引用。 - Jeff Puckett
你如何回答这个编程问题:map(identity, [1, 2, 3]) 返回 [1, 2, 3] - rds
类test1(对象): def __init__(self,* args,** kwargs): self.args = args self.kwargs = kwargs def identity(self): return self.argsprint(test1([1,2,3]).identity())` - >结果:([1,2,3],) - Sud
你的.identity()函数与赋值没有任何区别:objOut = objIn.identity()等同于objOut = objIn。这个线程是关于一个返回给定内容的函数:objIn is identity(objIn)——在这种情况下,kwargs毫无意义。 - Ethan Furman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接