8000 gh-71759: Deprecate using bytes-like objects in builtins. by serhiy-storchaka · Pull Request #779 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-71759: Deprecate using bytes-like objects in builtins. #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10000
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ Deprecated

(Contributed by Berker Peksag in :issue:`9372`.)

* Deprecated using :term:`bytes-like objects <bytes-like object>` except
:class:`bytes` and :class:`bytearray` in builtins :class:`int`,
:class:`float`, :func:`compile`, :func:`eval` and :func:`exec`.
(Contributed by Serhiy Storchaka in :issue:`27572`.)

* The :class:`typing.NamedTuple` class has deprecated the ``_field_types``
attribute in favor of the ``__annotations__`` attribute which has the same
information. (Contributed by Raymond Hettinger in :issue:`36320`.)
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ def test_compile(self):
compile(source='pass', filename='?', mode='exec')
compile(dont_inherit=0, filename='tmp', source='0', mode='eval')
compile('pass', '?', dont_inherit=1, mode='exec')
compile(memoryview(b"text"), "name", "exec")
with self.assertWarns(DeprecationWarning):
compile(memoryview(b"text"), "name", "exec")
self.assertRaises(TypeError, compile)
self.assertRaises(ValueError, compile, 'print(42)\n', '<string>', 'badmode')
self.assertRaises(ValueError, compile, 'print(42)\n', '<string>', 'single', 0xff)
Expand Down
18 changes: 12 additions & 6 deletions Lib/test/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,19 +562,25 @@ def test_null_terminated(self):
# objects are accepted, which could be not terminated.
with self.assertRaisesRegex(ValueError, "cannot contain null"):
compile("123\x00", "<dummy>", "eval")
with self.assertRaisesRegex(ValueError, "cannot contain null"):
with self.assertRaisesRegex(ValueError, "cannot contain null"), \
self.assertWarns(DeprecationWarning):
compile(memoryview(b"123\x00"), "<dummy>", "eval")
code = compile(memoryview(b"123\x00")[1:-1], "<dummy>", "eval")
with self.assertWarns(DeprecationWarning):
code = compile(memoryview(b"123\x00")[1:-1], "<dummy>", "eval")
self.assertEqual(eval(code), 23)
code = compile(memoryview(b"1234")[1:-1], "<dummy>", "eval")
with self.assertWarns(DeprecationWarning):
code = compile(memoryview(b"1234")[1:-1], "<dummy>", "eval")
self.assertEqual(eval(code), 23)
code = compile(memoryview(b"$23$")[1:-1], "<dummy>", "eval")
with self.assertWarns(DeprecationWarning):
code = compile(memoryview(b"$23$")[1:-1], "<dummy>", "eval")
self.assertEqual(eval(code), 23)

