【问题标题】:A Faster Nested Tuple to List and Back一个更快的嵌套元组列表和返回
【发布时间】:2013-03-04 06:52:30
【问题描述】:

我正在尝试对未知深度和形状的嵌套序列执行元组到列表和列表到元组的转换。呼叫已被拨打数十万次,这就是为什么我试图尽可能加快速度。

非常感谢任何帮助。

这是我目前所拥有的......

def listify(self, seq, was, toBe):
  temp = []
  a = temp.append
  for g in seq:
    if type(g) == was:
      a(self.listify(g, was, toBe))
    else:
      a(g)
  return toBe(temp)

对 tuple 的调用如下所示:

self.listify((...), tuple, list)

编辑: 是的,我完全错过了枚举(来自旧实现)并忘记输入 else 部分。

感谢你们俩的帮助。我可能会选择协程。

【问题讨论】:

  • 请问为什么需要这种往返转换?另外,你试过 PyPy 吗?
  • 你应该知道Python中有一个固定的递归限制:docs.python.org/library/sys.html#sys.setrecursionlimit
  • 您应该为每次转换制作专门的函数。这将节省额外的参数传递和测试
  • 这是您的实际代码吗?如果是这样,您似乎不使用pos,因此可以像enumerate 一样摆脱它。
  • 你为什么将它作为某个对象的成员来实现?那不应该只是一个功能吗?此外,您似乎忽略了不属于was 类型的项目。是故意的吗?

标签: python performance list nested tuples


【解决方案1】:

分别定义两个函数:

def list2tuple(a):
    return tuple((list2tuple(x) if isinstance(x, list) else x for x in a))

def tuple2list(a):
    return list((tuple2list(x) if isinstance(x, tuple) else x for x in a))

一些测试:

t = [1, 2, [3, 4], [5, [7, 8]], 9]
t2 = list2tuple(t)
t3 = tuple2list(t2)
print t2
print t3

结果:

(1, 2, (3, 4), (5, (7, 8)), 9)
[1, 2, [3, 4], [5, [7, 8]], 9]

编辑:快速版本:

def list2tuple2(a, tuple=tuple, type=type, list=list):
    return tuple([list2tuple2(x) if type(x)==list else x for x in a])

def tuple2list2(a, tuple=tuple, type=type):
    return [tuple2list2(x) if type(x)==tuple else x for x in a]

为了比较,我还包括 cython 版本:

%%cython

def list2tuple3(a):
    return tuple([list2tuple3(x) if type(x)==list else x for x in a])

def tuple2list3(a):
    return [tuple2list3(x) if type(x)==tuple else x for x in a]

创建一些嵌套列表:

def make_test(m, n):
    return [[range(m), make_test(m, n-1)] for i in range(n)]

t = make_test(20, 8)
t2 = list2tuple2(t)

然后比较一下速度:

%timeit listify(t, list, tuple)
%timeit listify(t2, tuple, list)
%timeit list2tuple(t)
%timeit tuple2list(t2)
%timeit list2tuple2(t)
%timeit tuple2list2(t2)
%timeit list2tuple3(t)
%timeit tuple2list3(t2)

结果是:

listify
1 loops, best of 3: 828 ms per loop
1 loops, best of 3: 912 ms per loop

list2tuple generator expression version
1 loops, best of 3: 1.49 s per loop
1 loops, best of 3: 1.67 s per loop

list2tuple2 list comprehension with local cache
1 loops, best of 3: 623 ms per loop
1 loops, best of 3: 566 ms per loop

list2tuple3 cython
1 loops, best of 3: 212 ms per loop
10 loops, best of 3: 232 ms per loop

【讨论】:

  • 既然这是一个关于性能的问题,你有没有做过计时?
  • 尝试使用tuple2list的列表理解
【解决方案2】:

