Created
December 23, 2017 18:31
-
-
Save egregius313/b4604cdfff448b2a1a947039aeba616e to your computer and use it in GitHub Desktop.
Allow tuple unpacking on classes defined with @attr.s
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Allow tuple unpacking on classes defined with | |
# @attr.s decorator. Uses the attribute information | |
# from the definition of the class using attrs | |
# | |
# See also: | |
# http://www.attrs.org | |
# https://github.com/python-attrs/attrs | |
import hashlib | |
import linecache | |
from toolz import curry | |
__author__ = 'egregius313' | |
@curry | |
def unpackable(cls, attrs=None): | |
""" | |
Take a class defined using the @attr.s method | |
and define a __iter__ method to allow for easier | |
pattern matching variable definitions. | |
Unpacking order is the same as the order @attr.s | |
uses for methods like __init__ | |
The `attrs' argument must be either iterable or None. | |
If the argument is iterable, only attributes with those names | |
will be used. Otherwise, all attributes defined with attr.ib() | |
will be unpacked. | |
>>> @unpackable | |
... @attr.s | |
... class Person: | |
... name = attr.ib() | |
... age = attr.ib() | |
... | |
>>> people = [ | |
... Person('Jonathan', 35), | |
... Person('Rick', 26), | |
... Person('Samuel', 20), | |
... Person('Brian', 39), | |
... ] | |
... | |
>>> for name, age in people: | |
... print(name, 'is', age, 'years old.') | |
... | |
Jonathan is 35 years old | |
Rick is 26 years old | |
Samuel is 20 years old | |
Brian is 39 years old | |
""" | |
if hasattr(cls, '__iter__'): | |
return cls | |
if attrs is None: | |
attributes = tuple(a.name for a in cls.__attrs_attrs__) | |
else: | |
attributes = tuple( | |
a.name for a in cls.__attrs_attrs__ | |
if a.name not in attrs | |
) | |
print(attributes) | |
lines = [ | |
'def __iter__(self):', | |
] | |
if attributes: | |
for attrib in attributes: | |
lines.append(' yield self.%s' % attrib) | |
else: | |
lines.append(' yield from ()') | |
sha1 = hashlib.sha1() | |
sha1.update(repr(attributes).encode('utf-8')) | |
unique_filename = '<unpackable generated iter %s>' % (sha1.hexdigest(),) | |
# The additional newline helps inspect.getsource | |
script = '\n'.join(lines) + '\n' | |
globs = {} | |
locs = {} | |
bytecode = compile(script, unique_filename, "exec") | |
eval(bytecode, globs, locs) | |
linecache.cache[unique_filename] = ( | |
len(script), | |
None, | |
script.splitlines(True), | |
unique_filename, | |
) | |
cls.__iter__ = locs['__iter__'] | |
return cls |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment