Open mentalisttraceur opened 1 year ago
In the case of __str__()
, or similarly __repr__()
, if you want the representation to reflect something about the wrapper, the customised implementation would need to be supplied in place and return a string. In your example you are attempting to return an object instance which cannot work. The assumption that you could supply a string as argument to the wrapper is also wrong.
There is no generic recipe for providing an implementation of __str__()
or __repr__()
is this sort of arrangement with a proxy wrapper.
Even __add__()
could well be problematic in a generic way because the argument could technically be a different type to the wrapped object it is being added to. So using the same wrapper type to wrap the result may not be the correct thing to do.
So with arithmetic operands like that, again may not be a generic recipe which one could use.
Can you provide a better example, with code, of the actual problem you are trying to solve that you think it needs to behave this way? As it stands it doesn't look like a generic object proxy would be appropriate and a more customised wrapper would be need for the specific types you intend wrapping.
Okay, I'll provide a more concrete example when I have the time+motivation+spoons+etc.
In the meantime, a few quick comments:
if you want the representation to reflect something about the wrapper,
I don't. I just used two dunder methods as examples, one unary and one binary, to show the shape of the code I had in mind.
The assumption that you could supply a string as argument to the wrapper is also wrong.
If that assumption wasn't true for a wrapper, we wouldn't make that wrapper inherit from StickyObjectProxy (or it would override __new__
to limit what it sticks to).
Even add() could well be problematic in a generic way because the argument could technically be a different type to the wrapped object it is being added to. So using the same wrapper type to wrap the result may not be the correct thing to do.
So with arithmetic operands like that, again may not be a generic recipe which one could use.
Right, and again the implementer of a sticky proxy would be expected to know what their proxy is valid for, and override __new__
to narrow what it sticks to.
...hmm, I think the "override __new__
" idea is muddling things (and has other flaws). Here's a better design:
def StickyObjectProxy(ObjectProxy):
@staticmethod
def can_stick(from_object, to_object):
# Check if this wrapper should "stick" from one object to another.
# Sticky proxy subclasses override this as-needed.
return True
...
def __str__(self):
result = str(self.__wrapped__)
if type(self).can_stick(self, result):
return type(self)(result)
return result
...
def __add__(self, other):
result = self.__wrapped__ + other
if type(self).can_stick(self, result):
return type(self)(result)
return result
...
So for example, if I write a sticky proxy which only sticks to callables, then my can_stick
method would just be:
@staticmethod
def can_stick(from_object, to_object):
return callable(to_object)
But if I write a debugging-aiding "track everything that caused this value" annotation wrapper, then the default always-true can_stick
is perfect.
and a more customised wrapper would be need for the specific types you intend wrapping.
Usually yes, a sticky proxy would be a customized subclass, to narrow things down, but I think the generic stick-to-everything behavior is very useful as a base (reduces work+boilerplate).
I think this is great idea! I had a need for similar object several times.
Few times it was just wrapping an attribute and one time proxying a list of objects, where proxy method executes methods of all objects and reconstructs the proxy with result list - this was mostly with numpy objects.
I'm picturing a subclass of
ObjectProxy
(suggested name:StickyObjectProxy
orViralObjectProxy
) which wraps the result of every method called on it with itself:Subclasses would override
can_stick
with logic that is appropriate for that wrapper.[Edit: I have updated this proposal since Graham's first reply.]
(Of course I also suggest adding a corresponding
Callable{Sticky,Viral}ObjectProxy
.)This would enable propagating
annotations (for example, when debugging/exploring complex code, I sometimes really want to know where a value "came from", which sometimes means "what data paths fed into this value? what was this computed from"? the
can_stick
for such a thing would probably be justto_object is not None
to keep it from breakingis None
checks) andbehaviors (for example, ergonomics improvements like adding an operator overload for function composition) (in this example, the
can_stick
check would becallable(to_object)
.)through code with minimal boilerplate.
The big reason for having this inside of
wrapt
is thatwrapt
is already in the business of knowing when a new dunder method is added to the language, and aStickyObjectProxy
would have to override almost every method thatObjectProxy
has to override, and ifObjectProxy
has to change (for example, adding a new dunder method or a new workaround for newer versions of Python) thenStickyObjectProxy
probably needs the same exact change, so it would be a really big efficiency gain to consolidate that effort, visibility, and need-for-awareness in one place.