From 38580632ceac1cd6e477db71e1d190a4130beed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= Date: Wed, 30 Jun 2021 08:28:56 +0200 Subject: [PATCH] Rework linecache handling (#828) * Rework linecache handling * lint * Add changelog --- changelog.d/828.changes.rst | 1 + src/attr/_make.py | 56 +++++++++++++++---------------------- tests/test_dunders.py | 28 +++++++++++++++++-- 3 files changed, 49 insertions(+), 36 deletions(-) create mode 100644 changelog.d/828.changes.rst diff --git a/changelog.d/828.changes.rst b/changelog.d/828.changes.rst new file mode 100644 index 000000000..b4a5454c8 --- /dev/null +++ b/changelog.d/828.changes.rst @@ -0,0 +1 @@ +Generated source code is now cached more efficiently for identical classes. diff --git a/src/attr/_make.py b/src/attr/_make.py index 658eb047b..95a37ea64 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -5,7 +5,6 @@ import linecache import sys import threading -import uuid import warnings from operator import itemgetter @@ -329,16 +328,25 @@ def _make_method(name, script, filename, globs=None): if globs is None: globs = {} - _compile_and_eval(script, globs, locs, filename) - # In order of debuggers like PDB being able to step through the code, # we add a fake linecache entry. - linecache.cache[filename] = ( - len(script), - None, - script.splitlines(True), - filename, - ) + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + else: + filename = "{}-{}>".format(base_filename[:-1], count) + count += 1 + + _compile_and_eval(script, globs, locs, filename) return locs[name] @@ -1632,30 +1640,12 @@ def _generate_unique_filename(cls, func_name): """ Create a "filename" suitable for a function being generated. """ - unique_id = uuid.uuid4() - extra = "" - count = 1 - - while True: - unique_filename = "".format( - func_name, - cls.__module__, - getattr(cls, "__qualname__", cls.__name__), - extra, - ) - # To handle concurrency we essentially "reserve" our spot in - # the linecache with a dummy line. The caller can then - # set this value correctly. - cache_line = (1, None, (str(unique_id),), unique_filename) - if ( - linecache.cache.setdefault(unique_filename, cache_line) - == cache_line - ): - return unique_filename - - # Looks like this spot is taken. Try again. - count += 1 - extra = "-{0}".format(count) + unique_filename = "".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + ) + return unique_filename def _make_hash(cls, attrs, frozen, cache_hash): diff --git a/tests/test_dunders.py b/tests/test_dunders.py index ba8f3ce89..3a8300399 100644 --- a/tests/test_dunders.py +++ b/tests/test_dunders.py @@ -936,6 +936,16 @@ class C(object): pass +CopyC = C + + +@attr.s(hash=True, order=True) +class C(object): + """A different class, to generate different methods.""" + + a = attr.ib() + + class TestFilenames(object): def test_filenames(self): """ @@ -953,15 +963,27 @@ def test_filenames(self): OriginalC.__hash__.__code__.co_filename == "" ) + assert ( + CopyC.__init__.__code__.co_filename + == "" + ) + assert ( + CopyC.__eq__.__code__.co_filename + == "" + ) + assert ( + CopyC.__hash__.__code__.co_filename + == "" + ) assert ( C.__init__.__code__.co_filename - == "" + == "" ) assert ( C.__eq__.__code__.co_filename - == "" + == "" ) assert ( C.__hash__.__code__.co_filename - == "" + == "" )