Как клонировать итеративно и рекурсивно заменить конкретные значения? - PullRequest
0 голосов
/ 04 ноября 2018

Я пишу виджет, который позволяет указывать расположение его частей.

Для этого я использую модульный принцип :
«Строительные блоки» используются для указания любого заказа.

Эти «блоки» реализованы в виде значений перечисления, где каждое значение представляет отдельный компонент.

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.

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Этот подход должен работать для общих (контейнерных) классов.

Параметры функции recursively_replace:

  • original - объект, для которого должна быть выполнена рекурсивная замена.
  • замен - dict, который содержит пары вида: value_to_replace : replacement.
  • include_original_keys - bool, который определяет, следует ли также заменять ключи, в случае, если original является dict. (По умолчанию 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'})
0 голосов
/ 04 ноября 2018

Одним из вариантов может быть проверка того, является ли элемент arrangement итеративным , и использование соответствующего понимания списка в зависимости от результата. Итак, с помощью некоторого рефакторинга вы можете сделать это:

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 списков (или "смешанных" списков), которые вы разместили в своем вопросе.

Без повторяющихся элементов:

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')]
...