如何克隆可迭代并递归替换特定值?
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 – 应该执行递归替换的对象。
- replacements –
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'})
我正在编写一个小部件,允许指定其部件的排列。
为了实现这一点,我使用了模块化原则:
'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 – 应该执行递归替换的对象。
- replacements –
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'})