【问题标题】:Dynamically create a funtion with variable number of statements动态创建具有可变数量语句的函数
【发布时间】:2017-03-12 10:56:04
【问题描述】:

我知道大多数时候不推荐 exec 和 eval,但我想动态创建一个具有可变数量语句的函数。我想这样做的原因是因为每个带有 1 个语句的 3 个函数的性能都比一个带有 3 个语句的 1 个函数的性能差。

基本上我有一些对象,会从输入中读取字段和对象。

对于数量可变的 obj,我想为它们组装一个函数,而不是创建许多手动函数。有没有办法避免 (或者有没有办法生成make_funcN?)

def make_func1( obj1, field1, obj2, field2 ):
  def copy():
    obj1.__setattr__( field1, obj2.__getattribute__( field2 ) )
  return copy

def make_func2( obj1, field1, obj2, field2, \
                obj3, field3, obj4, field4 ):
  def copy():
    obj1.__setattr__( field1, obj2.__getattribute__( field2 ) )
    obj3.__setattr__( field3, obj4.__getattribute__( field4 ) )
  return copy

def make_func3( ... ):
  ...

谢谢,

【问题讨论】:

  • “用 1 个语句生成 3 个函数,每个函数的性能都比一个 1 个函数和 3 个语句的性能差”可能是一个真实的陈述。但是,除非您非常、非常频繁地使用此函数,否则性能差异不太可能产生影响——尤其是在与使用不支持本机宏的语言执行此操作所涉及的额外复杂性相平衡时。
  • 是的,我非常频繁地使用这个功能......
  • 那么,请确保您对替代方法进行基准测试。如果性能真的很重要,那么基于数据的优化要比猜测更快的方法要好得多。特别是当优化版本依赖于元编程并包含自己的函数调用时。

标签: python python-2.7 function


【解决方案1】:

首先,您应该使用内置的getattrsetattr 而不是__setattr____getattribute__;不要为了可读性而牺牲微小的性能提升。

然后,您可以将args 分组,然后相应地设置和获取每对对象的属性:

def make_func(*args):
   if len(args) % 4 != 0:
       raise ValueError("number of arguments must be a multiple of 4")
   def copy():
       for i in range(0, len(args), 4):
           setattr(args[i], args[i+1], getattr(args[i+2], args[i+3]))
   return copy

【讨论】:

  • 我在网上阅读,__getattribute__ 基本上比 getattr 做的工作少。我用timeit做实验,发现__getattribute__比getattr快。
  • 那是因为内置函数会调用幕后的那些,但请记住 Python 不只是微优化。 代码可读性是该语言强烈强调的。
【解决方案2】:

这不是 OP 要求的。我没有创建主要在arity 中不同的一系列相似的属性复制函数,而是提出了一些简单的函数和这个问题的“零假设”:创建多个相似的函数是善意的,但不是有效的优化对于这种情况。

最简单的方法是单个复制函数,迭代次数与要设置的字段一样多:

def copyattr(obj1, field1, obj2, field2):
     setattr(obj1, field1, getattr(obj2, field2))

根据需要多次调用它以设置您喜欢的所有属性。 它很简单,Pythonic,并且基于我运行的基准测试,性能非常好。

如果您想要一个复制指令的平面列表(由您的扩展参数列表建议),那么:

def copyattrs(*args):
    for i in range(0, len(args), 4):
        setattr(args[i], args[i+1], getattr(args[i+2], args[i+3]))

会成功的。它以与以前相同的顺序采用任意长的平面参数序列。除了需要设置步骤 (make_func()) 和通过闭包传输参数外,几个使用嵌入式循环返回 copy() 函数的答案基本上是这样的。

如果您只使用 Python 3,则可以使用扩展的参数解包语法节省几纳秒:

def copyattrs(*args):
    for i in range(0, len(args), 4):
        setattr(*args[i:i+2], getattr(*args[i+2:i+4]))

但我没有尝试过任何变体——包括生成特定数量的复制函数——在这项任务中产生了很大的不同。基于 Python 数据处理语义,这些方法都不会复制大量数据。它们都复制指针,而不是大型数据集,而且几乎任何方式都非常有效。大部分工作都花在参数构造和访问以及函数调用设置/拆卸上,而不是原始数据副本上。我不得不运行大量的副本——从 1,000,000 到 20,000,000——甚至开始花费有趣的时间。即使是 200,000,000 个属性副本的完整基准测试运行也只需要几秒钟。这包括每次将测试状态重置为零所需的完整数据集副本。

长话短说,这种“创建动态属性复制功能”的方法似乎没什么优势。这是一个有用的实验,但我运行的任何基准测试都没有显示出明显更快的结果——特别是如果您考虑(如您应该)创建自定义属性复制器函数所需的时间,然后将参数编组到其中。在内存中生成和保存大量闭包也存在潜在的材料开销,具体取决于它们的使用方式。在我运行的场景中,闭包驱动的无参数copy() 实际上是最差的基准测试。 (对于较大的 arities,速度要慢得多。)

著名的计算机科学家 CAR Hoare 和 Donald Knuth 提出了著名的论点:“过早的优化是万恶之源”。因此,在您进行花哨的优化之前,请确保您知道真正的性能问题在哪里,并且您提出的解决方案实际上是一种改进。 Ned Batchelder nails this:

……“你不知道问题出在哪里。”我不记得有多少次我有一个关于加快速度的好主意,尝试过,但没有成功。

他所指的文章是 Russ Olsen 的 Five Truths about Optimization,是一本可靠的读物,也是加快速度的良好指导。要获得一本精巧的书本指南,请尝试 Jon Bentley 的Writing Efficient Programs

【讨论】:

  • 感谢您对这些基于列表的方法进行比较的回答。正如您所说,它们没有太大区别。我想我一开始的意思是生成一个没有循环“for i in range(0, len(args), 4):”的函数。相反,我想象这将被扁平化为每个函数的几个语句。我进行了实验,在 1 个函数中复制 3 个 var 确实比在 3 个函数中复制 1 个 var 更快。但是你可能是正确的,我不应该求助于 python 来完成这个性能关键的任务
  • 我通过随机生成的数据测试了数十亿次迭代。做这种loop unrolling有区别吗?是的。这是一场大胜利吗?那种会让你的程序整体更快的事情?不。事实上,一些展开(当与闭包一起使用时,情况要糟糕得多)。但一如既往,YMMV。
【解决方案3】:

您可以将对象和字段作为列表或元组传递:

def make_func(to_objs, to_fields, from_objs, from_fields):
    def copy():
        for to_obj, to_field, from_obj, from_field in zip(to_obj, to_fields, 
                                                          from_objs, from_fields):
            to_obj.__setattr__(to_field, from_obj.__getattribute__(from_field))
    return copy

【讨论】:

    【解决方案4】:
    def make_func1(*args):
        assert len(args) % 4 == 0
    
        def copy():
            for i in range(0, len(args), 4):
                obj1, field1, obj2, field2 = args[i:i + 4]
                obj1.__setattr__(field1, obj2.__getattribute__(field2))
    
        return copy
    

    【讨论】:

    • @MosesKoledoye 谢谢,想错了,更正了范围。
    猜你喜欢
    • 1970-01-01
    • 2019-12-17
    • 2012-01-13
    • 1970-01-01
    • 1970-01-01
    • 2012-11-25
    • 1970-01-01
    • 2013-08-26
    相关资源
    最近更新 更多