我最近一直在安静地使用协程。这样做的好处是可以减少方法调用的开销。将新值发送到协程中比调用函数更快。虽然你不能创建递归协程,但它会抛出一个ValueError: generator already executing,但你可以创建一个协程工作池——树的每一层都需要一个工作人员。我已经制作了一些有效的测试代码,但还没有查看时间问题。

def coroutine(func):
    """ A helper function decorator from Beazley"""
    def start(*args, **kwargs):
        g = func(*args, **kwargs)
        g.next()
        return g
    return start

@coroutine
def cotuple2list():
    """This does the work"""
    result = None
    while True:
        (tup, co_pool) = (yield result)
        result = list(tup)
        # I don't like using append. So I am changing the data in place.
        for (i,x) in enumerate(result):
            # consider using "if hasattr(x,'__iter__')"
            if isinstance(x,tuple):
                result[i] = co_pool[0].send((x, co_pool[1:]))


@coroutine
def colist2tuple():
    """This does the work"""
    result = None
    while True:
        (lst, co_pool) = (yield result)
        # I don't like using append so I am changing the data in place...
        for (i,x) in enumerate(lst):
            # consider using "if hasattr(x,'__iter__')"
            if isinstance(x,list):
                lst[i] = co_pool[0].send((x, co_pool[1:]))
        result = tuple(lst)

HYRY 帖子中的纯 python 替代品:

def list2tuple(a):
    return tuple((list2tuple(x) if isinstance(x, list) else x for x in a))
def tuple2list(a):
    return list((tuple2list(x) if isinstance(x, tuple) else x for x in a))

创建一个协同程序池 - 这是一个池的 hack,但它有效:

# Make Coroutine Pools
colist2tuple_pool = [colist2tuple() for i in xrange(20) ]
cotuple2list_pool = [cotuple2list() for i in xrange(20) ]

现在做一些时间安排 - 比较:

def make_test(m, n):
    # Test data function taken from HYRY's post!
    return [[range(m), make_test(m, n-1)] for i in range(n)]
import timeit
t = make_test(20, 8)
%timeit list2tuple(t)
%timeit colist2tuple_pool[0].send((t, colist2tuple_pool[1:]))

结果 - 注意第二行中“s”旁边的“u”:-)

1 loops, best of 3: 1.32 s per loop
1 loops, best of 3: 4.05 us per loop

似乎真的太快了,难以置信。有人知道 timeit 是否适用于协程吗? 这是老式的方式:

tic = time.time()
t1 = colist2tuple_pool[0].send((t, colist2tuple_pool[1:]))
toc = time.time()
print toc - tic

结果:

0.000446081161499

较新版本的 Ipython 和 %timit 会给出警告:

最慢的运行时间是最快的运行时间的 9.04 倍。这可以
表示中间结果被缓存 1000000 个循环,最好 3:每个循环 317 ns

经过进一步调查,python 生成器并不神奇,send 仍然是一个函数调用。我的基于生成器的方法看起来更快的原因是我在列表上进行了就地操作——这导致了更少的函数调用。

我在最近的talk 中写下了所有这些内容并提供了很多额外的细节。

希望这对想要玩发电机的人有所帮助。

【讨论】:

  • 在这篇文章的底部添加了重大更改 - 生成器并没有神奇地更快。有关详细信息,请参阅链接的 ipython 演示文稿。
【解决方案3】:

由于上面的答案不涉及字典值中的元组或列表,我发布了我自己的代码:

def tuple2list(data):
    if isinstance(data, dict):
        return {
            key: tuple2list(value)
            for key, value in data.items()
        }
    elif isinstance(data, (list, tuple)):
        return [
            tuple2list(item)
            for item in data
        ]
    return data

def list2tuple(data):
    if isinstance(data, dict):
        return {
            key: list2tuple(value)
            for key, value in data.items()
        }
    elif isinstance(data, (list, tuple)):
        return tuple(
            list2tuple(item)
            for item in data
        )
    return data

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多