After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 727374 - Use namedtuple for return values
Use namedtuple for return values
Status: RESOLVED FIXED
Product: pygobject
Classification: Bindings
Component: general
3.12.x
Other All
: Normal enhancement
: ---
Assigned To: Nobody's working on this now (help wanted and appreciated)
Python bindings maintainers
Depends on:
Blocks:
 
 
Reported: 2014-03-31 07:54 UTC by Christoph Reiter (lazka)
Modified: 2015-10-27 08:33 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
Use a named tuple for returning multiple values (24.37 KB, patch)
2015-06-08 16:58 UTC, Christoph Reiter (lazka)
none Details | Review
[v2] Use a named tuple for returning multiple values (26.78 KB, patch)
2015-06-09 13:36 UTC, Christoph Reiter (lazka)
none Details | Review
[v3] Use a named tuple for returning multiple values (26.67 KB, patch)
2015-10-26 09:13 UTC, Christoph Reiter (lazka)
committed Details | Review

Description Christoph Reiter (lazka) 2014-03-31 07:54:48 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
Comment 1 Simon Feltman 2014-04-28 06:17:13 UTC
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.
Comment 2 Christoph Reiter (lazka) 2015-06-08 16:58:07 UTC
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
Comment 3 Christoph Reiter (lazka) 2015-06-09 13:36:37 UTC
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.
Comment 4 Simon Feltman 2015-10-26 08:44:44 UTC
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.
Comment 5 Christoph Reiter (lazka) 2015-10-26 09:13:11 UTC
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;
Comment 6 Simon Feltman 2015-10-27 02:26:07 UTC
Review of attachment 314123 [details] [review]:

Nice work, thank you!