Use __self__ from bound function as allows us to provide instance as class type for class method and None for static method.

This commit is contained in:
Graham Dumpleton
2013-08-25 13:50:00 +10:00
parent a795381e99
commit ff933cd264
4 changed files with 47 additions and 21 deletions

View File

@@ -1602,6 +1602,8 @@ static PyObject *WraptBoundFunctionWrapper_call(
PyObject *call_args = NULL;
PyObject *param_kwds = NULL;
PyObject *instance = NULL;
PyObject *result = NULL;
Py_ssize_t len = 0;
@@ -1612,16 +1614,31 @@ static PyObject *WraptBoundFunctionWrapper_call(
kwds = param_kwds;
}
/*
* We actually ignore the instance supplied when the function was
* bound and use that saved against __self__ of the bound function.
* This will be the class type for a class method and None for the
* case of a static method.
*/
instance = PyObject_GetAttrString(self->object_proxy.wrapped, "__self__");
if (!instance) {
PyErr_Clear();
Py_INCREF(Py_None);
instance = Py_None;
}
len = PySequence_Size(self->wrapper_args);
call_args = PyTuple_New(len+4);
Py_INCREF(self->object_proxy.wrapped);
Py_INCREF(self->instance);
Py_INCREF(instance);
Py_INCREF(args);
Py_INCREF(kwds);
PyTuple_SET_ITEM(call_args, 0, self->object_proxy.wrapped);
PyTuple_SET_ITEM(call_args, 1, self->instance);
PyTuple_SET_ITEM(call_args, 1, instance);
PyTuple_SET_ITEM(call_args, 2, args);
PyTuple_SET_ITEM(call_args, 3, kwds);
@@ -1637,6 +1654,8 @@ static PyObject *WraptBoundFunctionWrapper_call(
Py_DECREF(call_args);
Py_XDECREF(param_kwds);
Py_DECREF(instance);
return result;
}

View File

@@ -330,7 +330,22 @@ class _BoundFunctionWrapper(ObjectProxy):
self._self_wrapper_kwargs = kwargs
def __call__(self, *args, **kwargs):
return self._self_wrapper(self._self_wrapped, self._self_instance,
# As in this case we would be dealing with a class method or
# static method, then _self_instance will only tell us whether
# when calling the class or static method they did it via an
# instance of the class it is bound to and not the case where
# done by the class type itself. We thus ignore __self_instance
# and use the __self__ attribute of the bound function instead.
# For a class method, this means instance will be the class type
# and for a static method it will be None. This is probably the
# more useful thing we can pass through even though we loose
# knowledge of whether they were called on the instance vs the
# class type, as it reflects what they have available in the
# decoratored function.
instance = getattr(self._self_wrapped, '__self__', None)
return self._self_wrapper(self._self_wrapped, instance,
args, kwargs, *self._self_wrapper_args,
**self._self_wrapper_kwargs)

View File

@@ -128,7 +128,7 @@ class TestCallingInnerClassMethod(unittest.TestCase):
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertEqual(instance, None)
self.assertEqual(instance, Class)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)
@@ -148,16 +148,14 @@ class TestCallingInnerClassMethod(unittest.TestCase):
self.assertEqual(result, (_args, _kwargs))
def test_instance_call_function(self):
# Test calling classmethod via class instance. The instance
# passed to the wrapper will not be None because our decorator
# surrounds the classmethod decorator.
# Test calling classmethod via class instance.
_args = (1, 2)
_kwargs = { 'one': 1, 'two': 2 }
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertNotEqual(instance, None)
self.assertEqual(instance, Class)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)
@@ -184,7 +182,7 @@ class TestCallingInnerClassMethod(unittest.TestCase):
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertEqual(instance, None)
self.assertEqual(instance, Class)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)
@@ -205,16 +203,14 @@ class TestCallingInnerClassMethod(unittest.TestCase):
self.assertEqual(result, (_args, _kwargs))
def test_instance_call_function_nested_decorators(self):
# Test calling classmethod via class instance. The instance
# passed to the wrapper will not be None because our decorator
# surrounds the classmethod decorator.
# Test calling classmethod via class instance.
_args = (1, 2)
_kwargs = { 'one': 1, 'two': 2 }
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertNotEqual(instance, None)
self.assertEqual(instance, Class)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)

View File

@@ -148,16 +148,14 @@ class TestCallingInnerStaticMethod(unittest.TestCase):
self.assertEqual(result, (_args, _kwargs))
def test_instance_call_function(self):
# Test calling staticmethod via class instance. The instance
# passed to the wrapper will not be None because our decorator
# surrounds the staticmethod decorator.
# Test calling staticmethod via class instance.
_args = (1, 2)
_kwargs = { 'one': 1, 'two': 2 }
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertNotEqual(instance, None)
self.assertEqual(instance, None)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)
@@ -205,16 +203,14 @@ class TestCallingInnerStaticMethod(unittest.TestCase):
self.assertEqual(result, (_args, _kwargs))
def test_instance_call_function_nested_decorator(self):
# Test calling staticmethod via class instance. The instance
# passed to the wrapper will not be None because our decorator
# surrounds the staticmethod decorator.
# Test calling staticmethod via class instance.
_args = (1, 2)
_kwargs = { 'one': 1, 'two': 2 }
@wrapt.decorator
def _decorator(wrapped, instance, args, kwargs):
self.assertNotEqual(instance, None)
self.assertEqual(instance, None)
self.assertEqual(args, _args)
self.assertEqual(kwargs, _kwargs)
return wrapped(*args, **kwargs)