New submission from Dan Snider <mr.assume.a...@gmail.com>:

I only just now realized that `dict.fromkeys('abc').keys() - 'bc'` returns 
{'a'} instead of raising an error like {*'abc'} - 'bc' would, which is really 
quite handy. 

The __xor__, __and__, and __sub__ methods of dict_keys (and items, assuming no 
there are no unhashable values) work just as set.symmetric_difference, 
set.intersection, and set.difference do, respectively.

>>> a, b, c, d = [*map(dict.keys, map(dict.fromkeys, 'abcd'))]
>>> ((a | 'a') | (b & 'b') | (c ^ 'c')) - d
{'b', 'a'}
>>> a, b, c, d = [*map(dict.items, map(dict.fromkeys, 'abcd'))]
>>> ((a | 'a') | (b & 'b') | (c ^ 'c')) - d
{'c', ('a', None), 'a', ('c', None)}

However, set objects are arbitrarily restricted to taking a set object for the 
second argument on these functions. As for the first example here, there is 
even code specifically there to handle a dictionary as the second argument, but 
it is unreachable when called through the dunder version. 

{<class 'list'>, <class 'dict'>, <class 'set'>}
>>> {list, set} | dict.fromkeys((dict, set))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'set' and 'dict'

>>> {*'abc'}.difference('cde')
{'b', 'a'}
>>> {*'abc'} - set('cde')
{'b', 'a'}
>>> {*'abc'} - 'cde'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'set' and 'str'

>>> {1,2,3}.symmetric_difference(b'\x00')
{0, 1, 2, 3}
>>> {1,2,3} ^ b'\x00'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for ^: 'set' and 'bytes'

The sources of set_and, set_sub, and set_xor all look like this. All they do is 
add a check that the second argument is a set and then simply call the same 
function their respective non-dunder method uses. They're so identical in fact 
that set_xor actually calls the exact same C function used in the PyMethodDef 
for set.symmetric_update:

static PyObject *
set_xor(PySetObject *so, PyObject *other)
{
    if (!PyAnySet_Check(so) || !PyAnySet_Check(other))
        Py_RETURN_NOTIMPLEMENTED;
    return set_symmetric_difference(so, other);
}
static PyMethodDef set_methods[] = {
/* ... */
{"symmetric_difference",(PyCFunction)set_symmetric_difference, 
 METH_O, symmetric_difference_doc},
/* ... */
};

All that's needed to fix this is to remove a total of 106 characters from 
setobject.c (4 x " || !PyAnySet_Check(other)").

----------
components: Interpreter Core, Library (Lib)
messages: 324060
nosy: bup
priority: normal
severity: normal
status: open
title: Remove needless set operator restriction
type: enhancement
versions: Python 3.7, Python 3.8

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue34497>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to