如何克隆可迭代并递归替换特定值?

How to clone an iterable and recursively replace specific values?

我正在编写一个小部件,允许指定其部件的排列。

为了实现这一点,我使用了模块化原则:
'Building blocks'用于指定任意顺序。

这些 'blocks' 作为枚举值实现,其中每个值代表一个单独的组件。

import enum

# The 'blocks'
class E(enum.Enum):
    A = 1
    B = 2
    C = 3

class Test():
    def __init__(self, arrangement):
        # The passed 'arrangement' is translated into the real arrangement.
        real_arrangement = []

        for a in arrangement:
            if a == E.A:
                real_arrangement.append("a_component")
            elif a == E.B:
                real_arrangement.append("b_component")
            elif a == E.C:
                real_arrangement.append("c_component")

        print(real_arrangement)


# The user can specify an arrangement...
arrangement = (E.A, E.C, E.B)

# ... and pass it to the constructor.
Test(arrangement)

# 'real_arrangement' = ("a_component", "c_component", "b_component")

请注意,占位符被替换,但结构相同。


但是,我也想在元素的属性方面给予一些自由。 因此,除了纯枚举值之外,可以传递一个包含枚举值和其他参数的可迭代对象

# the elements are iterables themself.
arrangement = ((10, E.A),
               (20, E.C),
               (5, E.B))

# real_arrangement = ((10, "a_component"), (20, "c_component"), (5, "b_component"))

请注意,结构保持不变。


所以我基本上是尝试克隆一个可迭代对象并递归地替换特定值。

我想到的任何方法都很难读。
是否已经有我可以使用的解决方案?


上面的代码是 运行 和 Python 3.5.2.

一个选项可以是检查 arrangement is iterable 的元素并根据结果使用适当的列表理解。因此,通过一些重构,您可以这样做:

import enum
import collections

# ...
class Test():
    def __init__(self, arrangement):
        def replace(a):
            if a == E.A:
                return "a_component"
            elif a == E.B:
                return "b_component"
            elif a == E.C:
                return "c_component"
            return a

        real_arrangement = [tuple(replace(e) for e in a) if isinstance(a, collections.Iterable) else replace(a) for a in arrangement]
        print(real_arrangement)

    #...

这将使您在问题中发布的两种类型的 arrangement 列表(或 "mixed" 列表)都起作用。

没有可迭代元素:

arrangement = (E.A, E.C, E.B)
Test(arrangement)
# ['a_component', 'c_component', 'b_component']

所有可迭代元素:

arrangement = ((10, E.A), (20, E.C), (5, E.B), (1, E.A))
Test(arrangement)
# [(10, 'a_component'), (20, 'c_component'), (5, 'b_component'), (1, 'a_component')]

有一些可迭代的元素:

arrangement = (E.A, (20, E.C), (5, E.B), (1, E.A))
Test(arrangement)
# ['a_component', (20, 'c_component'), (5, 'b_component'), (1, 'a_component')]

这种方法应该适用于普通(容器)类。

recursively_replace 函数的参数:

  • original – 应该执行递归替换的对象。
  • replacementsdict 包含以下形式的对:value_to_replace : replacement.
  • include_original_keys - bool 确定是否还应替换密钥,如果 originaldict. (默认为 False。)

该函数尝试使用与原始容器相同的容器 。 (不是同一个容器对象。)

def recursively_replace(original, replacements, include_original_keys=False):
    """Clones an iterable and recursively replaces specific values."""

    # If this function would be called recursively, the parameters 'replacements' and 'include_original_keys'
    # would have to be passed each time. Therefore, a helper function with a reduced parameter list is used
    # for the recursion, which nevertheless can access the said parameters.

    def _recursion_helper(obj):
        #Determine if the object should be replaced. If it is not hashable, the search will throw a TypeError.
        try: 
            if obj in replacements:
                return replacements[obj]
        except TypeError:
            pass

        # An iterable is recursively processed depending on its class.
        if hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
            if isinstance(obj, dict):
                contents = {}

                for key, val in obj.items():
                    new_key = _recursion_helper(key) if include_original_keys else key
                    new_val = _recursion_helper(val)

                    contents[new_key] = new_val

            else:
                contents = []

                for element in obj:
                    new_element = _recursion_helper(element)

                    contents.append(new_element)

            # Use the same class as the original.
            return obj.__class__(contents)

        # If it is not replaced and it is not an iterable, return it.
        return obj

    return _recursion_helper(original)


# Demonstration
if __name__ == "__main__":

    import enum

    # Define an enumeration whose values should be replaced later.
    class E(enum.Enum):
        A = 1
        B = 2
        C = 3

    # Map the values to be replaced with their respective replacements.
    dict_with_replacements = {E.A : "a_replacement",
                              E.B : "b_replacement",
                              E.C : "c_replacement"}

    ### example 1 ###
    test = (E.A, E.C, E.B)

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ('a_component', 'c_component', 'b_component')


    ### example 2 ###
    test = ((10, E.A), (20, E.C), (5, E.B))

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ((10, 'a_component'), (20, 'c_component'), (5, 'b_component'))


    ### example 3 ###
    test = (E.A, (20, E.C), (5, E.B))

    result = recursively_replace(test, dict_with_replacements)

    print(result)       # ('a_component', (20, 'c_component'), (5, 'b_component'))


    ### example 4 & 5 ###
    test = (E.A, {20:E.C, E.B:5})

    result = recursively_replace(test, dict_with_replacements) 

    print(result)       # ('a_component', {<E.B: 2>: 5, 20: 'c_component'})

    result = recursively_replace(test, dict_with_replacements, True)

    print(result)       # ('a_component', {'b_component': 5, 20: 'c_component'})