深度复制嵌套可迭代(或改进的 itertools.tee 用于可迭代的可迭代)

2024-03-13

Preface

我有一个测试,我正在使用嵌套迭代(通过嵌套迭代我的意思是仅可迭代作为元素)。

作为测试级联考虑

from itertools import tee
from typing import (Any,
                    Iterable)


def foo(nested_iterable: Iterable[Iterable[Any]]) -> Any:
    ...


def test_foo(nested_iterable: Iterable[Iterable[Any]]) -> None:
    original, target = tee(nested_iterable)  # this doesn't copy iterators elements

    result = foo(target)

    assert is_contract_satisfied(result, original)


def is_contract_satisfied(result: Any,
                          original: Iterable[Iterable[Any]]) -> bool:
    ...

E.g. foo可能是简单的恒等函数

def foo(nested_iterable: Iterable[Iterable[Any]]) -> Iterable[Iterable[Any]]:
    return nested_iterable

契约只是检查扁平化的迭代是否具有相同的元素

from itertools import (chain,
                       starmap,
                       zip_longest)
from operator import eq
...
flatten = chain.from_iterable


def is_contract_satisfied(result: Iterable[Iterable[Any]],
                          original: Iterable[Iterable[Any]]) -> bool:
    return all(starmap(eq,
                       zip_longest(flatten(result), flatten(original),
                                   # we're assuming that ``object()``
                                   # will create some unique object
                                   # not presented in any of arguments
                                   fillvalue=object())))

但如果其中一些nested_iterableelements 是一个迭代器,它可能会被耗尽,因为tee正在制作浅拷贝,而不是深拷贝,即对于给定的foo and is_contract_satisfied下一个声明

>>> test_foo([iter(range(10))])

导致可预测的

Traceback (most recent call last):
  ...
    test_foo([iter(range(10))])
  File "...", line 19, in test_foo
    assert is_contract_satisfied(result, original)
AssertionError

Problem

如何深度复制任意嵌套的可迭代对象?

Note

我知道copy.deepcopy功能 https://docs.python.org/3/library/copy.html#copy.deepcopy,但它不适用于文件对象。


朴素的解决方案

简单的算法是

  1. 对原始嵌套可迭代执行按元素复制。
  2. Make n元素复制的副本。
  3. 获取与每个独立副本相关的坐标。

这可以像这样实现

from itertools import tee
from operator import itemgetter
from typing import (Any,
                    Iterable,
                    Tuple,
                    TypeVar)

Domain = TypeVar('Domain')


def copy_nested_iterable(nested_iterable: Iterable[Iterable[Domain]],
                         *,
                         count: int = 2
                         ) -> Tuple[Iterable[Iterable[Domain]], ...]:
    def shallow_copy(iterable: Iterable[Domain]) -> Tuple[Iterable[Domain], ...]:
        return tee(iterable, count)

    copies = shallow_copy(map(shallow_copy, nested_iterable))
    return tuple(map(itemgetter(index), iterables)
                 for index, iterables in enumerate(copies))

Pros:

  • 很容易阅读和解释。

Cons:

  • 如果我们想以更高的嵌套级别扩展我们的可迭代方法(例如嵌套可迭代的可迭代等等),那么这种方法看起来没有帮助。

我们可以做得更好。

改进的解决方案

如果我们看一下itertools.tee功能文档 https://docs.python.org/3/library/itertools.html#itertools.tee,它包含Python配方,在以下帮助下functools.singledispatch装饰者 https://docs.python.org/3/library/functools.html#functools.singledispatch可以重写为

from collections import (abc,
                         deque)
from functools import singledispatch
from itertools import repeat
from typing import (Iterable,
                    Tuple,
                    TypeVar)

Domain = TypeVar('Domain')


@functools.singledispatch
def copy(object_: Domain,
         *,
         count: int) -> Iterable[Domain]:
    raise TypeError('Unsupported object type: {type}.'
                    .format(type=type(object_)))

# handle general case
@copy.register(object)
# immutable strings represent a special kind of iterables
# that can be copied by simply repeating
@copy.register(bytes)
@copy.register(str)
# mappings cannot be copied as other iterables
# since they are iterable only by key
@copy.register(abc.Mapping)
def copy_object(object_: Domain,
                *,
                count: int) -> Iterable[Domain]:
    return itertools.repeat(object_, count)


