[Python-3000] Warning about future-unsafe usage patterns in Python 2.x e.g. dict.keys().sort()

Guido van Rossum guido at python.org
Mon Aug 28 18:22:52 CEST 2006


Not much time to review the patch, but +1 on this -- I've described
this a few times in my Py3k talk, glad that some code is forthcoming
now!

--Guido

On 8/28/06, Brian Quinlan <brian at sweetapp.com> wrote:
> It is my understanding that, in Python 3000, certain functions and
> methods that currently return lists will return some sort of view type
> (e.g. dict.values()) or an iterator (e.g. zip). So certain usage
> patterns will no longer be supported e.g. d.keys().sort().
>
> The attached patch, which is a diff against the subversion "trunk" of
> Python 2.x, tries to warn the user about these kind of future-unsafe
> usage patterns. It works by storing the type that the list will become
> in the future, at creation time, and checking to see if called list
> functions will be supported by that type in the future.
>
> Currently the patch if very incomplete and the idea itself may be
> flawed. But I thought it was interesting to run against my own code to
> see what potential problems it has. Example:
>
> ...
> Type "help", "copyright", "credits" or "license" for more information.
>  >>> d = {"apple" : "sweet", "orange" : "tangy"}
>  >>> "juicy" in d.values()
> False
>  >>> d.keys().sort()
> __main__:1: DeprecationWarning: dictionary view will not support sort
>  >>> "a" in zip([1,2,3,4], "abcd")
> __main__:1: DeprecationWarning: iterator will not support contains
> False
>
> Cheers,
> Brian
>
>
> Index: Python/bltinmodule.c
> ===================================================================
> --- Python/bltinmodule.c        (revision 51629)
> +++ Python/bltinmodule.c        (working copy)
> @@ -1570,7 +1570,7 @@
>                 goto Fail;
>         }
>
> -       v = PyList_New(n);
> +       v = PyList_NewFutureType(n, PY_BECOME_ITER);
>         if (v == NULL)
>                 goto Fail;
>
> @@ -1678,7 +1678,7 @@
>                                 "range() result has too many items");
>                 return NULL;
>         }
> -       v = PyList_New(n);
> +       v = PyList_NewFutureType(n, PY_BECOME_ITER);
>         if (v == NULL)
>                 return NULL;
>         for (i = 0; i < n; i++) {
> @@ -2120,7 +2120,7 @@
>         Py_ssize_t len;    /* guess at result length */
>
>         if (itemsize == 0)
> -               return PyList_New(0);
> +               return PyList_NewFutureType(0, PY_BECOME_ITER);
>
>         /* args must be a tuple */
>         assert(PyTuple_Check(args));
> @@ -2148,7 +2148,7 @@
>         /* allocate result list */
>         if (len < 0)
>                 len = 10;       /* arbitrary */
> -       if ((ret = PyList_New(len)) == NULL)
> +       if ((ret = PyList_NewFutureType(len, PY_BECOME_ITER)) == NULL)
>                 return NULL;
>
>         /* obtain iterators */
> Index: Include/listobject.h
> ===================================================================
> --- Include/listobject.h        (revision 51629)
> +++ Include/listobject.h        (working copy)
> @@ -19,6 +19,12 @@
>  extern "C" {
>  #endif
>
> +/* Constants representing the types that may be used instead of a list
> +   in Python 3000 */
> +#define PY_REMAIN_LIST     0x01  /* List will remain a list in Py2K */
> +#define PY_BECOME_DICTVIEW 0x02  /* List will become a "view" on a dict */
> +#define PY_BECOME_ITER     0x04  /* List will become an iterator */
> +
>  typedef struct {
>      PyObject_VAR_HEAD
>      /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
> @@ -36,6 +42,7 @@
>       * the list is not yet visible outside the function that builds it.
>       */
>      Py_ssize_t allocated;
> +    int future_type; /* The type the object will have in Py3K */
>  } PyListObject;
>
>  PyAPI_DATA(PyTypeObject) PyList_Type;
> @@ -44,6 +51,7 @@
>  #define PyList_CheckExact(op) ((op)->ob_type == &PyList_Type)
>
>  PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size);
> +PyAPI_FUNC(PyObject *) PyList_NewFutureType(Py_ssize_t size, int future_type);
>  PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *);
>  PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t);
>  PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *);
> @@ -57,6 +65,9 @@
>  PyAPI_FUNC(PyObject *) _PyList_Extend(PyListObject *, PyObject *);
>
>  /* Macro, trading safety for speed */
> +/* XXX These functions do not (yet) trigger future usage warnings.
> +   So e.g. range(100)[0] will slip though
> +*/
>  #define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])
>  #define PyList_SET_ITEM(op, i, v) (((PyListObject *)(op))->ob_item[i] = (v))
>  #define PyList_GET_SIZE(op)    (((PyListObject *)(op))->ob_size)
> Index: Objects/dictobject.c
> ===================================================================
> --- Objects/dictobject.c        (revision 51629)
> +++ Objects/dictobject.c        (working copy)
> @@ -1003,7 +1003,7 @@
>
>    again:
>         n = mp->ma_used;
> -       v = PyList_New(n);
> +       v = PyList_NewFutureType(n, PY_BECOME_DICTVIEW);
>         if (v == NULL)
>                 return NULL;
>         if (n != mp->ma_used) {
> @@ -1037,7 +1037,7 @@
>
>    again:
>         n = mp->ma_used;
> -       v = PyList_New(n);
> +       v = PyList_NewFutureType(n, PY_BECOME_DICTVIEW);
>         if (v == NULL)
>                 return NULL;
>         if (n != mp->ma_used) {
> @@ -1076,7 +1076,7 @@
>          */
>    again:
>         n = mp->ma_used;
> -       v = PyList_New(n);
> +       v = PyList_NewFutureType(n, PY_BECOME_DICTVIEW);
>         if (v == NULL)
>                 return NULL;
>         for (i = 0; i < n; i++) {
> Index: Objects/listobject.c
> ===================================================================
> --- Objects/listobject.c        (revision 51629)
> +++ Objects/listobject.c        (working copy)
> @@ -8,6 +8,49 @@
>  #include <sys/types.h>         /* For size_t */
>  #endif
>
> +static int warn_future_usage(PyListObject *self,
> +                                                int supported_types, char *operation)
> +{
> +       char message[256];
> +
> +       if ((((PyListObject *) self)->future_type & supported_types) == 0)
> +       {
> +               switch (self->future_type) {
> +                       case PY_BECOME_DICTVIEW:
> +                               PyOS_snprintf(message, sizeof(message),
> +                                       "dictionary view will not support %s",
> +                                       operation);
> +                               break;
> +                       case PY_BECOME_ITER:
> +                               PyOS_snprintf(message, sizeof(message),
> +                                       "iterator will not support %s",
> +                                       operation);
> +                               break;
> +                       default: /* This shouldn't happen */
> +                               PyErr_BadInternalCall();
> +                               return -1;
> +               }
> +
> +               /* XXX This should be PyExc_PendingDeprecationWarning */
> +               if (PyErr_WarnEx(PyExc_DeprecationWarning, message, 1) < 0)
> +                       return -1;
> +       }
> +
> +       return 0;
> +}
> +
> +#define WARN_LIST_USAGE(self, supported_types, operation) \
> +       if (warn_future_usage((PyListObject *) self, \
> +                           supported_types, operation) < 0) \
> +               return NULL;
> +
> +#define WARN_LIST_USAGE_INT(self, supported_types, operation) \
> +       if (warn_future_usage((PyListObject *) self, \
> +                                                  supported_types, operation) < 0) \
> +               return -1;
> +
> +#define PyList_Check(op) PyObject_TypeCheck(op, &PyList_Type)
> +
>  /* Ensure ob_item has room for at least newsize elements, and set
>   * ob_size to newsize.  If newsize > ob_size on entry, the content
>   * of the new slots at exit is undefined heap trash; it's the caller's
> @@ -116,10 +159,29 @@
>         }
>         op->ob_size = size;
>         op->allocated = size;
> +       op->future_type = PY_REMAIN_LIST;
>         _PyObject_GC_TRACK(op);
>         return (PyObject *) op;
>  }
>
> +PyObject *
> +PyList_NewFutureType(Py_ssize_t size, int future_type)
> +{
> +       PyListObject *op = (PyListObject *) PyList_New(size);
> +       if (op == NULL)
> +               return NULL;
> +       else {
> +               if (future_type == 0)
> +               {
> +                       Py_DECREF(op);
> +                       PyErr_BadInternalCall();
> +                       return NULL;
> +               }
> +               op->future_type = future_type;
> +               return (PyObject *) op;
> +       }
> +}
> +
>  Py_ssize_t
>  PyList_Size(PyObject *op)
>  {
> @@ -369,6 +431,7 @@
>  static Py_ssize_t
>  list_length(PyListObject *a)
>  {
> +       WARN_LIST_USAGE_INT(a, PY_REMAIN_LIST | PY_BECOME_DICTVIEW, "len");
>         return a->ob_size;
>  }
>
> @@ -378,6 +441,7 @@
>         Py_ssize_t i;
>         int cmp;
>
> +       WARN_LIST_USAGE_INT(a, PY_REMAIN_LIST | PY_BECOME_DICTVIEW, "contains");
>         for (i = 0, cmp = 0 ; cmp == 0 && i < a->ob_size; ++i)
>                 cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
>                                                    Py_EQ);
> @@ -387,6 +451,7 @@
>  static PyObject *
>  list_item(PyListObject *a, Py_ssize_t i)
>  {
> +       WARN_LIST_USAGE(a, PY_REMAIN_LIST, "item indexing");
>         if (i < 0 || i >= a->ob_size) {
>                 if (indexerr == NULL)
>                         indexerr = PyString_FromString(
> @@ -404,6 +469,8 @@
>         PyListObject *np;
>         PyObject **src, **dest;
>         Py_ssize_t i, len;
> +
> +       WARN_LIST_USAGE(a, PY_REMAIN_LIST, "slicing");
>         if (ilow < 0)
>                 ilow = 0;
>         else if (ilow > a->ob_size)
> @@ -444,6 +511,9 @@
>         Py_ssize_t i;
>         PyObject **src, **dest;
>         PyListObject *np;
> +
> +       WARN_LIST_USAGE(a, PY_REMAIN_LIST, "concatenation");
> +
>         if (!PyList_Check(bb)) {
>                 PyErr_Format(PyExc_TypeError,
>                           "can only concatenate list (not \"%.200s\") to list",
> @@ -484,6 +554,8 @@
>         PyListObject *np;
>         PyObject **p, **items;
>         PyObject *elem;
> +
> +       WARN_LIST_USAGE(a, PY_REMAIN_LIST, "repitition");
>         if (n < 0)
>                 n = 0;
>         size = a->ob_size * n;
> @@ -521,6 +593,8 @@
>  {
>         Py_ssize_t i;
>         PyObject **item = a->ob_item;
> +
> +       WARN_LIST_USAGE_INT(a, PY_REMAIN_LIST, "clear");
>         if (item != NULL) {
>                 /* Because XDECREF can recursively invoke operations on
>                    this list, we make it empty first. */
> @@ -565,6 +639,9 @@
>         Py_ssize_t k;
>         size_t s;
>         int result = -1;        /* guilty until proved innocent */
> +
> +       WARN_LIST_USAGE_INT(a, PY_REMAIN_LIST, "slicing");
> +
>  #define b ((PyListObject *)v)
>         if (v == NULL)
>                 n = 0;
> @@ -658,9 +735,9 @@
>  {
>         PyObject **items;
>         Py_ssize_t size, i, j, p;
> +       size = PyList_GET_SIZE(self);
>
> -
> -       size = PyList_GET_SIZE(self);
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "repeat");
>         if (size == 0) {
>                 Py_INCREF(self);
>                 return (PyObject *)self;
> @@ -692,6 +769,8 @@
>  list_ass_item(PyListObject *a, Py_ssize_t i, PyObject *v)
>  {
>         PyObject *old_value;
> +
> +       WARN_LIST_USAGE_INT(a, PY_REMAIN_LIST, "item assignment");
>         if (i < 0 || i >= a->ob_size) {
>                 PyErr_SetString(PyExc_IndexError,
>                                 "list assignment index out of range");
> @@ -711,6 +790,8 @@
>  {
>         Py_ssize_t i;
>         PyObject *v;
> +
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "insert");
>         if (!PyArg_ParseTuple(args, "nO:insert", &i, &v))
>                 return NULL;
>         if (ins1(self, i, v) == 0)
> @@ -721,6 +802,7 @@
>  static PyObject *
>  listappend(PyListObject *self, PyObject *v)
>  {
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "append");
>         if (app1(self, v) == 0)
>                 Py_RETURN_NONE;
>         return NULL;
> @@ -736,6 +818,7 @@
>         Py_ssize_t i;
>         PyObject *(*iternext)(PyObject *);
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "extend");
>         /* Special cases:
>            1) lists and tuples which can use PySequence_Fast ops
>            2) extending self to self requires making a copy first
> @@ -851,6 +934,7 @@
>  {
>         PyObject *result;
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "concatentation");
>         result = listextend(self, other);
>         if (result == NULL)
>                 return result;
> @@ -866,6 +950,7 @@
>         PyObject *v, *arg = NULL;
>         int status;
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "pop");
>         if (!PyArg_UnpackTuple(args, "pop", 0, 1, &arg))
>                 return NULL;
>         if (arg != NULL) {
> @@ -1995,6 +2080,8 @@
>         PyObject *key, *value, *kvpair;
>         static char *kwlist[] = {"cmp", "key", "reverse", 0};
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "sort");
> +
>         assert(self != NULL);
>         assert (PyList_Check(self));
>         if (args != NULL) {
> @@ -2163,6 +2250,7 @@
>  static PyObject *
>  listreverse(PyListObject *self)
>  {
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "reverse");
>         if (self->ob_size > 1)
>                 reverse_slice(self->ob_item, self->ob_item + self->ob_size);
>         Py_RETURN_NONE;
> @@ -2213,6 +2301,7 @@
>         Py_ssize_t i, start=0, stop=self->ob_size;
>         PyObject *v;
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "index");
>         if (!PyArg_ParseTuple(args, "O|O&O&:index", &v,
>                                     _PyEval_SliceIndex, &start,
>                                     _PyEval_SliceIndex, &stop))
> @@ -2244,6 +2333,7 @@
>         Py_ssize_t count = 0;
>         Py_ssize_t i;
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "count");
>         for (i = 0; i < self->ob_size; i++) {
>                 int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
>                 if (cmp > 0)
> @@ -2259,6 +2349,7 @@
>  {
>         Py_ssize_t i;
>
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "remove");
>         for (i = 0; i < self->ob_size; i++) {
>                 int cmp = PyObject_RichCompareBool(self->ob_item[i], v, Py_EQ);
>                 if (cmp > 0) {
> @@ -2372,6 +2463,7 @@
>                self->allocated == 0 || self->allocated == -1);
>
>         /* Empty previous contents */
> +       self->future_type = PY_REMAIN_LIST;
>         if (self->ob_item != NULL) {
>                 (void)list_clear(self);
>         }
> @@ -2456,6 +2548,8 @@
>  static PyObject *
>  list_subscript(PyListObject* self, PyObject* item)
>  {
> +       WARN_LIST_USAGE(self, PY_REMAIN_LIST, "__getitem__");
> +
>         if (PyIndex_Check(item)) {
>                 Py_ssize_t i;
>                 i = PyNumber_AsSsize_t(item, PyExc_IndexError);
> @@ -2505,6 +2599,7 @@
>  static int
>  list_ass_subscript(PyListObject* self, PyObject* item, PyObject* value)
>  {
> +       WARN_LIST_USAGE_INT(self, PY_REMAIN_LIST, "item assignment");
>         if (PyIndex_Check(item)) {
>                 Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
>                 if (i == -1 && PyErr_Occurred())
> @@ -2874,6 +2969,7 @@
>  {
>         listreviterobject *it;
>
> +       WARN_LIST_USAGE(seq, PY_REMAIN_LIST, "reversed");
>         it = PyObject_GC_New(listreviterobject, &PyListRevIter_Type);
>         if (it == NULL)
>                 return NULL;
>
>
> _______________________________________________
> Python-3000 mailing list
> Python-3000 at python.org
> http://mail.python.org/mailman/listinfo/python-3000
> Unsubscribe: http://mail.python.org/mailman/options/python-3000/guido%40python.org
>
>
>


-- 
--Guido van Rossum (home page: http://www.python.org/~guido/)


More information about the Python-3000 mailing list