From 62d55a4d11fe25e8981e27e68ba080ab47c3f590 Mon Sep 17 00:00:00 2001 From: Elisha Hollander Date: Tue, 13 Jul 2021 16:02:30 +0300 Subject: [PATCH 01/11] Remove unnecessary pass statements (GH-27103) --- Lib/lib2to3/btm_utils.py | 1 - Lib/multiprocessing/managers.py | 1 - Lib/telnetlib.py | 1 - 3 files changed, 3 deletions(-) diff --git a/Lib/lib2to3/btm_utils.py b/Lib/lib2to3/btm_utils.py index ff76ba34047072..b61afdba693071 100644 --- a/Lib/lib2to3/btm_utils.py +++ b/Lib/lib2to3/btm_utils.py @@ -220,7 +220,6 @@ def reduce_tree(node, parent=None): else: #TODO: handle {min, max} repeaters raise NotImplementedError - pass #add children if details_node and new_node is not None: diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index b981b0e1cb8ed8..9c7e92faec9427 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -1337,7 +1337,6 @@ def __init__(self, *args, **kwargs): def __del__(self): util.debug(f"{self.__class__.__name__}.__del__ by pid {getpid()}") - pass def get_server(self): 'Better than monkeypatching for now; merge into Server ultimately' diff --git a/Lib/telnetlib.py b/Lib/telnetlib.py index 8ce053e881a309..ae88ea594746fd 100644 --- a/Lib/telnetlib.py +++ b/Lib/telnetlib.py @@ -489,7 +489,6 @@ def process_rawq(self): except EOFError: # raised by self.rawq_getchar() self.iacseq = b'' # Reset on EOF self.sb = 0 - pass self.cookedq = self.cookedq + buf[0] self.sbdataq = self.sbdataq + buf[1] From d4a5f0b659a2b8f206cfbdfd37fc36aedf77a71f Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 13 Jul 2021 09:42:56 -0400 Subject: [PATCH 02/11] bpo-35113: clean up duplicate import and comment (#27073) --- Lib/inspect.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index 89b2e722be8fad..7a2eefec5a80d4 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2096,10 +2096,6 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True): """Private helper to parse content of '__text_signature__' and return a Signature based on it. """ - # Lazy import ast because it's relatively heavy and - # it's not used for other than this function. - import ast - Parameter = cls._parameter_cls clean_signature, self_parameter, last_positional_only = \ From 2924bb1a566977efd45f335d6a94cd84d8047edf Mon Sep 17 00:00:00 2001 From: jsnklln Date: Tue, 13 Jul 2021 09:54:06 -0400 Subject: [PATCH 03/11] bpo-38741: Definition of multiple ']' in header configparser (GH-17129) Co-authored-by: Jason Killen Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> --- Lib/configparser.py | 2 +- Lib/test/test_configparser.py | 7 +++++++ .../next/Library/2019-11-12-18-59-33.bpo-38741.W7IYkq.rst | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2019-11-12-18-59-33.bpo-38741.W7IYkq.rst diff --git a/Lib/configparser.py b/Lib/configparser.py index 2f45e242b49426..042a5c74b696fb 100644 --- a/Lib/configparser.py +++ b/Lib/configparser.py @@ -563,7 +563,7 @@ class RawConfigParser(MutableMapping): # Regular expressions for parsing section headers and options _SECT_TMPL = r""" \[ # [ - (?P
[^]]+) # very permissive! + (?P
.+) # very permissive! \] # ] """ _OPT_TMPL = r""" diff --git a/Lib/test/test_configparser.py b/Lib/test/test_configparser.py index 9373a62072ef47..e9b03e6c62ef14 100644 --- a/Lib/test/test_configparser.py +++ b/Lib/test/test_configparser.py @@ -79,6 +79,7 @@ def basic_test(self, cf): 'Spacey Bar', 'Spacey Bar From The Beginning', 'Types', + 'This One Has A ] In It', ] if self.allow_no_value: @@ -130,6 +131,7 @@ def basic_test(self, cf): eq(cf.get('Types', 'float'), "0.44") eq(cf.getboolean('Types', 'boolean'), False) eq(cf.get('Types', '123'), 'strange but acceptable') + eq(cf.get('This One Has A ] In It', 'forks'), 'spoons') if self.allow_no_value: eq(cf.get('NoValue', 'option-without-value'), None) @@ -320,6 +322,8 @@ def test_basic(self): float {0[0]} 0.44 boolean {0[0]} NO 123 {0[1]} strange but acceptable +[This One Has A ] In It] + forks {0[0]} spoons """.format(self.delimiters, self.comment_prefixes) if self.allow_no_value: config_string += ( @@ -394,6 +398,9 @@ def test_basic_from_dict(self): "boolean": False, 123: "strange but acceptable", }, + "This One Has A ] In It": { + "forks": "spoons" + }, } if self.allow_no_value: config.update({ diff --git a/Misc/NEWS.d/next/Library/2019-11-12-18-59-33.bpo-38741.W7IYkq.rst b/Misc/NEWS.d/next/Library/2019-11-12-18-59-33.bpo-38741.W7IYkq.rst new file mode 100644 index 00000000000000..39d84ccea55b8c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-11-12-18-59-33.bpo-38741.W7IYkq.rst @@ -0,0 +1 @@ +:mod:`configparser`: using ']' inside a section header will no longer cut the section name short at the ']' \ No newline at end of file From 48a5aa7f128caf5a46e4326c1fd285cd5fc8e59d Mon Sep 17 00:00:00 2001 From: Kevin Follstad Date: Tue, 13 Jul 2021 06:57:05 -0700 Subject: [PATCH 04/11] bpo-44514: Add doctest testcleanup for configparser and bz2 (#26909) Add testcleanup section to configparser and bz2 documentation which removes temporary files created in the filesystem when 'make doctest' is run. --- Doc/library/bz2.rst | 5 +++++ Doc/library/configparser.rst | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Doc/library/bz2.rst b/Doc/library/bz2.rst index f6787ab120ed57..999892e95f4715 100644 --- a/Doc/library/bz2.rst +++ b/Doc/library/bz2.rst @@ -325,3 +325,8 @@ Writing and reading a bzip2-compressed file in binary mode: ... content = f.read() >>> content == data # Check equality to original object after round-trip True + +.. testcleanup:: + + import os + os.remove("myfile.bz2") diff --git a/Doc/library/configparser.rst b/Doc/library/configparser.rst index b0c2a2cfb06a47..2bb425930bba7f 100644 --- a/Doc/library/configparser.rst +++ b/Doc/library/configparser.rst @@ -46,6 +46,11 @@ can be customized by end users easily. import configparser +.. testcleanup:: + + import os + os.remove("example.ini") + Quick Start ----------- From 3b5b99da4b256a31933112f4a2385386149c19e1 Mon Sep 17 00:00:00 2001 From: andrei kulakov Date: Tue, 13 Jul 2021 10:07:56 -0400 Subject: [PATCH 05/11] bpo-43126: Expand docs on io.IOBase.readlines() method (#27061) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Langa --- Doc/library/io.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/io.rst b/Doc/library/io.rst index f9ffc19fac489d..0881015c7fad68 100644 --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -391,6 +391,9 @@ I/O Base Classes to control the number of lines read: no more lines will be read if the total size (in bytes/characters) of all lines so far exceeds *hint*. + *hint* values of ``0`` or less, as well as ``None``, are treated as no + hint. + Note that it's already possible to iterate on file objects using ``for line in file: ...`` without calling ``file.readlines()``. From 6252670732c68420c2a8b3f0259559e45eed7610 Mon Sep 17 00:00:00 2001 From: Clemens Brunner Date: Tue, 13 Jul 2021 18:25:12 +0200 Subject: [PATCH 06/11] Fix typos in Mac/README.rst (#27108) --- Mac/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mac/README.rst b/Mac/README.rst index 35bbfded7f175f..766c57f155a797 100644 --- a/Mac/README.rst +++ b/Mac/README.rst @@ -192,7 +192,7 @@ Building and using a framework-based Python on macOS 1. Why would I want a framework Python instead of a normal static Python? --------------------------------------------------------------------------- +------------------------------------------------------------------------- The main reason is because you want to create GUI programs in Python. With the exception of X11/XDarwin-based GUI toolkits all GUI programs need to be run @@ -206,7 +206,7 @@ only two places: "/Library/Framework/Python.framework" and "/Applications/Python " where ```` can be e.g. "3.8", "2.7", etc. This simplifies matters for users installing Python from a binary distribution if they want to get rid of it again. Moreover, -due to the way frameworks work, usera without admin privileges can install a +due to the way frameworks work, users without admin privileges can install a binary distribution in their home directory without recompilation. 2. How does a framework Python differ from a normal static Python? @@ -272,7 +272,7 @@ normal frameworkinstall which installs the Tools directory into distributions. What do all these programs do? -=============================== +============================== "IDLE.app" is an integrated development environment for Python: editor, debugger, etc. From 0ee0a740e12ec8568aafa033aa6bb08b265afe26 Mon Sep 17 00:00:00 2001 From: Konstantin-Glukhov Date: Wed, 14 Jul 2021 04:21:48 +0900 Subject: [PATCH 07/11] bpo-44572: On Windows, disconnect STDIN in platform._syscmd_ver() to prevent erroneous STDIN consumption (GH-27092) --- Lib/platform.py | 1 + .../NEWS.d/next/Windows/2021-07-13-15-32-49.bpo-44572.gXvhDc.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2021-07-13-15-32-49.bpo-44572.gXvhDc.rst diff --git a/Lib/platform.py b/Lib/platform.py index 02152f6fc9bf86..39c8ad587a8b78 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -280,6 +280,7 @@ def _syscmd_ver(system='', release='', version='', for cmd in ('ver', 'command /c ver', 'cmd /c ver'): try: info = subprocess.check_output(cmd, + stdin=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True, shell=True) diff --git a/Misc/NEWS.d/next/Windows/2021-07-13-15-32-49.bpo-44572.gXvhDc.rst b/Misc/NEWS.d/next/Windows/2021-07-13-15-32-49.bpo-44572.gXvhDc.rst new file mode 100644 index 00000000000000..6e074c59b8445b --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2021-07-13-15-32-49.bpo-44572.gXvhDc.rst @@ -0,0 +1 @@ +Avoid consuming standard input in the :mod:`platform` module \ No newline at end of file From 054e9c84ac7c394941bba3ea1829d14dce1243fc Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 14 Jul 2021 00:27:50 +0300 Subject: [PATCH 08/11] bpo-33346: Allow async comprehensions inside implicit async comprehensions (GH-6766) Co-authored-by: Pablo Galindo --- Doc/reference/expressions.rst | 11 ++- Doc/whatsnew/3.11.rst | 17 +++-- Lib/test/test_coroutines.py | 72 +++++++++++++++++++ .../2018-05-11-12-44-03.bpo-33346.ZgBkvB.rst | 3 + Python/compile.c | 14 ++-- Python/symtable.c | 9 ++- 6 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-05-11-12-44-03.bpo-33346.ZgBkvB.rst diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 5ad640ad35d615..aaa21349076ba2 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -218,9 +218,9 @@ A comprehension in an :keyword:`!async def` function may consist of either a :keyword:`!for` or :keyword:`!async for` clause following the leading expression, may contain additional :keyword:`!for` or :keyword:`!async for` clauses, and may also use :keyword:`await` expressions. -If a comprehension contains either :keyword:`!async for` clauses -or :keyword:`!await` expressions it is called an -:dfn:`asynchronous comprehension`. An asynchronous comprehension may +If a comprehension contains either :keyword:`!async for` clauses or +:keyword:`!await` expressions or other asynchronous comprehensions it is called +an :dfn:`asynchronous comprehension`. An asynchronous comprehension may suspend the execution of the coroutine function in which it appears. See also :pep:`530`. @@ -230,6 +230,11 @@ See also :pep:`530`. .. versionchanged:: 3.8 ``yield`` and ``yield from`` prohibited in the implicitly nested scope. +.. versionchanged:: 3.11 + Asynchronous comprehensions are now allowed inside comprehensions in + asynchronous functions. Outer comprehensions implicitly become + asynchronous. + .. _lists: diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 57e9667c15776d..7d2e4e81269e93 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -148,16 +148,19 @@ See :pep:`657` for more details. (Contributed by Pablo Galindo, Batuhan Taskaya and Ammar Askar in :issue:`43950`.) - Other Language Changes ====================== -A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in -:meth:`contextlib.ExitStack.enter_context` and -:meth:`contextlib.AsyncExitStack.enter_async_context` for objects which do not -support the :term:`context manager` or :term:`asynchronous context manager` -protocols correspondingly. -(Contributed by Serhiy Storchaka in :issue:`44471`.) +* Asynchronous comprehensions are now allowed inside comprehensions in + asynchronous functions. Outer comprehensions implicitly become + asynchronous. (Contributed by Serhiy Storchaka in :issue:`33346`.) + +* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in + :meth:`contextlib.ExitStack.enter_context` and + :meth:`contextlib.AsyncExitStack.enter_async_context` for objects which do not + support the :term:`context manager` or :term:`asynchronous context manager` + protocols correspondingly. + (Contributed by Serhiy Storchaka in :issue:`44471`.) * A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in :keyword:`with` and :keyword:`async with` statements for objects which do not diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 9b83244b5006d0..4350e185a247f1 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -28,6 +28,12 @@ def __await__(self): yield self.value +async def asynciter(iterable): + """Convert an iterable to an asynchronous iterator.""" + for x in iterable: + yield x + + def run_async(coro): assert coro.__class__ in {types.GeneratorType, types.CoroutineType} @@ -125,6 +131,11 @@ def bar(): for c in b] """, + """async def foo(): + def bar(): + [[async for i in b] for b in els] + """, + """async def foo(): def bar(): [i for i in els @@ -200,6 +211,13 @@ def bar(): [i for i in els if await i] """, + """def bar(): + [[i async for i in a] for a in elts] + """, + + """[[i async for i in a] for a in elts] + """, + """async def foo(): await """, @@ -2011,6 +2029,60 @@ async def f(): run_async(f()), ([], {1: 1, 2: 2, 3: 3})) + def test_nested_comp(self): + async def run_list_inside_list(): + return [[i + j async for i in asynciter([1, 2])] for j in [10, 20]] + self.assertEqual( + run_async(run_list_inside_list()), + ([], [[11, 12], [21, 22]])) + + async def run_set_inside_list(): + return [{i + j async for i in asynciter([1, 2])} for j in [10, 20]] + self.assertEqual( + run_async(run_set_inside_list()), + ([], [{11, 12}, {21, 22}])) + + async def run_list_inside_set(): + return {sum([i async for i in asynciter(range(j))]) for j in [3, 5]} + self.assertEqual( + run_async(run_list_inside_set()), + ([], {3, 10})) + + async def run_dict_inside_dict(): + return {j: {i: i + j async for i in asynciter([1, 2])} for j in [10, 20]} + self.assertEqual( + run_async(run_dict_inside_dict()), + ([], {10: {1: 11, 2: 12}, 20: {1: 21, 2: 22}})) + + async def run_list_inside_gen(): + gen = ([i + j async for i in asynciter([1, 2])] for j in [10, 20]) + return [x async for x in gen] + self.assertEqual( + run_async(run_list_inside_gen()), + ([], [[11, 12], [21, 22]])) + + async def run_gen_inside_list(): + gens = [(i async for i in asynciter(range(j))) for j in [3, 5]] + return [x for g in gens async for x in g] + self.assertEqual( + run_async(run_gen_inside_list()), + ([], [0, 1, 2, 0, 1, 2, 3, 4])) + + async def run_gen_inside_gen(): + gens = ((i async for i in asynciter(range(j))) for j in [3, 5]) + return [x for g in gens async for x in g] + self.assertEqual( + run_async(run_gen_inside_gen()), + ([], [0, 1, 2, 0, 1, 2, 3, 4])) + + async def run_list_inside_list_inside_list(): + return [[[i + j + k async for i in asynciter([1, 2])] + for j in [10, 20]] + for k in [100, 200]] + self.assertEqual( + run_async(run_list_inside_list_inside_list()), + ([], [[[111, 112], [121, 122]], [[211, 212], [221, 222]]])) + def test_copy(self): async def func(): pass coro = func() diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-05-11-12-44-03.bpo-33346.ZgBkvB.rst b/Misc/NEWS.d/next/Core and Builtins/2018-05-11-12-44-03.bpo-33346.ZgBkvB.rst new file mode 100644 index 00000000000000..9c91a8c0bf9ee4 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-05-11-12-44-03.bpo-33346.ZgBkvB.rst @@ -0,0 +1,3 @@ +Asynchronous comprehensions are now allowed inside comprehensions in +asynchronous functions. Outer comprehensions implicitly become +asynchronous. diff --git a/Python/compile.c b/Python/compile.c index 1feb97c93d8c2a..bb5c2eca960de4 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4947,11 +4947,9 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, PyCodeObject *co = NULL; comprehension_ty outermost; PyObject *qualname = NULL; + int scope_type = c->u->u_scope_type; int is_async_generator = 0; - int top_level_await = IS_TOP_LEVEL_AWAIT(c); - - - int is_async_function = c->u->u_ste->ste_coroutine; + int is_top_level_await = IS_TOP_LEVEL_AWAIT(c); outermost = (comprehension_ty) asdl_seq_GET(generators, 0); if (!compiler_enter_scope(c, name, COMPILER_SCOPE_COMPREHENSION, @@ -4963,7 +4961,11 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, is_async_generator = c->u->u_ste->ste_coroutine; - if (is_async_generator && !is_async_function && type != COMP_GENEXP && !top_level_await) { + if (is_async_generator && type != COMP_GENEXP && + scope_type != COMPILER_SCOPE_ASYNC_FUNCTION && + scope_type != COMPILER_SCOPE_COMPREHENSION && + !is_top_level_await) + { compiler_error(c, "asynchronous comprehension outside of " "an asynchronous function"); goto error_in_scope; @@ -5002,7 +5004,7 @@ compiler_comprehension(struct compiler *c, expr_ty e, int type, qualname = c->u->u_qualname; Py_INCREF(qualname); compiler_exit_scope(c); - if (top_level_await && is_async_generator){ + if (is_top_level_await && is_async_generator){ c->u->u_ste->ste_coroutine = 1; } if (co == NULL) diff --git a/Python/symtable.c b/Python/symtable.c index 4508464ef13f24..64c1635fba7603 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -2056,7 +2056,14 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e, return 0; } st->st_cur->ste_generator = is_generator; - return symtable_exit_block(st); + int is_async = st->st_cur->ste_coroutine && !is_generator; + if (!symtable_exit_block(st)) { + return 0; + } + if (is_async) { + st->st_cur->ste_coroutine = 1; + } + return 1; } static int From 0093876328afa330224c9d887c18dee0b3117852 Mon Sep 17 00:00:00 2001 From: "T. Wouters" Date: Wed, 14 Jul 2021 00:56:45 +0200 Subject: [PATCH 09/11] bpo-44630: Fix assertion errors in csv module (GH-27127) Fix incorrect handling of exceptions when interpreting dialect objects in the csv module. Not clearing exceptions between calls to PyObject_GetAttrString() causes assertion failures in pydebug mode (or with assertions enabled). Add a minimal test that would've caught this (passing None as dialect, or any object that isn't a csv.Dialect subclass, which the csv module allows and caters to, even though it is not documented.) In pydebug mode, the test triggers the assertion failure in the old code. Contributed-By: T. Wouters [Google] --- Lib/test/test_csv.py | 6 ++++++ Modules/_csv.c | 12 ++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 225f5c7289081a..18b86aa71a531f 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -458,9 +458,15 @@ class testC(csv.excel): class testUni(csv.excel): delimiter = "\u039B" + class unspecified(): + # A class to pass as dialect but with no dialect attributes. + pass + csv.register_dialect('testC', testC) try: self.compare_dialect_123("1,2,3\r\n") + self.compare_dialect_123("1,2,3\r\n", dialect=None) + self.compare_dialect_123("1,2,3\r\n", dialect=unspecified) self.compare_dialect_123("1\t2\t3\r\n", testA) self.compare_dialect_123("1:2:3\r\n", dialect=testB()) self.compare_dialect_123("1|2|3\r\n", dialect='testC') diff --git a/Modules/_csv.c b/Modules/_csv.c index 78855b871352c5..3109fd16bc744b 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -421,9 +421,14 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) Py_XINCREF(skipinitialspace); Py_XINCREF(strict); if (dialect != NULL) { -#define DIALECT_GETATTR(v, n) \ - if (v == NULL) \ - v = PyObject_GetAttrString(dialect, n) +#define DIALECT_GETATTR(v, n) \ + do { \ + if (v == NULL) { \ + v = PyObject_GetAttrString(dialect, n); \ + if (v == NULL) \ + PyErr_Clear(); \ + } \ + } while (0) DIALECT_GETATTR(delimiter, "delimiter"); DIALECT_GETATTR(doublequote, "doublequote"); DIALECT_GETATTR(escapechar, "escapechar"); @@ -432,7 +437,6 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) DIALECT_GETATTR(quoting, "quoting"); DIALECT_GETATTR(skipinitialspace, "skipinitialspace"); DIALECT_GETATTR(strict, "strict"); - PyErr_Clear(); } /* check types and convert to C values */ From 81989058de381108dfd0a4255b93d4fb34417002 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 14 Jul 2021 07:35:39 +0300 Subject: [PATCH 10/11] bpo-44606: Fix __instancecheck__ and __subclasscheck__ for the union type. (GH-27120) * Fix issubclass() for None. E.g. issubclass(type(None), int | None) returns now True. * Fix issubclass() for virtual subclasses. E.g. issubclass(dict, int | collections.abc.Mapping) returns now True. * Fix crash in isinstance() if the check for one of items raises exception. --- Lib/test/test_types.py | 37 ++++++++++++++++++- .../2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst | 1 + Objects/unionobject.c | 23 ++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 7f7ce86ff08ef3..f2c64649e1b670 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -661,6 +661,39 @@ def test_or_types_operator(self): x.__args__ = [str, int] (int | str ) == x + def test_instancecheck(self): + x = int | str + self.assertIsInstance(1, x) + self.assertIsInstance(True, x) + self.assertIsInstance('a', x) + self.assertNotIsInstance(None, x) + self.assertTrue(issubclass(int, x)) + self.assertTrue(issubclass(bool, x)) + self.assertTrue(issubclass(str, x)) + self.assertFalse(issubclass(type(None), x)) + x = int | None + self.assertIsInstance(None, x) + self.assertTrue(issubclass(type(None), x)) + x = int | collections.abc.Mapping + self.assertIsInstance({}, x) + self.assertTrue(issubclass(dict, x)) + + def test_bad_instancecheck(self): + class BadMeta(type): + def __instancecheck__(cls, inst): + 1/0 + x = int | BadMeta('A', (), {}) + self.assertTrue(isinstance(1, x)) + self.assertRaises(ZeroDivisionError, isinstance, [], x) + + def test_bad_subclasscheck(self): + class BadMeta(type): + def __subclasscheck__(cls, sub): + 1/0 + x = int | BadMeta('A', (), {}) + self.assertTrue(issubclass(int, x)) + self.assertRaises(ZeroDivisionError, issubclass, list, x) + def test_or_type_operator_with_TypeVar(self): TV = typing.TypeVar('T') assert TV | str == typing.Union[TV, str] @@ -754,9 +787,9 @@ def __eq__(self, other): for type_ in union_ga: with self.subTest(f"check isinstance/issubclass is invalid for {type_}"): with self.assertRaises(TypeError): - isinstance(list, type_) + isinstance(1, type_) with self.assertRaises(TypeError): - issubclass(list, type_) + issubclass(int, type_) def test_or_type_operator_with_bad_module(self): class TypeVar: diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst new file mode 100644 index 00000000000000..3c7ef3b21fe092 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-13-20-22-12.bpo-44606.S3Bv2w.rst @@ -0,0 +1 @@ +Fix ``__instancecheck__`` and ``__subclasscheck__`` for the union type. diff --git a/Objects/unionobject.c b/Objects/unionobject.c index d2a10dfec858ea..cf04de1bcc8095 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -70,8 +70,14 @@ union_instancecheck(PyObject *self, PyObject *instance) if (arg == Py_None) { arg = (PyObject *)&_PyNone_Type; } - if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) { - Py_RETURN_TRUE; + if (PyType_Check(arg)) { + int res = PyObject_IsInstance(instance, arg); + if (res < 0) { + return NULL; + } + if (res) { + Py_RETURN_TRUE; + } } } Py_RETURN_FALSE; @@ -93,8 +99,17 @@ union_subclasscheck(PyObject *self, PyObject *instance) Py_ssize_t nargs = PyTuple_GET_SIZE(alias->args); for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg); - if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) { - Py_RETURN_TRUE; + if (arg == Py_None) { + arg = (PyObject *)&_PyNone_Type; + } + if (PyType_Check(arg)) { + int res = PyObject_IsSubclass(instance, arg); + if (res < 0) { + return NULL; + } + if (res) { + Py_RETURN_TRUE; + } } } Py_RETURN_FALSE; From f572cbf1faab33d9afbbe3e95738ed6fbe6e48e6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 14 Jul 2021 08:19:18 +0300 Subject: [PATCH 11/11] bpo-44608: Fix memory leak in _tkinter._flatten() (GH-27107) if it is called with a sequence or set, but not list or tuple. --- Lib/test/test_tcl.py | 8 +++++++- .../next/Library/2021-07-13-09-01-33.bpo-44608.R3IcM1.rst | 2 ++ Modules/_tkinter.c | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-07-13-09-01-33.bpo-44608.R3IcM1.rst diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py index e7a60db7778202..6e5ef097c888f9 100644 --- a/Lib/test/test_tcl.py +++ b/Lib/test/test_tcl.py @@ -43,8 +43,14 @@ def get_tk_patchlevel(): class TkinterTest(unittest.TestCase): def testFlattenLen(self): - # flatten() + # Object without length. self.assertRaises(TypeError, _tkinter._flatten, True) + # Object with length, but not sequence. + self.assertRaises(TypeError, _tkinter._flatten, {}) + # Sequence or set, but not tuple or list. + # (issue44608: there were leaks in the following cases) + self.assertRaises(TypeError, _tkinter._flatten, 'string') + self.assertRaises(TypeError, _tkinter._flatten, {'set'}) class TclTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2021-07-13-09-01-33.bpo-44608.R3IcM1.rst b/Misc/NEWS.d/next/Library/2021-07-13-09-01-33.bpo-44608.R3IcM1.rst new file mode 100644 index 00000000000000..e0cf948f3cba68 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-13-09-01-33.bpo-44608.R3IcM1.rst @@ -0,0 +1,2 @@ +Fix memory leak in :func:`_tkinter._flatten` if it is called with a sequence +or set, but not list or tuple. diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 14101d9e3951c0..329b291729d581 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3197,8 +3197,10 @@ _tkinter__flatten(PyObject *module, PyObject *item) context.size = 0; - if (!_flatten1(&context, item,0)) + if (!_flatten1(&context, item, 0)) { + Py_XDECREF(context.tuple); return NULL; + } if (_PyTuple_Resize(&context.tuple, context.size)) return NULL;