@copy.register(abc.Iterable)
def copy_iterable(object_: Iterable[Domain],
                  *,
                  count: int = 2) -> Tuple[Iterable[Domain], ...]:
    iterator = iter(object_)
    # we are using `itertools.repeat` instead of `range` here
    # due to efficiency of the former
    # more info at
    # https://stackoverflow.com/questions/9059173/what-is-the-purpose-in-pythons-itertools-repeat/9098860#9098860
    queues = [deque() for _ in repeat(None, count)]

    def replica(queue: deque) -> Iterable[Domain]:
        while True:
            if not queue:
                try:
                    element = next(iterator)
                except StopIteration:
                    return
                element_copies = copy(element,
                                           count=count)
                for sub_queue, element_copy in zip(queues, element_copies):
                    sub_queue.append(element_copy)
            yield queue.popleft()

    return tuple(replica(queue) for queue in queues)

Pros:

  • 处理更深层次的嵌套,甚至是同一级别上的可迭代元素和不可迭代元素等混合元素,
  • 可以扩展为用户定义的结构(例如,用于制作它们的独立深层副本)。

Cons:

  • 可读性较差(但据我们所知“实用性胜过纯粹性” https://www.python.org/dev/peps/pep-0020/#the-zen-of-python),
  • 提供了一些与调度相关的开销(但没关系,因为它基于字典查找,其中有O(1)复杂)。

Test

准备

让我们定义嵌套迭代如下

nested_iterable = [range(10 ** index) for index in range(1, 7)]

由于迭代器的创建没有提及底层副本的性能,因此让我们定义迭代器耗尽的函数(描述here https://mail.python.org/pipermail/python-ideas/2013-September/023488.html)

exhaust_iterable = deque(maxlen=0).extend

Time

Using timeit package

import timeit

def naive(): exhaust_iterable(copy_nested_iterable(nested_iterable))

def improved(): exhaust_iterable(copy_iterable(nested_iterable))

print('naive approach:', min(timeit.repeat(naive)))
print('improved approach:', min(timeit.repeat(improved)))

我的笔记本电脑运行 Windows 10 x64,运行 Python 3.5.4

naive approach: 5.1863865
improved approach: 3.5602296000000013

Memory

Using memory_profiler package https://pypi.org/project/memory-profiler/

Line #    Mem usage    Increment   Line Contents
================================================
    78     17.2 MiB     17.2 MiB   @profile
    79                             def profile_memory(nested_iterable: Iterable[Iterable[Any]]) -> None:
    80     68.6 MiB     51.4 MiB       result = list(flatten(flatten(copy_nested_iterable(nested_iterable))))

对于“天真的”方法和

Line #    Mem usage    Increment   Line Contents
================================================
    78     17.2 MiB     17.2 MiB   @profile
    79                             def profile_memory(nested_iterable: Iterable[Iterable[Any]]) -> None:
    80     68.7 MiB     51.4 MiB       result = list(flatten(flatten(copy_iterable(nested_iterable))))

为“改进”之一。

Note:我已经运行了不同的脚本,因为立即运行它们不会具有代表性,因为第二条语句将重用之前在后台创建的内容int对象。


结论

正如我们所看到的,这两个函数具有相似的性能,但最后一个函数支持更深层次的嵌套,并且看起来非常可扩展。

广告

我添加了“改进”的解决方案lz package https://pypi.org/project/lz from 0.4.0可以像这样使用的版本

>>> from lz.replication import replicate
>>> iterable = iter(range(5))
>>> list(map(list, replicate(iterable,
                             count=3)))
[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]

它是基于属性的测试,使用hypothesis框架 https://hypothesis.works/,所以我们可以确定它按预期工作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度复制嵌套可迭代(或改进的 itertools.tee 用于可迭代的可迭代) 的相关文章

  • 如何使用 os.chdir 转到减去最后一步的路径?

    例如 一个方法传递了一个路径作为参数 这个路径可能是 C a b c d 如果我想使用 os chdir 更改为 C a b 怎么办 c 没有最后一个文件夹 os chdir 可以接受 命令吗 os chdir 可以采取 作为论点 是的 然
  • 如何使用 Pandas Series 绘制两个不同长度/开始日期的时间序列?

    我正在绘制 每周总事件 的几个熊猫系列对象 系列中的数据events per week看起来像这样 Datetime 1995 10 09 45 1995 10 16 63 1995 10 23 83 1995 10 30 91 1995
  • Python 中的字符串slugification

    我正在寻找 slugify 字符串的最佳方法 蛞蝓 是什么 https stackoverflow com questions 427102 in django what is a slug 我当前的解决方案基于这个食谱 http code
  • 如何从连接矩阵绘制图像?

    我想编写一个脚本来从连接矩阵创建图像 基本上 只要矩阵中有 1 我就希望该区域在图像中被着色 对于例如 我使用 Photoshop 创建了这张图像 但我有一个很大的数据集 所以我必须自动化这个过程 如果有人能指出我正确的方向 那将非常有帮助
  • 如何从 pandas 数据框中的列中删除字符串值

    我正在尝试编写一些代码 以逗号分隔数据帧列中的字符串 因此它成为一个列表 并从该列表中删除某个字符串 如果存在 删除不需要的字符串后 我想再次以逗号加入列表元素 我的数据框如下所示 df Column1 Column2 0 a a b c
  • 如何使用 Plotly 将两张图合并为一张图?

    我有2个csv文件 我的代码如下 df pd read csv test csv sep t skiprows range 9 names A B C D df2 pd read csv LoadMatch Limit csv skipro
  • 将 pandas 数据帧拆分为子数据帧列表的最快方法

    我有一个大数据框df我有完整的清单indices中的独特元素df index 我现在想创建一个由元素索引的所有子数据帧的列表indices 具体来说 list df df loc x for x in indices 运行这个命令需要很长时
  • Python 将列表追加到列表中

    我正在尝试编写一个通过矩阵的函数 当满足条件时 它会记住该位置 我从一个空列表开始 locations 当函数遍历行时 我使用以下方法附加坐标 locations append x locations append y 函数末尾的列表如下所
  • 用 Pandas 计算该月的最后一个星期五

    我编写了这个函数来获取该月的最后一个星期四 def last thurs date date month date dt month year date dt year cal calendar monthcalendar year mon
  • 无法在 Windows 10 上更新 pip 的 PATH 变量

    我知道有数千个类似的主题 但我的 pip 命令突然停止工作 尽管我进行了所有研究 但我无法弄清楚原因 自从我上次使用 pip 以来已经有一段时间了 令人惊讶的是我的计算机不再识别该命令 我重新安装了pip 提示告诉我PATH变量没有正确更新
  • 如果文件为空,如何跳过文件行

    python 3中的程序 这是我的第一个涉及文件的程序 我需要忽略注释行 以 开头 和空行 然后拆分这些行 以便它们可迭代 但我不断收到 IndexError 消息 指出字符串索引超出范围 并且程序在空行处崩溃 import os path
  • Django Admin DateTimeField 显示 24 小时格式时间

    我尝试了谷歌 但没有找到解决方案 在Django管理端 我正在显示开始日期 and end date随着时间的推移 但时间已在24 hr格式 我想显示它12 hr format class CompanyEvent models Model
  • 清理 .txt 并计算最常见的单词

    我需要 1 从停用词列表中清除 txt 我将其放在单独的 txt中 2 之后我需要统计最常见的 25 个单词 这是我为第一部分想到的 usr bin python coding iso 8859 15 import re from coll
  • 在python中删除链表中的节点

    删除链表中的节点 这个实现有什么问题 def delete self val tmp self head prev None while tmp if val tmp data self size 1 if prev None self h
  • Django 将所有未捕获的 url 路由到包含的 urls.py

    我希望每个不以 api 开头的网址都使用 foo urls py urls py from django conf urls import include url from foo import urls as foo urls urlpa
  • Serializer.is_valid() 虽然 `required=False` 失败 - Django REST Framework

    我有一个像这样的序列化器 class DataSetColumnSerializer serializers ModelSerializer custom target serializers PrimaryKeyRelatedField
  • RStudio 不会通过 rPython 调用加载所有 Python 模块

    我从 Bash 和 RStudio 中运行相同的脚本时出现一些意外行为 请考虑以下事项 我有一个文件夹 rpython 包含两个脚本 test1 R library rPython setwd rpython python load tes
  • 如何使用 python 在 Windows 中禁用/启用特定 USB 端口? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我想在图形窗口中创建一个切换开关 可以使用 python 禁用 启用 Windows 中的特定 USB 端口 我可以使用哪个外部命令或
  • 动态创建类 - Python

    我需要动态创建一个类 为了更详细地讲 我需要动态创建 Django 的子类Form class 通过 动态 我打算根据用户提供的配置创建一个类 e g 我想要一个名为CommentForm这应该子类化Form class 该类应该有一个选定
  • 2d 图像点和 3d 网格之间的交点

    Given 网格 源相机 我有内在和外在参数 图像坐标 2d Output 3D 点 是从相机中心发出的光线穿过图像平面上的 2d 点与网格的交点 我试图找到网格上的 3d 点 This is the process From Multip

随机推荐