Reputation:
I'm having a bit of an issue solving a problem I'm looking at. I have a specialized set of functions which are going to be in use across a program, which are basically dynamic callables which can replace functions and methods. Due to the need to have them work properly to emulate the functionality of methods, these functions override __get__
to provide a wrapped version that gives access to the retrieving object.
Unfortunately, __get__
does not work if the function is set directly on an instance. This is because only "data descriptors" call the __get__
function when the key is found in the __dict__
of an instance. The only solution to this that comes to mind is: trick python into thinking this is a data descriptor. This involves creating a __set__
function on the descriptor. Ideally, I want this __set__
function to work as a pass-through (returns control to the caller and continues evaluating as if it doesn't exist).
Is there any way to trick python into thinking that a descriptor is a data descriptor but letting a containing class/instance still be able to use its setattr command as normal?
Also, I am aware that it is possible to do this with an override of __getattribute__
for the caller. However, this is a bad solution because I would have to do this for the 'object' built-in and anything that overrides it. Not exactly a great solution.
Alternatively, if there is any alternative solution I would be happy to hear it.
class Descriptor(object):
def __get__(self, obj, objtype = None):
return None
class Caller(object):
a = Descriptor()
print a
>>> None
x = Caller()
print a
>>> None
x.a = Descriptor()
print x.a
>>> <__main__.Descriptor object at 0x011D7F10>
The last case should print 'None' to maintain consistency.
If you add a __set__
to the Descriptor, this will print 'None' (as desired). However, this messes up any command of x.a = (some value) from working as it had previously. Since I do not want to mess up this functionality, that is not helpful. Any solutions would be great.
Correction: My prior idea would still not work, as I misunderstood the descriptor handling slightly. Apparently if a descriptor is not on a class at all, it will never be called- regardless of the set. The condition I had only helps if there is a dict val and a class accessor of the same name. I am actually looking for a solution more along the lines of: http://blog.brianbeck.com/post/74086029/instance-descriptors but that does not involve having everything under the sun inherit a specialized interface.
Unfortunately, given this new understanding of the descriptor interface this may not be possible? Why oh why would python make decorators essentially non-dynamic?
Upvotes: 1
Views: 886
Reputation:
I think I may have one answer to my question, though it's not all that pretty- it does sidestep the issue. My current plan of attack is to do what python does- bind the functions manually. I was already using my unbound function's get command to generate bound-type functions. One possible solution is to force anybody who wants to set a new function to manually bind it. It's annoying but it's not crazy. Python actually makes you do it (if you just set a function onto an instance as an attribute, it doesn't become bound).
It would still be nice to have this happen automatically, but it's not awful to force someone who is setting a new function to use x.a = Descriptor().get(x) which in this case will give the desired behavior (as well as for the example, for that matter). It's not a general solution but it will work for this limited problem, where method binding was being emulated basically. With that said, if anybody has a better solution I'd still be very happy to hear it.
Upvotes: 0
Reputation: 882421
I think the cleanest solution is to leave __set__
alone, and set the descriptor on the class -- wrapping the original class if needed. I.e., instead of x.a = Descriptor()
, do setdesc(x, 'a', Descriptor()
where:
class Wrapper(object): pass
def setdesc(x, name, desc):
t = type(x)
if not issubclass(t, wrapper):
class awrap(Wrapper, t): pass
x.__class__ = awrap
setattr(x.__class__, name, desc)
This is the general approach I suggest when somebody wants to "set on an instance" anything (special method or descriptor) that needs to be set on the class to work, but doesn't want to affect the instance's original class.
Of course, it all works well only if you have new-style classes, but then descriptors don't really play well with old-style classes anyhow;-).
Upvotes: 1