Last active
December 15, 2015 19:38
-
-
Save numberoverzero/5312543 to your computer and use it in GitHub Desktop.
treat a group of objects as a single instance
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
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 people.first | |
#Call function on all objects | |
people.greet("Steve") | |
#Multiple iteration works without creating new lists | |
last = people.last | |
print last | |
print last | |
#Modify multiple elements at once | |
people.last = "Bull" | |
print people.last | |
#Drop the proxying by iterating the GroupProxy | |
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)]) | |
# 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 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment