| from itertools import filterfalse |
| |
| |
| def unique_everseen(iterable, key=None): |
| "List unique elements, preserving order. Remember all elements ever seen." |
| # unique_everseen('AAAABBBCCDAABBB') --> A B C D |
| # unique_everseen('ABBCcAD', str.lower) --> A B C D |
| seen = set() |
| seen_add = seen.add |
| if key is None: |
| for element in filterfalse(seen.__contains__, iterable): |
| seen_add(element) |
| yield element |
| else: |
| for element in iterable: |
| k = key(element) |
| if k not in seen: |
| seen_add(k) |
| yield element |
| |
| |
| # copied from more_itertools 8.8 |
| def always_iterable(obj, base_type=(str, bytes)): |
| """If *obj* is iterable, return an iterator over its items:: |
| |
| >>> obj = (1, 2, 3) |
| >>> list(always_iterable(obj)) |
| [1, 2, 3] |
| |
| If *obj* is not iterable, return a one-item iterable containing *obj*:: |
| |
| >>> obj = 1 |
| >>> list(always_iterable(obj)) |
| [1] |
| |
| If *obj* is ``None``, return an empty iterable: |
| |
| >>> obj = None |
| >>> list(always_iterable(None)) |
| [] |
| |
| By default, binary and text strings are not considered iterable:: |
| |
| >>> obj = 'foo' |
| >>> list(always_iterable(obj)) |
| ['foo'] |
| |
| If *base_type* is set, objects for which ``isinstance(obj, base_type)`` |
| returns ``True`` won't be considered iterable. |
| |
| >>> obj = {'a': 1} |
| >>> list(always_iterable(obj)) # Iterate over the dict's keys |
| ['a'] |
| >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit |
| [{'a': 1}] |
| |
| Set *base_type* to ``None`` to avoid any special handling and treat objects |
| Python considers iterable as iterable: |
| |
| >>> obj = 'foo' |
| >>> list(always_iterable(obj, base_type=None)) |
| ['f', 'o', 'o'] |
| """ |
| if obj is None: |
| return iter(()) |
| |
| if (base_type is not None) and isinstance(obj, base_type): |
| return iter((obj,)) |
| |
| try: |
| return iter(obj) |
| except TypeError: |
| return iter((obj,)) |