Commit 35f79b22 authored by Simon Feltman's avatar Simon Feltman
Browse files

Add callable and descriptor protocols to PyGICallableInfo

Add tp_call (__call__) function to callable info objects.
This allows for replacement of wrapped invoke methods directly
with the already created callable info object. This has the
additional side effect of making doc strings lazily bound
(only generated when __doc__ is accessed).

Add tp_desc_get (__get__) to PyGIFunctionInfo which returns
a bound version of itself for methods and constructors.

Update various internal type checks to reflect the changes.
Update tests to reflect the new callable type being the same
across Python 2 & 3.

This patch gives roughly a %17 speedup for Gtk imports and
an %11 speedup for GI method calls.

https://bugzilla.gnome.org/show_bug.cgi?id=704037
parent 2339e030
......@@ -57,8 +57,7 @@ from ._gi import \
flags_register_new_gtype_and_add
from .types import \
GObjectMeta, \
StructMeta, \
Function
StructMeta
from ._gobject._gobject import \
GInterface, \
......@@ -220,7 +219,7 @@ class IntrospectionModule(object):
g_type.pytype = wrapper
elif isinstance(info, FunctionInfo):
wrapper = Function(info)
wrapper = info
elif isinstance(info, ConstantInfo):
wrapper = info.get_value()
else:
......
......@@ -3,6 +3,7 @@ import warnings
import functools
from gi import PyGIDeprecationWarning
from gi._gi import CallableInfo
from gi._gobject.constants import \
TYPE_NONE, \
TYPE_INVALID
......@@ -33,7 +34,8 @@ class _Registry(dict):
raise TypeError('Can not override a type %s, which is not in a gobject introspection typelib' % value.__name__)
if not value.__module__.startswith('gi.overrides'):
raise KeyError('You have tried to modify the registry outside of the overrides module. This is not allowed')
raise KeyError('You have tried to modify the registry outside of the overrides module. '
'This is not allowed (%s, %s)' % (value, value.__module__))
g_type = info.get_g_type()
assert g_type != TYPE_NONE
......@@ -52,8 +54,8 @@ class _Registry(dict):
class overridefunc(object):
'''decorator for overriding a function'''
def __init__(self, func):
if not hasattr(func, '__info__'):
raise TypeError("func must be an gi function")
if not isinstance(func, CallableInfo):
raise TypeError("func must be a gi function, got %s" % func)
from ..importer import modules
module_name = func.__module__.rsplit('.', 1)[-1]
self.module = modules[module_name]._introspection_module
......@@ -70,7 +72,7 @@ registry = _Registry()
def override(type_):
'''Decorator for registering an override'''
if isinstance(type_, types.FunctionType):
if isinstance(type_, (types.FunctionType, CallableInfo)):
return overridefunc(type_)
else:
registry.register(type_)
......
......@@ -357,7 +357,7 @@ out:
/* CallableInfo */
PYGLIB_DEFINE_TYPE ("gi.CallableInfo", PyGICallableInfo_Type, PyGIBaseInfo);
PYGLIB_DEFINE_TYPE ("gi.CallableInfo", PyGICallableInfo_Type, PyGICallableInfo);
static PyObject *
_wrap_g_callable_info_get_arguments (PyGIBaseInfo *self)
......@@ -395,6 +395,164 @@ _wrap_g_callable_info_get_arguments (PyGIBaseInfo *self)
return infos;
}
/* _callable_info_call:
*
* Shared wrapper for invoke which can be bound (instance method or class constructor)
* or unbound (function or static method).
*/
static PyObject *
_callable_info_call (PyGICallableInfo *self, PyObject *args, PyObject *kwargs)
{
/* Insert the bound arg at the beginning of the invoke method args. */
if (self->py_bound_arg) {
int i;
PyObject *result;
Py_ssize_t argcount = PyTuple_Size (args);
PyObject *newargs = PyTuple_New (argcount + 1);
if (newargs == NULL)
return NULL;
Py_INCREF (self->py_bound_arg);
PyTuple_SET_ITEM (newargs, 0, self->py_bound_arg);
for (i = 0; i < argcount; i++) {
PyObject *v = PyTuple_GET_ITEM (args, i);
Py_XINCREF (v);
PyTuple_SET_ITEM (newargs, i+1, v);
}
/* Invoke with the original GI info struct this wrapper was based upon.
* This is necessary to maintain the same cache for all bound versions.
*/
result = _wrap_g_callable_info_invoke ((PyGIBaseInfo *)self->py_unbound_info,
newargs, kwargs);
Py_DECREF (newargs);
return result;
} else {
/* We should never have an unbound info when calling when calling invoke
* at this point because the descriptor implementation on sub-classes
* should return "self" not a copy when there is no bound arg.
*/
g_assert (self->py_unbound_info == NULL);
return _wrap_g_callable_info_invoke ((PyGIBaseInfo *)self, args, kwargs);
}
}
/* _function_info_call:
*
* Specialization of _callable_info_call for GIFunctionInfo which
* handles constructor error conditions.
*/
static PyObject *
_function_info_call (PyGICallableInfo *self, PyObject *args, PyObject *kwargs)
{
if (self->py_bound_arg) {
GIFunctionInfoFlags flags;
/* Ensure constructors are only called as class methods on the class
* implementing the constructor and not on sub-classes.
*/
flags = g_function_info_get_flags ( (GIFunctionInfo*) self->base.info);
if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
PyObject *py_str_name;
const gchar *str_name;
GIBaseInfo *container_info = g_base_info_get_container (self->base.info);
g_assert (container_info != NULL);
py_str_name = PyObject_GetAttrString (self->py_bound_arg, "__name__");
if (py_str_name == NULL)
return NULL;
if (PyUnicode_Check (py_str_name) ) {
PyObject *tmp = PyUnicode_AsUTF8String (py_str_name);
Py_DECREF (py_str_name);
py_str_name = tmp;
}
#if PY_VERSION_HEX < 0x03000000
str_name = PyString_AsString (py_str_name);
#else
str_name = PyBytes_AsString (py_str_name);
#endif
if (strcmp (str_name, g_base_info_get_name (container_info))) {
PyErr_Format (PyExc_TypeError,
"%s constructor cannot be used to create instances of "
"a subclass %s",
g_base_info_get_name (container_info),
str_name);
Py_DECREF (py_str_name);
return NULL;
}
Py_DECREF (py_str_name);
}
}
return _callable_info_call (self, args, kwargs);
}
/* _new_bound_callable_info
*
* Utility function for sub-classes to create a bound version of themself.
*/
static PyGICallableInfo *
_new_bound_callable_info (PyGICallableInfo *self, PyObject *bound_arg)
{
PyGICallableInfo *new_self;
/* Return self if this is already bound or there is nothing passed to bind. */
if (self->py_bound_arg != NULL || bound_arg == NULL || bound_arg == Py_None) {
Py_INCREF ((PyObject *)self);
return self;
}
new_self = (PyGICallableInfo *)_pygi_info_new (self->base.info);
if (new_self == NULL)
return NULL;
Py_INCREF ((PyObject *)self);
new_self->py_unbound_info = (struct PyGICallableInfo *)self;
Py_INCREF (bound_arg);
new_self->py_bound_arg = bound_arg;
return new_self;
}
/* _function_info_descr_get
*
* Descriptor protocol implementation for functions, methods, and constructors.
*/
static PyObject *
_function_info_descr_get (PyGICallableInfo *self, PyObject *obj, PyObject *type) {
GIFunctionInfoFlags flags;
PyObject *bound_arg = NULL;
flags = g_function_info_get_flags ( (GIFunctionInfo*) self->base.info);
if (flags & GI_FUNCTION_IS_CONSTRUCTOR) {
if (type == NULL)
bound_arg = (PyObject *)(Py_TYPE(obj));
else
bound_arg = type;
} else if (flags & GI_FUNCTION_IS_METHOD) {
bound_arg = obj;
}
return (PyObject *)_new_bound_callable_info (self, bound_arg);
}
static void
_callable_info_dealloc (PyGICallableInfo *self)
{
Py_CLEAR (self->py_unbound_info);
Py_CLEAR (self->py_bound_arg);
PyGIBaseInfo_Type.tp_dealloc ((PyObject *) self);
}
static PyMethodDef _PyGICallableInfo_methods[] = {
{ "invoke", (PyCFunction) _wrap_g_callable_info_invoke, METH_VARARGS | METH_KEYWORDS },
{ "get_arguments", (PyCFunction) _wrap_g_callable_info_get_arguments, METH_NOARGS },
......@@ -543,7 +701,7 @@ static PyMethodDef _PyGITypeInfo_methods[] = {
/* FunctionInfo */
PYGLIB_DEFINE_TYPE ("gi.FunctionInfo", PyGIFunctionInfo_Type, PyGIBaseInfo);
PYGLIB_DEFINE_TYPE ("gi.FunctionInfo", PyGIFunctionInfo_Type, PyGICallableInfo);
static PyObject *
_wrap_g_function_info_is_constructor (PyGIBaseInfo *self)
......@@ -729,7 +887,6 @@ static PyMethodDef _PyGIFunctionInfo_methods[] = {
{ NULL, NULL, 0 }
};
/* RegisteredTypeInfo */
PYGLIB_DEFINE_TYPE ("gi.RegisteredTypeInfo", PyGIRegisteredTypeInfo_Type, PyGIBaseInfo);
......@@ -1667,7 +1824,7 @@ static PyMethodDef _PyGIUnresolvedInfo_methods[] = {
};
/* GIVFuncInfo */
PYGLIB_DEFINE_TYPE ("gi.VFuncInfo", PyGIVFuncInfo_Type, PyGIBaseInfo);
PYGLIB_DEFINE_TYPE ("gi.VFuncInfo", PyGIVFuncInfo_Type, PyGICallableInfo);
static PyObject *
_wrap_g_vfunc_info_get_invoker (PyGIBaseInfo *self)
......@@ -1835,14 +1992,20 @@ _pygi_info_register_types (PyObject *m)
if (PyModule_AddObject(m, "DIRECTION_INOUT", PyLong_FromLong(GI_DIRECTION_INOUT)))
return;
_PyGI_REGISTER_TYPE (m, PyGIUnresolvedInfo_Type, UnresolvedInfo,
PyGIBaseInfo_Type);
_PyGI_REGISTER_TYPE (m, PyGICallableInfo_Type, CallableInfo,
PyGIBaseInfo_Type);
_PyGI_REGISTER_TYPE (m, PyGICallbackInfo_Type, CallbackInfo,
PyGIBaseInfo_Type);
PyGICallableInfo_Type.tp_call = (ternaryfunc) _callable_info_call;
PyGICallableInfo_Type.tp_dealloc = (destructor) _callable_info_dealloc;
_PyGI_REGISTER_TYPE (m, PyGIFunctionInfo_Type, FunctionInfo,
PyGICallableInfo_Type);
PyGIFunctionInfo_Type.tp_call = (ternaryfunc) _function_info_call;
PyGIFunctionInfo_Type.tp_descr_get = (descrgetfunc) _function_info_descr_get;
_PyGI_REGISTER_TYPE (m, PyGIUnresolvedInfo_Type, UnresolvedInfo,
PyGIBaseInfo_Type);
_PyGI_REGISTER_TYPE (m, PyGICallbackInfo_Type, CallbackInfo,
PyGIBaseInfo_Type);
_PyGI_REGISTER_TYPE (m, PyGIRegisteredTypeInfo_Type, RegisteredTypeInfo,
PyGIBaseInfo_Type);
_PyGI_REGISTER_TYPE (m, PyGIStructInfo_Type, StructInfo,
......
......@@ -46,6 +46,19 @@ typedef struct {
PyGICallableCache *cache;
} PyGIBaseInfo;
typedef struct {
PyGIBaseInfo base;
/* Reference the unbound version of this struct.
* We use this for the actual call to invoke because it manages the cache.
*/
struct PyGICallableInfo *py_unbound_info;
/* Holds bound argument for instance, class, and vfunc methods. */
PyObject *py_bound_arg;
} PyGICallableInfo;
typedef struct {
PyGPointer base;
gboolean free_on_dealloc;
......
......@@ -57,15 +57,6 @@ def wraps_callable_info(info):
return update_func
def Function(info):
"""Wraps GIFunctionInfo"""
@wraps_callable_info(info)
def function(*args, **kwargs):
return info.invoke(*args, **kwargs)
return function
class NativeVFunc(object):
"""Wraps GINativeVFuncInfo"""
def __init__(self, info):
......@@ -78,30 +69,10 @@ class NativeVFunc(object):
return native_vfunc
def Constructor(info):
"""Wraps GIFunctionInfo with get_constructor() == True"""
@wraps_callable_info(info)
def constructor(cls, *args, **kwargs):
cls_name = info.get_container().get_name()
if cls.__name__ != cls_name:
raise TypeError('%s constructor cannot be used to create instances of a subclass' % cls_name)
return info.invoke(cls, *args, **kwargs)
return constructor
class MetaClassHelper(object):
def _setup_methods(cls):
for method_info in cls.__info__.get_methods():
if method_info.is_method():
function = Function(method_info)
method = function
elif method_info.is_constructor():
function = Constructor(method_info)
method = classmethod(function)
else:
function = Function(method_info)
method = staticmethod(function)
setattr(cls, function.__name__, method)
setattr(cls, method_info.__name__, method_info)
def _setup_fields(cls):
for field_info in cls.__info__.get_fields():
......@@ -326,7 +297,7 @@ class StructMeta(type, MetaClassHelper):
for method_info in cls.__info__.get_methods():
if method_info.is_constructor() and \
method_info.get_name() == 'new' and \
method_info.__name__ == 'new' and \
not method_info.get_arguments():
cls.__new__ = staticmethod(Constructor(method_info))
cls.__new__ = staticmethod(method_info)
break
......@@ -21,14 +21,14 @@ class Test(unittest.TestCase):
old_func)
def test_split_args_multi_out(self):
in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.int_out_out.__info__)
in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.int_out_out)
self.assertEqual(len(in_args), 0)
self.assertEqual(len(out_args), 2)
self.assertEqual(out_args[0].get_pytype_hint(), 'int')
self.assertEqual(out_args[1].get_pytype_hint(), 'int')
def test_split_args_inout(self):
in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.long_inout_max_min.__info__)
in_args, out_args = gi.docstring.split_function_info_args(GIMarshallingTests.long_inout_max_min)
self.assertEqual(len(in_args), 1)
self.assertEqual(len(out_args), 1)
self.assertEqual(in_args[0].get_name(), out_args[0].get_name())
......@@ -36,7 +36,7 @@ class Test(unittest.TestCase):
def test_split_args_none(self):
obj = GIMarshallingTests.Object(int=33)
in_args, out_args = gi.docstring.split_function_info_args(obj.none_inout.__info__)
in_args, out_args = gi.docstring.split_function_info_args(obj.none_inout)
self.assertEqual(len(in_args), 1)
self.assertEqual(len(out_args), 1)
......
......@@ -2329,16 +2329,12 @@ class TestInterfaces(unittest.TestCase):
Gio.FileEnumerator.next_file(obj, None)
self.fail('call with wrong type argument unexpectedly succeeded')
except TypeError as e:
if sys.version_info < (3, 0):
self.assertTrue('FileEnumerator' in str(e), e)
self.assertTrue('Object' in str(e), e)
else:
# should have argument name
self.assertTrue('self' in str(e), e)
# should have expected type
self.assertTrue('xpected Gio.FileEnumerator' in str(e), e)
# should have actual type
self.assertTrue('GIMarshallingTests.Object' in str(e), e)
# should have argument name
self.assertTrue('self' in str(e), e)
# should have expected type
self.assertTrue('xpected Gio.FileEnumerator' in str(e), e)
# should have actual type
self.assertTrue('GIMarshallingTests.Object' in str(e), e)
# wrong type for first argument: GObject
var = GLib.Variant('s', 'mystring')
......@@ -2359,16 +2355,12 @@ class TestInterfaces(unittest.TestCase):
Gio.SimpleAction.activate(obj, obj)
self.fail('call with wrong type argument unexpectedly succeeded')
except TypeError as e:
if sys.version_info < (3, 0):
self.assertTrue('SimpleAction' in str(e), e)
self.assertTrue('Object' in str(e), e)
else:
# should have argument name
self.assertTrue('self' in str(e), e)
# should have expected type
self.assertTrue('xpected Gio.Action' in str(e), e)
# should have actual type
self.assertTrue('GIMarshallingTests.Object' in str(e), e)
# should have argument name
self.assertTrue('self' in str(e), e)
# should have expected type
self.assertTrue('xpected Gio.Action' in str(e), e)
# should have actual type
self.assertTrue('GIMarshallingTests.Object' in str(e), e)
class TestMRO(unittest.TestCase):
......
......@@ -484,8 +484,8 @@ class TestGtk(unittest.TestCase):
# these methods cannot be called because they require a valid drag on
# a real GdkWindow. So we only check that they exist and are callable.
self.assertTrue(hasattr(widget.drag_dest_set_proxy, '__call__'))
self.assertTrue(hasattr(widget.drag_get_data, '__call__'))
self.assertTrue(hasattr(widget, 'drag_dest_set_proxy'))
self.assertTrue(hasattr(widget, 'drag_get_data'))
def test_drag_target_list(self):
mixed_target_list = [Gtk.TargetEntry.new('test0', 0, 0),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment