Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subclassing proxied classes fails #21

Closed
ohanar opened this issue May 15, 2014 · 6 comments
Closed

Subclassing proxied classes fails #21

ohanar opened this issue May 15, 2014 · 6 comments

Comments

@ohanar
Copy link

ohanar commented May 15, 2014

In python 3.4 (and probably other versions, just didn't test against them), if you try the following:

@wrapt.CallableObjectProxy
class Foo:
    pass
class Foo(Bar):
    pass

You will get a TypeError complaining about the number of arguments you are passing to the ObjectProxy constructor (at least with the c-extension version).

I tried fiddling with this a little, but I couldn't really think of any way to fix this. At the very least, if this is not fixable, this behavior should be documented.

@GrahamDumpleton
Copy link
Owner

What exactly are you trying to do?

The CallableObjectProxy class wouldn't be used in that way.

If that was just a bad example and what you are wanting to do was have a decorator on a base class which was then derived from, you need to use:

@mydecorator
class Blass(object):
    pass

class Derived(Base.__wrapped__):
    pass

If the decorator has to also apply to the derived class, then you should add it to the base class.

If you somehow want that to happen automatically, you probably should be using meta classes and not decorators.

@ohanar
Copy link
Author

ohanar commented May 15, 2014

I'm working on a lazy import hook, and am using object proxies to lazily evaluate `from foo import bar`` statements (using caching), although it is still very much a work in progress. The point is when I hand off my proxied objects, I don't know how they are going to be used.

In particular, it would be nice if inner defined derived classes could be lazily evaluated, such as in

from foo import Base

class Bar:
    def __init__(self):
        class Derived(Base):
            pass
        self._derived = Derived

where Base would be a proxy for a class object (and foo would only be imported if an instance of Bar was created). I can of course special case class objects in the cache and not proxy them (and hence reduce the amount of possible laziness), but obviously I would prefer not to.

Like I said, I don't really see a fix, and regardless I think that it should be documented (e.g. "an ObjectProxy should behave in every way like the wrapped object, except for the following exceptions: [enumerate exceptions]").

P.S. I'm not actually using your ObjectProxy class (although mine is based on top of it) because it requires the wrapped object to already exist at initialization, which breaks the whole point of laziness.

@GrahamDumpleton
Copy link
Owner

For intercepting attribute access on a module, to either change what is returned, or to generate an attribute on demand, then you need to wrap the target module itself and replace the module in sys.modules.

import wrapt

class LazyModule(wrapt.ObjectProxy):

    @property
    def Bar(self):
        print 'generate Bar'
        # Generate reference to Bar some how.
        # Use a nested class for this example.
        class Bar(object):
            def method(self):
                print 'method'
        return Bar

import sys

sys.modules[__name__] = LazyModule(sys.modules[__name__])

With this we then get:

>>> import lazymodule
>>> type(lazymodule)
<class 'LazyModule'>
>>> dir(lazymodule)
['LazyModule', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'sys', 'wrapt']
>>> b = lazymodule.Bar()
generate Bar
>>> b.method()
method

If the names of attributes isn't fixed list and so using a property like this cannot be done, you can override __getattr__ in the LazyModule class. That can get a little tricky if want to also overload __setattr__ at the same time.

The general case of generating the object to be wrapped lazily on demand the first time only is something have been thinking about. The issue is how to do it without having to duplicate all the existing proxy code again for that case. If can be done, idea would be that you could have:

def wrapped():
    # create the object to be wrapped when called
    return …

wrapper = LazyObjectProxy(wrapped)

So when the first requirement exists to need the wrapped object it is created by calling the supplied 'wrapped' function and then caching the result and using it there after.

Is doable, but suspect I need a complete separate LazyObjectProxy implementation and cannot have the existing ObjectProxy class do double duty or even harness what is done already by deriving from ObjectProxy.

Even then, I don't think you could make it a proper object proxy and there would be various issues with it. For example, you can't intercept access to certain __ attributes properly to allow you to create the value on demand after lazily creating the wrapped object instance and then grab that attribute from it. So can't be totally transparent as try to do with ObjectProxy.

Anyway, try that LazyModule example and see if that will work for what you are doing and if you think it doesn't then explain the problem and will see what else you may need to do.

@GrahamDumpleton
Copy link
Owner

@ohanar Did you try the suggestion I made for a lazy module proxy object?

I will leave this issue here for the moment to remind me to document the issue about how to derive a class from a decorated one:

@mydecorator
class Base(object):
    pass

class Derived(Base.__wrapped__):
    pass

but when that is done will close the issue.

@ohanar
Copy link
Author

ohanar commented May 21, 2014

Sorry, busy week.

What you suggest is more or less what I was already doing. However, I'm looking at implementing an import hook, so I really have no idea what the attributes of the module are (and hence doing things with __setattr__ and __getattr__). The main issue that I've encountered is with was going one layer deep (by making the attributes of a lazy module be [lazy] object proxies) -- this is to support the from foo import bar syntax, which should proxy bar and only load foo when bar is used in some non-trivial way.

I think that with a little re-writing of your ObjectProxy class, it would be pretty trivial to add a LazyObjectProxy derivative. I'm pretty busy at the moment, but once I have a bit of time (probably in a couple of weeks) I could see about putting this together and making a pull request.

@GrahamDumpleton
Copy link
Owner

Closing as nothing specific to wrapt that has to be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants