【问题标题】:Is there a builtin identity function in python?python中有内置的身份函数吗?
【发布时间】:2012-02-03 14:29:51
【问题描述】:

我想指出一个什么都不做的函数:

def identity(*args)
    return args

我的用例是这样的

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

当然,我可以使用上面定义的identity,但是内置的肯定会运行得更快(并且避免我自己引入的错误)​​。

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

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

【问题讨论】:

  • map and filter use None for the identity 是什么意思?
  • @MattFenwick: map(None, [1, 2, 3])
  • 查看返回值。您的 args 变量将是(在这种情况下)一个值的序列,因此要么在声明中省略星号,要么在返回之前将其解包。
  • @GregHewgill:遗憾的是,这在 Python 3.x 中不起作用。
  • @GregHewgill 我的错。我在谷歌搜索后从文档中获取了它。但是 Python2.x 文档总是排在第一位的……

标签: python python-3.x python-2.7


【解决方案1】:

做一些更多的研究,没有,在issue 1673203Raymond Hettinger said there won't be 中询问了一个功能:

最好让人们编写自己的琐碎传递 并考虑签名和时间成本。

所以实际上更好的方法是(lambda 避免命名函数):

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

_ = lambda x: x
  • 优点:不改变参数的类型
  • 缺点:只需要 1 个位置参数

【讨论】:

  • 请注意,这不是恒等函数。
  • @Marcin 感谢您的评论。为了不误导任何人,我添加了两者的优点/缺点。现在,我真的相信应该有一个内置函数可以接受任意数量的参数并且是一个真实的身份:)
  • 不错的答案。但是,当采用多个参数时,真正的恒等函数会返回什么?
  • @Marcin:两者都没有,只是按照他在问题中提出的问题。
  • 是的,谢谢,我有一个简单的lambda x: x 标识函数,它适用于一个字符串参数。 @Marcin 我希望我能做到lambda *args: *args :-)
【解决方案2】:

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)&gt;1 else args[0]就足够了。
  • @Max,根据您的_ = lambda *args: args if len(args)&gt;1 else args[0] 提议,调用_((1,2))(带有一个元组参数!)将产生(1,2),这与调用_(1,2)(带有两个整数)的结果相同论据)。因此,您的函数不是单射的:您无法从输出中分辨出输入是什么。对于身份函数来说,这是一个非常不明确的事态。 (应该是双射的,包括单射性。)
【解决方案3】:

你的会很好用。当参数数量固定后,您可以使用这样的匿名函数:

lambda x: x

【讨论】:

  • 您也可以使用可变参数来执行此操作:lambda *args: args。这确实是一种风格选择。
  • 我更喜欢第二种,因为它可以接受任意数量的参数。
  • @delnan @rds - *args 版本具有不同的返回类型,因此即使对于单参数情况,它们也不等价。
  • @delnan:你说这是一种风格选择,这错误地暗示了两种形式的语义没有区别。
  • @Marcin:如果我暗示了这一点,那就太不幸了。我的意思是在 deflambda 之间选择这样简单的功能。
【解决方案4】:

Python 中没有内置的标识函数。 Haskell's id function 的模仿是:

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

示例用法:

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

由于identity 除了返回给定的参数之外什么都不做,我认为它不会比原生实现慢。

【讨论】:

  • 调用本身的构建需要时间,无论您在设置完成后做什么。
  • @chepner 你能更详细地解释一下你的意思吗?也必须构造对本机函数的调用,对吗?这种构造是否比对非本地函数的调用构造更快?
  • 对用户定义函数的调用至少与对内置函数的调用一样昂贵,而且可能更是如此,因为一旦你调用了用户定义函数,它可能发生的任何其他事情调用更多用户定义或内置函数。
【解决方案5】:

不,没有。

注意你的identity:

  1. 等价于 lambda *args: args
  2. 将其 args 装箱 - 即

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

所以,如果你想要一个真实的身份功能,你可能想使用lambda arg: arg

注意:此示例将隐藏内置的 id 函数(您可能永远不会使用)。

【讨论】:

  • 注意id是内置函数,这个sn-p会覆盖掉。
  • @Arnie97 公平!我忘了id
【解决方案6】:

如果速度无关紧要,这应该可以处理所有情况:

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)

【讨论】:

    【解决方案7】:

    单参数函数的存根

    gettext.gettext(OP 的示例用例)接受单个参数 message。如果需要一个存根,没有理由返回[message] 而不是message (def 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 我同意。我已经删除了价值评论。也感谢您改进答案。我在你的基础上做了一些小改动。
    • 如果您有一个 10,000x10,000 像素的图像,那么我强烈建议您使用 numpy 之类的矢量化操作。它会更快,使用更少的内存,并且不需要编写 cython 代码。
    【解决方案8】:

    添加到所有答案:

    请注意,Python stdlib 中有一个隐式约定,其中 HOF 将其 key 参数函数默认为标识函数,将 None 解释为这样。

    例如sortedheapq.mergemaxmin

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

    也就是说,而不是:

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

    (这是完全正确的)

    你可以写:

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

    如果你愿意。

    【讨论】:

      【解决方案9】:

      线程很旧。但还是想发这个。

      可以为参数和对象构建一个标识方法。在下面的示例中,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]
      • 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],)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多