1from itertools import filterfalse 2 3 4def unique_everseen(iterable, key=None): 5 "List unique elements, preserving order. Remember all elements ever seen." 6 # unique_everseen('AAAABBBCCDAABBB') --> A B C D 7 # unique_everseen('ABBCcAD', str.lower) --> A B C D 8 seen = set() 9 seen_add = seen.add 10 if key is None: 11 for element in filterfalse(seen.__contains__, iterable): 12 seen_add(element) 13 yield element 14 else: 15 for element in iterable: 16 k = key(element) 17 if k not in seen: 18 seen_add(k) 19 yield element 20 21 22# copied from more_itertools 8.8 23def always_iterable(obj, base_type=(str, bytes)): 24 """If *obj* is iterable, return an iterator over its items:: 25 26 >>> obj = (1, 2, 3) 27 >>> list(always_iterable(obj)) 28 [1, 2, 3] 29 30 If *obj* is not iterable, return a one-item iterable containing *obj*:: 31 32 >>> obj = 1 33 >>> list(always_iterable(obj)) 34 [1] 35 36 If *obj* is ``None``, return an empty iterable: 37 38 >>> obj = None 39 >>> list(always_iterable(None)) 40 [] 41 42 By default, binary and text strings are not considered iterable:: 43 44 >>> obj = 'foo' 45 >>> list(always_iterable(obj)) 46 ['foo'] 47 48 If *base_type* is set, objects for which ``isinstance(obj, base_type)`` 49 returns ``True`` won't be considered iterable. 50 51 >>> obj = {'a': 1} 52 >>> list(always_iterable(obj)) # Iterate over the dict's keys 53 ['a'] 54 >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit 55 [{'a': 1}] 56 57 Set *base_type* to ``None`` to avoid any special handling and treat objects 58 Python considers iterable as iterable: 59 60 >>> obj = 'foo' 61 >>> list(always_iterable(obj, base_type=None)) 62 ['f', 'o', 'o'] 63 """ 64 if obj is None: 65 return iter(()) 66 67 if (base_type is not None) and isinstance(obj, base_type): 68 return iter((obj,)) 69 70 try: 71 return iter(obj) 72 except TypeError: 73 return iter((obj,)) 74