GNOME Bugzilla – Bug 727374
Use namedtuple for return values
Last modified: 2015-10-27 08:33:25 UTC
I just implemented the following in pgi: ################################ >>> from pgi.repository import Gtk >>> v = Gtk.init_check([]) >>> v (True, argv=[]) >>> v[:] (True, []) >>> v.argv [] ############################# By creating a tuple subclass per function and cashing it there. In Python it comes down to this, which is similar to namedtuple with less magic since instantiating by the user isn't needed. ############################# from operator import itemgetter class _ReturnValue(tuple): __slots__ = () _fformat = () def __repr__(self): return self._fformat % self def __reduce__(self): return (tuple, (tuple(self),)) def create_return_tuple(args): """Creates a new class similar to namedtuple. Pass a list of field names or None for no field name. >>> x = create_return_tuple([None, "bar"]) >>> x((1, 3)) (1, bar=3) """ fformat = ["%r" if f is None else "%s=%%r" % f for f in args] fformat = "(%s)" % ", ".join(fformat) class ReturnValue(_ReturnValue): __slots__ = () _fformat = fformat for i, a in enumerate(args): if a is not None: vars()[a] = property(itemgetter(i)) del i, a return ReturnValue
Seems like a nice feature. The implementation should probably be guided with performance tests as this could slow things down. Personally I tend to expand tuple returns into multiple assignments to enhance readability: res, argv = Gtk.init_check(argv) But I also see a use for this especially in something like a list comprehension.
Created attachment 304795 [details] [review] Use a named tuple for returning multiple values Here is a working implementation. See the added tests for the exported Python API. Marshalling is ~7% slower for two item tuples (Gtk.Button.get_alignment for example) and 13% for 4 item tuples (Gst.version). This is (AFAICS) due to CPython implementing a free list for pure tuples [0], reducing allocations. I can implement a similar free list for the new type.. the CPython implementation doesn't look too complicated (a linked list + count per tuple size). Regarding the motivation for this: Another point besides list comprehensions is print debugging and exploring the API in the interactive Python console. [0] https://hg.python.org/cpython/file/db08f902bb3a/Objects/tupleobject.c#l7
Created attachment 304861 [details] [review] [v2] Use a named tuple for returning multiple values Added a free list now. This reduces the slowdown to 3-4% for both functions and I can't figure out what else would be different. The tuple free list in CPython is cleaned up during the cyclic gc run, and as I don't know how to hook into this, I limited the free list size to waste 21kB max. Assuming that only 2-4 length tuples are used, 3.5kB.
Review of attachment 304861 [details] [review]: Looks good overall, just some minor nitpicks. ::: gi/pygi-resulttuple.c @@ +42,3 @@ + */ +static PyObject* +_pygi_resulttuple_repr(PyObject *self) { I think we want to move away from using underscore prefixed symbols. Also for static functions, there's no need for the pygi prefix, this should become: resulttuple_repr ::: gi/pygi-resulttuple.h @@ +30,3 @@ + +PyObject* +_pygi_resulttuple_new (PyTypeObject *subclass, Py_ssize_t len); All of these functions should have the underscore removed but keep the pygi prefix.
Created attachment 314123 [details] [review] [v3] Use a named tuple for returning multiple values Thanks. Changed the naming as proposed; Also renamed _pygi_create_new_resulttuple_type to pygi_resulttuple_new_type for consistency;
Review of attachment 314123 [details] [review]: Nice work, thank you!
Thanks. https://git.gnome.org/browse/pygobject/commit/?id=175d10665472e6f4090d707e3b89255814c932b1