Hacker News new | ask | show | jobs
by bgschiller 2597 days ago
Nice write-up! Coincidentally, I wrote on the same topic, also in python just a few days ago [1]

I came to largely the same conclusions, but instead of forwardable, I wrote a new library called superdelegate that is, IMO, more explicit [2]

1: https://brianschiller.com/blog/2019/05/03/two-sorted-lists 2: https://github.com/bgschiller/superdelegate

3 comments

Alternately, if you want to just delegate everything, you can subclass wrapt.ObjectProxy https://wrapt.readthedocs.io/en/latest/wrappers.html#object-...

Python is scary sometimes. If it looks like a duck and quacks like a duck and isinstance(duck), it might be an alien masquerading as a duck using metaclasses to accomplish these feats. But with this craziness comes the ability to make a transparent runtime proxy to just about anything in Python, and do so recursively e.g. to track all uses of an object hierarchy.

I think I'm confused. If you want to delegate _everything_, why not just actually use inheritance? Or even the object itself. What's an example of when you would want an ObjectProxy?

Definitely agree about metaclass magic being a blessing and a curse. That superdelegate library was fun to write and uses a metaclass to make the API pleasant.

If you don't know what you're going to need to wrap (heck, it might be something with methods implemented with native code), but you want to override or intercept a certain subset of calls, then composition/delegation is really the only thing you can do. It's significantly more stable than trying to find the class of an arbitrary object at runtime, create a subclass of it, and try to copy the state of the original object.

For instance, I've seen instrumentation libraries monkey-patch database cursors with wrappers around those cursors, so the library can detect usage of the cursor without changing its behavior.

Django itself has a LazyObject class, which operates similarly to an ObjectProxy but only evaluates its wrapped initializer when one of its properties is accessed: https://docs.djangoproject.com/en/2.2/_modules/django/utils/...

And I'm using wrapt in a project (which I hope to open source) where, by recursively wrapping the return value of every __getitem__ and __getattr__ with a properly-bookkept proxy object, you can see what subgraph of a complicated object hierarchy was actually used by a function call (say, the rendering of a Django or Jinja template). Possibilities of such a transformation are endless!

You don't have to sacrifice type safety, that's a python problem. Other languages (eg: Groovy) accomplish the same thing in a type-safe way (see @Delegate annotation [1]).

http://docs.groovy-lang.org/2.4.9/html/gapi/groovy/lang/Dele...

In D some of that can be done with a Proxy mixin https://dlang.org/phobos/std_typecons.html#Proxy
That was a great read that was easy to digest. Thanks for writing this up!
I like it. I run into this use case all the time.