1import itertools 2 3 4class PseudoStr(str): 5 pass 6 7 8class StrProxy: 9 def __init__(self, value): 10 self.value = value 11 def __str__(self): 12 return self.value 13 def __bool__(self): 14 return bool(self.value) 15 16 17class Object: 18 def __repr__(self): 19 return '<object>' 20 21 22def wrapped_arg_combos(*args, 23 wrappers=(PseudoStr, StrProxy), 24 skip=(lambda w, i, v: not isinstance(v, str)), 25 ): 26 """Yield every possible combination of wrapped items for the given args. 27 28 Effectively, the wrappers are applied to the args according to the 29 powerset of the args indicies. So the result includes the args 30 completely unwrapped. 31 32 If "skip" is supplied (default is to skip all non-str values) and 33 it returns True for a given arg index/value then that arg will 34 remain unwrapped, 35 36 Only unique results are returned. If an arg was skipped for one 37 of the combinations then it could end up matching one of the other 38 combinations. In that case only one of them will be yielded. 39 """ 40 if not args: 41 return 42 indices = list(range(len(args))) 43 # The powerset (from recipe in the itertools docs). 44 combos = itertools.chain.from_iterable(itertools.combinations(indices, r) 45 for r in range(len(indices)+1)) 46 seen = set() 47 for combo in combos: 48 for wrap in wrappers: 49 indexes = [] 50 applied = list(args) 51 for i in combo: 52 arg = args[i] 53 if skip and skip(wrap, i, arg): 54 continue 55 indexes.append(i) 56 applied[i] = wrap(arg) 57 key = (wrap, tuple(indexes)) 58 if key not in seen: 59 yield tuple(applied) 60 seen.add(key) 61