# Also test when eval() and exec() do the compilation step
self.assertEqual(eval(memoryview(b"1234")[1:-1]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(eval(memoryview(b"1234")[1:-1]), 23)
namespace = dict()
exec(memoryview(b"ax = 123")[1:-1], namespace)
with self.assertWarns(DeprecationWarning):
exec(memoryview(b"ax = 123")[1:-1], namespace)
self.assertEqual(namespace['x'], 12)

def check_constant(self, func, expected):
Expand Down
46 changes: 25 additions & 21 deletions Lib/test/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,38 +88,42 @@ def test_underscores(self):
def test_non_numeric_input_types(self):
# Test possible non-numeric types for the argument x, including
# subclasses of the explicitly documented accepted types.
def check(f):
x = f(b" 3.14 ")
self.assertEqual(float(x), 3.14)
with self.assertRaisesRegex(ValueError, "could not convert"):
float(f(b'A' * 0x10))

class CustomStr(str): pass
class CustomBytes(bytes): pass
class CustomByteArray(bytearray): pass

factories = [
bytes,
bytearray,
lambda b: CustomStr(b.decode()),
CustomBytes,
CustomByteArray,
memoryview,
]
check(bytes)
check(bytearray)
check(lambda b: CustomStr(b.decode()))
check(CustomBytes)
check(CustomByteArray)
with self.assertWarns(DeprecationWarning):
check(memoryview)
try:
from array import array
except ImportError:
pass
else:
factories.append(lambda b: array('B', b))

for f in factories:
x = f(b" 3.14 ")
with self.subTest(type(x)):
self.assertEqual(float(x), 3.14)
with self.assertRaisesRegex(ValueError, "could not convert"):
float(f(b'A' * 0x10))
with self.assertWarns(DeprecationWarning):
check(lambda b: array('B', b))

def test_float_memoryview(self):
self.assertEqual(float(memoryview(b'12.3')[1:4]), 2.3)
self.assertEqual(float(memoryview(b'12.3\x00')[1:4]), 2.3)
self.assertEqual(float(memoryview(b'12.3 ')[1:4]), 2.3)
self.assertEqual(float(memoryview(b'12.3A')[1:4]), 2.3)
self.assertEqual(float(memoryview(b'12.34')[1:4]), 2.3)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(memoryview(b'12.3')[1:4]), 2.3)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(memoryview(b'12.3\x00')[1:4]), 2.3)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(memoryview(b'12.3 ')[1:4]), 2.3)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(memoryview(b'12.3A')[1:4]), 2.3)
with self.assertWarns(DeprecationWarning):
self.assertEqual(float(memoryview(b'12.34')[1:4]), 2.3)

def test_error_message(self):
def check(s):
Expand Down
58 changes: 31 additions & 27 deletions Lib/test/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,44 +298,48 @@ def __index__(self):
def test_non_numeric_input_types(self):
# Test possible non-numeric types for the argument x, including
# subclasses of the explicitly documented accepted types.
def check(f):
x = f(b'100')
self.assertEqual(int(x), 100)
if isinstance(x, (str, bytes, bytearray)):
self.assertEqual(int(x, 2), 4)
else:
msg = "can't convert non-string"
with self.assertRaisesRegex(TypeError, msg):
int(x, 2)
with self.assertRaisesRegex(ValueError, 'invalid literal'):
int(f(b'A' * 0x10))

class CustomStr(str): pass
class CustomBytes(bytes): pass
class CustomByteArray(bytearray): pass

factories = [
bytes,
bytearray,
lambda b: CustomStr(b.decode()),
CustomBytes,
CustomByteArray,
memoryview,
]
check(bytes)
check(bytearray)
check(lambda b: CustomStr(b.decode()))
check(CustomBytes)
check(CustomByteArray)
with self.assertWarns(DeprecationWarning):
check(memoryview)
try:
from array import array
except ImportError:
pass
else:
factories.append(lambda b: array('B', b))

for f in factories:
x = f(b'100')
with self.subTest(type(x)):
self.assertEqual(int(x), 100)
if isinstance(x, (str, bytes, bytearray)):
self.assertEqual(int(x, 2), 4)
else:
msg = "can't convert non-string"
with self.assertRaisesRegex(TypeError, msg):
int(x, 2)
with self.assertRaisesRegex(ValueError, 'invalid literal'):
int(f(b'A' * 0x10))
with self.assertWarns(DeprecationWarning):
check(lambda b: array('B', b))

