Skip to content

Instantly share code, notes, and snippets.

@numberoverzero
Last active December 15, 2015 19:38
Show Gist options
  • Save numberoverzero/5312543 to your computer and use it in GitHub Desktop.
Save numberoverzero/5312543 to your computer and use it in GitHub Desktop.
treat a group of objects as a single instance
binary_operator_str = """
def __{name}__(self, obj):
gen = lambda: (item {op} obj for obj in self)
return _proxy(gen)
#return GroupProxy([item {op} obj for item in self])
def __r{name}__(self, obj):
gen = lambda: (obj {op} item for obj in self)
return _proxy(gen)
#return GroupProxy([obj {op} item for item in self])
def __i{name}__(self, obj):
for index in xrange(len(self.__group_proxy_objects__)):
self.__group_proxy_objects__[index] {op}= obj
return self
"""
cmp_operator_str = "def __{name}__(self, obj): return GroupProxy([item {op} obj for item in self])"
unary_operator_str = "def __{name}__(self): return GroupProxy([{op} item for item in self])"
bin_ops = [
('or', '|'), ('and', '&'), ('xor', '^'), ('lshift', '<<'), ('rshift', '>>'),
('add', '+'), ('sub', '-'), ('mul', '*'), ('div', '/'), ('mod', '%'),
('truediv', '/'), ('floordiv', '//')
]
cmp_ops = [
('lt', '<'), ('gt', '>'), ('le', '<='), ('ge', '>='),
('eq', '=='), ('ne', '!=')
]
unary_ops = [
('neg', '-'),
('pos', '+'),
('invert', '~')
]
def _proxy(gen):
'''
Wraps an iterable in a lambda so it can be iterated any number of times.
This is used for chaining GroupProxy objects. While it can be called directly,
groups(gen) or GroupProxy(gen) are usually better (as they get rid of one indirection)
'''
cls = type("_proxy_inf_gen", (), {
'__iter__': lambda self: iter(gen()),
'__len__': lambda self: sum(1 for _ in iter(self))
})()
return GroupProxy(cls)
def group(*objects):
'''
Alias for creating new GroupProxy objects.
This creates a copy of the input, while using
the GroupProxy constructor directly just saves
a reference to the objects.
'''
return GroupProxy(list(objects))
class GroupProxy(object):
def __init__(self, objects):
self.__group_proxy_objects__ = objects
def __call__(self, *args, **kwargs):
# This returns a list instead of using the _proxy pattern
# found in the rest of the methods because we want to immediately
# evaluate the call instead of creating a generator to lazy eval.
# Also, we only want to call once, whereas wrapping a generator around this
# means that we potentially re-call the functions whenever the new proxy is iterated
return GroupProxy([obj(*args, **kwargs) for obj in self])
def __getattr__(self, name):
if name == '__group_proxy_objects__':
return object.__getattr__(self, name)
else:
return _proxy(lambda: (getattr(obj, name) for obj in self))
def __setattr__(self, name, value):
if name == '__group_proxy_objects__':
object.__setattr__(self, name, value)
else:
set_ = lambda obj: setattr(obj, name, value)
map(set_, self)
def __delattr__(self, name):
if name == '__group_proxy_objects__':
object.__delattr__(self, name)
else:
del_ = lambda obj: delattr(obj, name)
map(del_, self)
def __getitem__(self, arg):
return _proxy(lambda: (obj[arg] for obj in self))
def __setitem__(self, arg, value):
for obj in self:
obj[arg] = value
def __delitem__(self, arg):
for obj in self:
del obj[arg]
def __iter__(self):
return iter(self.__group_proxy_objects__)
def __str__(self):
return repr(self)
def __repr__(self):
return "GroupProxy([" + ", ".join(repr(obj) for obj in self) + "])"
def __len__(self):
return len(self.__group_proxy_objects__)
def __rdivmod__(self, obj_):
return _proxy(lambda: (divmod(obj_, obj) for obj in self))
def __pow__(self, *args):
return _proxy(lambda: (pow(obj, *args) for obj in self))
def __ipow__(self, obj):
for index in xrange(len(self.__group_proxy_objects__)):
self.__group_proxy_objects__[index] **= obj
return self
def __rpow__(self, obj_):
return _proxy(lambda: (pow(obj_, obj) for obj in self))
for name, op in bin_ops:
exec(binary_operator_str.format(name=name, op=op))
for name, op in cmp_ops:
exec(cmp_operator_str.format(name=name, op=op))
for name, op in unary_ops:
exec(unary_operator_str.format(name=name, op=op))
del name, op
if __name__ == "__main__":
class Person(object):
def __init__(self, first, last):
self.first = first
self.last = last
def __str__(self):
return repr(self)
def __repr__(self):
return "Person({}, {})".format(repr(self.first), repr(self.last))
def greet(self, name):
print "Hi {}, I'm {}.".format(name, self.first)
larry = Person("Larry", "Smith")
curly = Person("Curly", "Smith")
moe = Person("Moe", "Smith")
people = group(larry, curly, moe)
#Formatting
print people
#Get returns new proxy of multiple attributes
print
print people.first
#Call function on all objects
print
people.greet("Steve")
#Multiple iteration works without creating new lists
print
last = people.last
print last
print last
#Modify multiple elements at once
print
people.last = "Bull"
print people.last
#Drop the proxying by iterating the GroupProxy
print
a = list(people)
print a
print '='*30
class Watcher(object):
def __init__(self):
self._n_access = 0
@property
def foo(self):
print "Property accessed"
self._n_access += 1
return self._n_access
@foo.setter
def foo(self, value):
print "Set foo"
def __contains__(self, obj):
return obj == "foo"
wp = group(*[Watcher() for _ in range(5)])
print
# First lookup
print "First lookup"
print wp.foo, "\n"
# Lazy, no lookup
a = wp.foo
# Second lookup
print "Second lookup"
print wp.foo, "\n"
# Third lookup
print "Third lookup (just print a)"
print a, "\n"
print "Fourth lookup (just print a again)"
print a, "\n"
print "Contains check"
print "foo" in a
print
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment