I want to write a wrapper class which takes a value and behaves just like it except for adding a 'reason' attribute. I had something like this in mind:
class ExplainedValue(object):
def __init__(self, value, reason):
self.value = value
self.reason = reason
def __getattribute__(self, name):
print '__getattribute__ with %s called' % (name,)
if name in ('__str__', '__repr__', 'reason', 'value'):
return object.__getattribute__(self, name)
value = object.__getattribute__(self, 'value')
return object.__getattribute__(value, name)
def __str__(self):
return "ExplainedValue(%s, %s)" % (
str(self.value),
self.reason)
__repr__ = __str__
However, the double-underscore functions don't seem to be captured with __getattribute__
, for example:
>>> numbers = ExplainedValue([1, 2, 3, 4], "it worked")
>>> numbers[0]
Traceback (most recent call last):
File "<pyshell#118>", line 1, in <module>
numbers[0]
TypeError: 'ExplainedValue' object does not support indexing
>>> list(numbers)
__getattribute__ with __class__ called
Traceback (most recent call last):
File "<pyshell#119>", line 1, in <module>
list(numbers)
TypeError: 'ExplainedValue' object is not iterable
I would think the two above should end up doing this:
>>> numbers.value[0]
__getattribute__ with value called
1
>>> list(numbers.value)
__getattribute__ with value called
[1, 2, 3, 4]
Why is this not happening? How can I make it happen? (This might be a horrible idea to actually use in real code but I'm curious about the technical issue now.)
The magic methods ensure a consistent data model that retains the inherited feature of the built-in class while providing customized class behavior. These methods can enrich the class design and can enhance the readability of the language.
You can do it still, by naming your object just so: def _Foo__method(self): where you prefix the method name with one more underscore and the defining classname (so in this case prefixed with _Foo ). The process of renaming methods and attributes with a double underscore is called private name mangling.
__enter__ and __exit__ methods are used with the 'with' block in the python. __call__ method is used to use the object as a method. __iter__ method is used to generate generator objects using the object.
As millimoose says, an implicit __foo__
call never goes through __getattribute__
. The only thing you can do is actually add the appropriate functions to your wrapper class.
class Wrapper(object):
def __init__(self, wrapped):
self.wrapped = wrapped
for dunder in ('__add__', '__sub__', '__len__', ...):
locals()[dunder] = lambda self, __f=dunder, *args, **kwargs: getattr(self.wrapped, __f)(*args, **kwargs)
obj = [1,2,3]
w = Wrapper(obj)
print len(w)
Class bodies are executed code like any other block (well, except def
); you can put loops and whatever else you want inside. They're only magical in that the entire local scope is passed to type()
at the end of the block to create the class.
This is, perhaps, the only case where assigning to locals()
is even remotely useful.
For the sake of posterity, this is what I came up with:
class BaseExplainedValue(object):
def __init__(self, value, reason):
self.value = value
self.reason = reason
def __getattribute__(self, name):
if name in ('value', 'reason'):
return object.__getattribute__(self, name)
value = object.__getattribute__(self, 'value')
return object.__getattribute__(value, name)
def __str__(self):
return "<'%s' explained by '%s'>" % (
str(self.value),
str(self.reason))
def __unicode__(self):
return u"<'%s' explained by '%s'>" % (
unicode(self.value),
unicode(self.reason))
def __repr__(self):
return "ExplainedValue(%s, %s)" % (
repr(self.value),
repr(self.reason))
force_special_methods = set(
"__%s__" % name for name in (
'lt le eq ne gt ge cmp rcmp nonzero call len getitem setitem delitem iter reversed contains getslice setslice delslice' + \
'add sub mul floordiv mod divmod pow lshift rshift and xor or div truediv' + \
'radd rsub rmul rdiv rtruediv rfloordiv rmod rdivmod rpow rlshift rrshift rand rxor ror' + \
'iadd isub imul idiv itruediv ifloordiv imod ipow ilshift irshift iand ixor ior' + \
'neg pos abs invert complex int long float oct hex index coerce' + \
'enter exit').split(),
)
def make_special_method_wrapper(method_name):
def wrapper(self, *args, **kwargs):
return getattr(self, method_name)(*args, **kwargs)
wrapper.__name__ = method_name
return wrapper
def EXP(obj, reason="no reason provided"):
if isinstance(obj, BaseExplainedValue):
return obj
class ThisExplainedValue(BaseExplainedValue):
pass
#special-case the 'special' (underscore) methods we want
obj_class = obj.__class__
for method_name in dir(obj_class):
if not (method_name.startswith("__") and method_name.endswith("__")): continue
method = getattr(obj_class, method_name)
if method_name in force_special_methods:
setattr(ThisExplainedValue, method_name, make_special_method_wrapper(method_name))
ThisExplainedValue.__name__ = "%sExplainedValue" % (obj_class.__name__,)
return ThisExplainedValue(obj, reason)
Usage:
>>> success = EXP(True, "it went ok")
>>> if success:
print 'we did it!'
we did it!
>>> success = EXP(False, "Server was on fire")
>>> if not success:
print "We failed: %s" % (EXP(success).reason,)
We failed: Server was on fire
The explained values can be used interchangeably with those which they wrap:
>>> numbers = EXP([1, 2, 3, 4, 5], "method worked ok")
>>> numbers
ExplainedValue([1, 2, 3, 4, 5], 'method worked ok')
>>> numbers[3]
4
>>> del numbers[3]
>>> numbers
ExplainedValue([1, 2, 3, 5], 'method worked ok')
It even fools isinstance
(explanation here):
>>> isinstance(EXP(False), bool)
True
>>> isinstance(EXP([]), list)
True
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With