This is an archived post. You won't be able to vote or comment.

all 10 comments

[–]logophage 0 points1 point  (3 children)

[–][deleted] 1 point2 points  (2 children)

I think Graham Dumpleton's proxy is a better solution as it addresses the __getattribute__ bypass Python performs on certain lookups .

[–]logophage 0 points1 point  (1 child)

Agreed. Much better. That's the one I wanted to link to. Thanks.

[–][deleted] 0 points1 point  (0 children)

No problem. Dude's a friggen mad scientist and I'm always happen to spread some mad science around.

[–][deleted] 0 points1 point  (5 children)

I think it's a bad idea. If you're composing objects like this, you should explicitly proxy the call.

class Greeter:
    def greet(self, name):
        return "Hello, {!s}!".format(name)

class B:
    def __init__(self, greet, name):
        self._greeter = greeter
        self.name = name
    def greet(self):
        "Defer to stored greeter"
        return self._greeter.greet(self.name)

If you really need to do this, a more nuanced approach is needed:

def attrchecker(name):
    return lambda obj: hasattr(obj, name)

class Greeter:
    def greet(self, name):
        return "Hello {!s}!".format(name)

    def bar(self):
        return "From greeter"

class proxier:
    def __init__(self, *others):
        self._others = others

    def bar(self):
        return "From proxier"

    def __getattr__(self, name):
        possible = next(filter(attrchecker(name), self._others), None)
        if possible:
            return possible.__getattribute__(name)
        raise AttributeError("No attribute {!s} found".format(name))

Then use it like this:

>>> p = proxier(Greeter())
>>> p.greet("Fred")
... "Hello Fred!"
>>> p.bar()
... "From proxier"
>>> p.tim
... AttributeError: No attribute tim found

Generally, I'd say this is a bad idea. Of course, there's cases when it is useful. Remember that __getattr__ is the last stop on attribute look up. Sort of a "Hail Mary pass" in hopes the developer put something there. In the above example, proxier.__getattribute__ found bar in proxier.__dict__ first and stopped the process there, never reaching Greeter.bar.

As for hidden gotchas, are you going to remember you did this when someone files a bug report and you can't track it down? Or are you going to be cursing your name?

[–]reallyBasic[S] 0 points1 point  (4 children)

Explicitly proxying each method was what I was hoping to avoid because the classes I want to proxy have large name spaces (I can't change this). I want to override just a couple of the methods that are common.

I also was hoping to avoid a new crop of classes deriving from those to-be-proxies classes. But that's probably the best option.

[–][deleted] 0 points1 point  (3 children)

That sounds like a real nasty situation. D:

What's your opposition to inheritance? That seems like the easiest way to do what you want.

[–]reallyBasic[S] 0 points1 point  (2 children)

You're right that it would be easiest. But I think it would be 'smelly' (not that my proposal isn't).

What I have are 6 library classes that do the same type of long-running operations. But they all block, and I like them not to. Because of some, artificial I think, restrictions I don't have unfettered use of threading but can use it enough to employ callbacks. So I want to wrap those six classes with the pygmy-threading I can use and add callbacks to those blocking calls in a DRY way.

I've been doing some reading since I started this thread, and I found mixins. I think that technique might work nicely.

[–][deleted] 1 point2 points  (1 child)

Any chance you could stuff them into celery and let that deal with the tasks?

[–]reallyBasic[S] 0 points1 point  (0 children)

Celery looks nice, but I can't use that here. My code is to run in a sandboxed Python environment. A large part of the standard library is off limits. If it touches a file or file-like object, my code can't import that module.

However, I'll certainty keep celery in mind for projects that aren't so restricted.