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 all 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
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,11 @@ Deprecated
:exc:`ImportWarning`).
(Contributed by Brett Cannon in :gh:`65961`.)

* 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 :gh:`71759`.)

* The :meth:`~asyncio.DefaultEventLoopPolicy.get_event_loop` method of the
default event loop policy now emits a :exc:`DeprecationWarning` if there
is no current event loop set and it decides to create one.
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 @@ -338,7 +338,8 @@ def test_compile(self):
compile(source='pass', filename='?', mode='exec')
compile(dont_inherit=False, filename='tmp', source='0', mode='eval')
compile('pass', '?', dont_inherit=True, 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 @@ -591,19 +591,25 @@ def test_null_terminated(self):
# objects are accepted, which could be not terminated.
with self.assertRaisesRegex(SyntaxError, "cannot contain null"):
compile("123\x00", "<dummy>", "eval")
with self.assertRaisesRegex(SyntaxError, "cannot contain null"):
with self.assertRaisesRegex(SyntaxError, "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 @@ -93,38 +93,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 @@ -305,44 +305,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 @@ -761,12 +761,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 @@ -1599,6 +1599,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 real 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 @@ -1612,8 +1618,8 @@ PyNumber_Long(PyObject *o)
return result;
}

return type_error("int() argument must be a string, a bytes-like object "
"or a real number, not '%.200s'", o);
return type_error("int() argument must be a string, a bytes object, "
"a bytearray or a real 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 @@ -223,6 +223,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 real 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 @@ -235,7 +241,8 @@ PyFloat_FromString(PyObject *v)
}
else {
PyErr_Format(PyExc_TypeError,
"float() argument must be a string or a real number, not '%.200s'",
"float() argument must be a string, a bytes object, "
"a bytearray or a real number, not '%.200s'",
Py_TYPE(v)->tp_name);
return NULL;
}
Expand Down
6 changes: 3 additions & 3 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ builtin_compile_impl(PyObject *module, PyObject *source, PyObject *filename,
goto finally;
}

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

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

PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
str = _Py_SourceAsString(source, "eval", "string, bytes or code", &cf, &source_copy);
str = _Py_SourceAsString(source, "eval", "string, bytes, bytearray or code", &cf, &source_copy);
if (str == NULL)
return NULL;

Expand Down Expand Up @@ -1093,7 +1093,7 @@ builtin_exec_impl(PyObject *module, PyObject *source, PyObject *globals,
PyCompilerFlags cf = _PyCompilerFlags_INIT;
cf.cf_flags = PyCF_SOURCE_IS_UTF8;
str = _Py_SourceAsString(source, "exec",
"string, bytes or code", &cf,
"string, bytes, bytearray or code", &cf,
&source_copy);
if (str == NULL)
return NULL;
Expand Down
6 changes: 6 additions & 0 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,12 @@ _Py_SourceAsString(PyObject *cmd, const char *funcname, const char *what, PyComp
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
0