1

I'm trying to create an object collection proxy, which could do something like this:

class A:
    def do_something():
        # ...

class B:
    def get_a():
        return A()

class Proxy:
    ?

collection = [B(), B()]
proxy = Proxy(collection)

proxy.get_a().do_something()
# ^ for each B in collection get_a() and do_something()

What would be the best architecture / strategy for achieving this?

The key question, I guess is, how to cache the result of get_a() so I can then proxy do_something()

N.B. I don't expect proxy.get_a().do_something() to return anything sensible, it's only supposed to do things.

letoosh
  • 511
  • 2
  • 6
  • 13

2 Answers2

3

Simple enough... you may want to adapt it to do some more checking

class A(object):
    def do_something(self):
        print id(self), "called"

class B(object):
    def get_a(self):
        return A()

class Proxy(object):
    def __init__(self, objs):
        self._objs = objs

    def __getattr__(self, name):
        def func(*args, **kwargs):
            return Proxy([getattr(o, name)(*args, **kwargs) for o in self._objs])
        return func

collection = [B(), B()]

proxy = Proxy(collection)
proxy.get_a().do_something()

Results in:

4455571152 called
4455571216 called
GaretJax
  • 7,462
  • 1
  • 38
  • 47
  • That doesn't deal with caching calls to `B.get_a()` though (I first overlooked that requirement as well). But your class based approach to proxying and the memoization techniques in my answer should provide @Alex with everything he needs :) – Lukas Graf Nov 24 '12 at 23:03
  • Actually he doesn't want to cache it I think. He simply meant how can he store the result of the call to get_A until do_something is called. – GaretJax Nov 24 '12 at 23:05
  • Lukas, Garet, thanks that's what I was after. Garet, you're right, I just wanted to pipe it through so I can "chain" the next call in my code nicely. – letoosh Nov 25 '12 at 00:42
2

The most pythonic way of going about this would probably be a list comprehension:

results = [b.get_a().do_something() for b in collection]

If you want to cache calls to B.get_a(), you can use memoization. A simple way of doing memoization yourself could look like this:

cache = None

# ...

class B:
    def get_a(self):
        global cache
        if cache is None:
            cache = A()
        return cache

If you want to use caching in multiple places, you'll need to cache results based on keys in order to distinguish them, and for convenience's sake write a decorator that you can simply wrap functions with whose results you want to cache.

A good example of this is found in Python Algorithms: Mastering Basic Algorithms in the Python Language (see this question). Modified for your case, to not use the function arguments but the function name as cache key, it would look like this:

from functools import wraps

def memoize(func):
    cache = {}
    key = func.__name__
    @ wraps(func)
    def wrap(*args):
        if key not in cache:
            cache[key] = func(*args)
        return cache[key]
    return wrap

class A:
    def do_something(self):
        return 1

class B:
    @memoize
    def get_a(self):
        print "B.get_a() was called"
        return A()


collection = [B(), B()]

results = [b.get_a().do_something() for b in collection]
print results

Output:

B.get_a() was called
[1, 1]
Community
  • 1
  • 1
Lukas Graf
  • 30,317
  • 8
  • 77
  • 92
  • Thanks, Lukas. Probably it wasn't clear from the question I was looking for a class-based way of doing this, as I need to provide an interface like myclass.get_a().do_something() which would iterate over it's own collection, but your answer points me in some direction, so I'll keep digging. – letoosh Nov 24 '12 at 22:45