def test_int_memoryview(self):
self.assertEqual(int(memoryview(b'123')[1:3]), 23)
self.assertEqual(int(memoryview(b'123\x00')[1:3]), 23)
self.assertEqual(int(memoryview(b'123 ')[1:3]), 23)
self.assertEqual(int(memoryview(b'123A')[1:3]), 23)
self.assertEqual(int(memoryview(b'1234')[1:3]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(memoryview(b'123')[1:3]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(memoryview(b'123\x00')[1:3]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(memoryview(b'123 ')[1:3]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(memoryview(b'123A')[1:3]), 23)
with self.assertWarns(DeprecationWarning):
self.assertEqual(int(memoryview(b'1234')[1:3]), 23)

def test_string_float(self):
self.assertRaises(ValueError, int, '1.2')
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,12 +572,12 @@ def test_pdb_run_with_incorrect_argument():
>>> with pti:
... pdb_invoke('run', lambda x: x)
Traceback (most recent call last):
TypeError: exec() arg 1 must be a string, bytes or code object
TypeError: exec() arg 1 must be a string, bytes, bytearray or code object

>>> with pti:
... pdb_invoke('runeval', lambda x: x)
Traceback (most recent call last):
TypeError: eval() arg 1 must be a string, bytes or code object
TypeError: eval() arg 1 must be a string, bytes, bytearray or code object
"""


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Deprecated using bytes-like objects except bytes and bytearray in builtins
int(), float(), compile(), eval() and exec().
10 changes: 8 additions & 2 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,12 @@ PyNumber_Long(PyObject *o)
if (PyObject_GetBuffer(o, &view, PyBUF_SIMPLE) == 0) {
PyObject *bytes;

if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"int() argument must be a string, a bytes object, "
"a bytearray or a number, not '%.100s'",
o->ob_type->tp_name)) {
return NULL;
}
/* Copy to NUL-terminated buffer. */
bytes = PyBytes_FromStringAndSize((const char *)view.buf, view.len);
if (bytes == NULL) {
Expand All @@ -1436,8 +1442,8 @@ PyNumber_Long(PyObject *o)
return result;
}

return type_error("int() argument must be a string, a bytes-like object "
"or a number, not '%.200s'", o);
return type_error("int() argument must be a string, a bytes object, "
"a bytearray or a number, not '%.200s'", o);
}

PyObject *
Expand Down
9 changes: 8 additions & 1 deletion Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ PyFloat_FromString(PyObject *v)
len = PyByteArray_GET_SIZE(v);
}
else if (PyObject_GetBuffer(v, &view, PyBUF_SIMPLE) == 0) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"float() argument must be a string, a bytes object, "
"a bytearray or a number, not '%.100s'",
v->ob_type->tp_name)) {
return NULL;
}
s = (const char *)view.buf;
len = view.len;
/* Copy to NUL-terminated buffer. */
Expand All @@ -201,7 +207,8 @@ PyFloat_FromString(PyObject *v)
}
else {
PyErr_Format(PyExc_TypeError,
"float() argument must be a string or a number, not '%.200s'",
"float() argument must be a string, a bytes object, "
"a bytearray or a number, not '%.200s'",
Py_TYPE(v)->tp_name);
return NULL;
}
Expand Down
12 changes: 9 additions & 3 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,12 @@ source_as_string(PyObject *cmd, const char *funcname, const char *what, PyCompil
size = PyByteArray_GET_SIZE(cmd);
}
else if (PyObject_GetBuffer(cmd, &view, PyBUF_SIMPLE) == 0) {
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"%s() arg 1 must be a %s object, not '%.100s'",
funcname, what,
cmd->ob_type->tp_name)) {
return NULL;
}
/* Copy to NUL-terminated buffer. */
*cmd_copy = PyBytes_FromStringAndSize(
(const char *)view.buf, view.len);
Expand Down Expand Up @@ -855,7 +861,7 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
goto finally;
}

str = source_as_string(source, "compile", "string, bytes or AST", &cf, &source_copy);
str = source_as_string(source, "compile", "string, bytes, bytearray or AST", &cf, &source_copy);
if (str == NULL)
goto error;

Expand Down Expand Up @@ -987,7 +993,7 @@ builtin_eval_impl(PyObject *module, PyObject *source, PyObject *globals,

cf.cf_flags = PyCF_SOURCE_IS_UTF8;
cf.cf_feature_version = PY_MINOR_VERSION;
str = source_as_string(source, "eval", "string, bytes or code", &cf, &source_copy);
str = source_as_string(source, "eval", "string, bytes, bytearray or code", &cf, &source_copy);
if (str == NULL)
return NULL;

Expand Down Expand Up @@ -1076,7 +1082,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
cf.cf_feature_version = PY_MINOR_VERSION;
str = source_as_string(source, "exec",
"string, bytes or code", &cf,
"string, bytes, bytearray or code", &cf,
&source_copy);
if (str == NULL)
return NULL;
Expand Down
0