# Collections # # Provides common interface for dictionaries and lists. If a string is passed # in, it is split and then treated as a list. Optimized for convenience rather # than for large collections. Sorting versus the key is used to avoid randomness # in the ordering of the dictionary-based collections. # Collection {{{1 class Collection(object): """Collection Takes a list or dictionary and provides both a list like and dictionary like API, meaning that it provides the keys, values, and items methods like dictionary and you can iterate through the value like a list. When applying keys to a list, the indices are returned as the keys. You can also use an index or a key to get a value, you test to see if a value is in the collection using the *in* operator, and you can get the length of the collection. """ def __init__(self, collection): self.collection = collection def keys(self): try: return self.collection.keys() except AttributeError: return range(len(self.collection)) def values(self): try: return [self.collection[k] for k in self.collection.keys()] except AttributeError: return self.collection def items(self): try: return [(k, self.collection[k]) for k in self.collection.keys()] except AttributeError: return enumerate(self.collection) def __format__(self, template): """Convert the collection into a string The template consists of two components separated by a vertical bar. The first component specifies the formatting from each item. The key and value are interpolated using {{k}} to represent the key and {{v}} to represent the value. The second component is the separator. Thus: >>> dogs = Collection({'collie': 3, 'beagle':1, 'sheppard': 2}) >>> print('dogs: {:{{k}} ({{v}})|, }.'.format(dogs)) dogs: collie (3), beagle (1), sheppard (2). """ components = template.split('|') if len(components) == 2: fmt, sep = components elif len(components) == 1: fmt, sep = components[0], ', ' else: raise ValueError('invalid format string for {!r}', self) if not fmt: fmt = '{}' return sep.join(fmt.format(v, k=k, v=v) for k, v in self.items()) def __contains__(self, item): return item in self.values() def __iter__(self): return iter(self.values()) def __len__(self): return len(self.collection) def __getitem__(self, key): return self.collection[key] class Info: def __init__(self, **kwargs): self.__dict__.update(kwargs) # Test code {{{1 C = Collection([ Info(name='bob', email='bob@btca.com'), Info(name='ted', email='ted@btca.com'), Info(name='carol', email='carol@btca.com'), Info(name='alice', email='alice@btca.com'), ]) print('\nCASE 1') print('Email:\n {:{{v.name}}: {{v.email}}|\n }'.format(C)) print('\nCASE 2') print(f'Email:\n {C:{{v.name}} {{v.email}}|\n }') # Cases 1 and 2 are basically the same, except that Case 1 uses the strings # format method whereas case 2 uses formatted string literals. In both # cases the format string contains escaped braces. In the case of the format # method the escaping seems to be working as expected but the escaping in # the format string does not seem to be working in the f-string. # # The behavior when evaluating the f-string is to print the following error # message: # NameError: name 'v' is not defined # The error occurs before the objects __format__ method is called. # That suggests that the escaping of the braces is not working in the format # string in formatted string literals.