Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

bpo-35113: Fix inspect.getsource to return correct source for inner classes#10307

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
tirkarthi merged 17 commits intopython:masterfromtirkarthi:bpo35113
Apr 18, 2020

Conversation

tirkarthi
Copy link
Member

@tirkarthitirkarthi commentedNov 3, 2018
edited
Loading

Previous approach used regex along with indentation of the match to return the source code. This caused below issues. This PR proposes usage of ast module to do correct parsing.

Issues

  1. When a class with same name is defined under different classes.
  2. When a class definition is part of a string declaration.
  3. When a class definition is part of multiline comment.

Examples of incorrect output can be found withhttps://bugs.python.org/issue35113.

This PR also adds returning class decorators as part of the source code instead of just returning the class definition.

Inner class example :

importinspectclassFoo:classBar:deffoo(self):passclassSpam:classBar:defbar(self):passprint(inspect.getsource(Spam.Bar))

On master

$ ./python.exe foo.py    class Bar:        def foo(self):            pass

IPython

inspect.getsource is also used in lot of tools like IPython and pdb which also return incorrect output due to this

ipythonPython3.6.4 (default,Mar122018,13:42:53)Type'copyright','credits'or'license'formoreinformationIPython6.3.1--AnenhancedInteractivePython.Type'?'forhelp.In [1]:importfooIn [2]:foo.Spam.Bar??Initsignature:foo.Spam.Bar()Docstring:<nodocstring>Source:classBar:deffoo(self):passFile:~/stuff/python/backups/foo.pyType:           type$python3foo.py>/Users/karthikeyansingaravelan/stuff/python/backups/foo.py(13)<module>()->if__name__=="__main__":(Pdb)pdb.getsourcelines(Spam.Bar)(['    class Bar:\n','        def foo(self):\n','            pass\n'],2)

With PR

$ ./python.exe foo.py    class Bar:        def bar(self):            pass

Class decorator example

importinspectdeffoo(cls):returncls@foo@fooclassFoo:passprint(inspect.getsource(Foo))

On master

$ ./python.exe foo.pyclass Foo:    pass

With PR

$ ./python.exe foo.py@foo@fooclass Foo:    pass

https://bugs.python.org/issue35113


class ClassVisitor(ast.NodeVisitor):

def recursive(func):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This decorator is only used once, and it is not general. I think the code would be simpler if just adds corresponding code invisit_ClassDef().

set_top_level_class = True
self.stack.append(node.name)

qualname = '.'.join(self.stack)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Just inline it.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I feel this is more readable that I am joining the stack to form qualname thanif self.qualname == '.'join(self.stack) . Let me know if it needs to be inlined.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It is needed if use local variables from the outer scope. Then the condition will beif qualname == '.'join(stack).

@serhiy-storchaka
Copy link
Member

Next step. The classes can be nested not only in other classes, but in functions, which can be nested in classes or functions, etc.

  1. Remove theisinstance(child, ast.ClassDef) check in the loop.
  2. Add visitors for FuncDef and AsyncFuncDef. They should just push/pop the name and'<locals>' on the stack and iterate child nodes.
  3. Add corresponding tests. Something like:
deffunc123():classclass234:passreturnclass234classclass345:deffunc456(self):classclass567:passreturnclass567

etc (and similar for async functions).


class ClassVisitor(ast.NodeVisitor):

def __init__(self, qualname):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Since the class is not used outside offindsource() you can simplify the code by removing__init__. Inside the visitor you can just use local variables from the outer scope. This will save several lines. Addnonlocal line_number because it is set inside the visitor.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I tried removing the constructor and using local variables which removes use ofself as inself.stack. But since stack is changed in three functions now I have to declarenonlocal on three and make other changes. After conversion I think using class variables is more self-contained and I have kept them. Let me know if you need to change them to use local variables.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No, you don't have to usenonlocal with a stack, because you don't assign to this variable in these methods. Onlyline_number needs it, at only in a single place. I prefer more compact code, but it is up to you using the construct and instance attributes.

set_top_level_class = True
self.stack.append(node.name)

qualname = '.'.join(self.stack)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It is needed if use local variables from the outer scope. Then the condition will beif qualname == '.'join(stack).

@tirkarthi
Copy link
MemberAuthor

Thanks, I will work on them. It seems there is no test for similar to your examples since my PR raises OSError for your examples meanwhile master works. I guess my code breaks these cases which was handled by the previous regex based approach.

deffunc123():classclass234:deffunc(self):passreturnclass234classclass345:deffunc456(self):classclass567:passreturnclass567if__name__=="__main__":importinspectprint(inspect.getsource(func123()))print(inspect.getsource(class345().func456()))
$ python3 foo_1.py    class class234:        def func(self):            pass        class class567:            pass$ ../cpython/python.exe foo_1.pyTraceback (most recent call last):  File "foo_1.py", line 15, in <module>    print(inspect.getsource(func123()))  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/inspect.py", line 993, in getsource    lines, lnum = getsourcelines(object)  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/inspect.py", line 975, in getsourcelines    lines, lnum = findsource(object)  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/inspect.py", line 832, in findsource    raise OSError('could not find class definition')OSError: could not find class definition

I will fix the rest of the PR comments too in next commit.

@tirkarthi
Copy link
MemberAuthor

One side effect of the change is thatinspect.getcomments also works correctly. It caused a failure in test_pydoc athttps://github.com/python/cpython/blob/master/Lib/test/test_pydoc.py#L479 .

pydoc'srender_doc adds some more content usinginspect.getdoc or comments of a class usinginspect.getcomments if either of them is present at

result=inspect.getdoc(object)orinspect.getcomments(object)
. Since on master the class prediction is wrong for two classes of same name the below on Python 3 returns None forgetcomments() though there is a valid comment# 124. This causes the comment to be rendered on pydoc. In the linkedtest_pydoc function there was a comment which was also rendered along with the doc forA() causing test failure. So I have added an empty line between theclass A and comment at 4d11679a9ab3997280a02ce6b5de37566d984444 to fix it.

importpydocdeffunc123():# 124classclass234:passreturnclass234classclass234:passif__name__=="__main__":importinspectprint(inspect.getcomments(func123()))print(pydoc.render_doc(func123()))
➜  backups python3 foo_3.pyNonePython Library Documentation: class class234 in module __main__class class234(builtins.object) |  Data descriptors defined here: | |  __dict__ |      dictionary for instance variables (if defined) | |  __weakref__ |      list of weak references to the object (if defined)
➜  backups ../cpython/python.exe foo_3.py# 124Python Library Documentation: class class234 in module __main__class class234(builtins.object) |  # 124 | |  Data descriptors defined here: | |  __dict__ |      dictionary for instance variables (if defined) | |  __weakref__ |      list of weak references to the object (if defined)

Seems there is an altered environment failure with my test I will look into it. I think I am running the asyncdef version of the test correctly athttps://github.com/python/cpython/pull/10307/files#diff-0258c7716015b67c231e0d2a60ebbd31R711. Please add in your thoughts.

Thanks!

@tirkarthi
Copy link
MemberAuthor

Looking at the failure and other tests I think I need to callasyncio.set_event_loop_policy(None) in the end as a teardown action.

Current test

./python.exe -m test --fail-env-changed test_inspectRun tests sequentially0:00:00 load avg: 2.47 [1/1] test_inspectWarning -- asyncio.events._event_loop_policy was modified by test_inspect  Before: None  After:  <asyncio.unix_events._UnixDefaultEventLoopPolicy object at 0x1109e7050>test_inspect failed (env changed)== Tests result: ENV CHANGED ==1 test altered the execution environment:    test_inspect

set event loop policy in the end

deftest_nested_class_definition_inside_async_function(self):importasyncioself.assertSourceEqual(asyncio.run(mod2.func226()),227,229)self.assertSourceEqual(mod2.cls227,233,237)self.assertSourceEqual(asyncio.run(mod2.cls227().func234()),235,236)asyncio.set_event_loop_policy(None)
./python.exe -m test --fail-env-changed test_inspectRun tests sequentially0:00:00 load avg: 2.33 [1/1] test_inspect== Tests result: SUCCESS ==1 test OK.Total duration: 2 sec 386 msTests result: SUCCESS


class ClassVisitor(ast.NodeVisitor):

def __init__(self, qualname):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No, you don't have to usenonlocal with a stack, because you don't assign to this variable in these methods. Onlyline_number needs it, at only in a single place. I prefer more compact code, but it is up to you using the construct and instance attributes.

self.stack.pop()
self.stack.pop()

def visit_AsyncFunctionDef(self, node):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Justvisit_AsyncFunctionDef = visit_FunctionDef.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks, I will add this. I will also refactor class to use local variables. My assumption was thatstack.append requires stack variable to be nonlocal since it mutatesstack. I was wrong on that part. Thanks for clearing up and I will change it as suggested to use variables from outer scope making it more compact 👍

class cls205:
pass
class cls207:
class cls208:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Maybe usecls205 for testing classes with same name but different__qualname__?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Usedcls205 in the test. Thanks.

# line 211
def func212():
class cls213:
def func(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This method is not needed.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Removed. Thanks

# line 225
async def func226():
class cls227:
def func(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This method is not needed.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Removed. Thanks

self.assertSourceEqual(asyncio.run(mod2.func226()), 227, 229)
self.assertSourceEqual(mod2.cls227, 233, 237)
self.assertSourceEqual(asyncio.run(mod2.cls227().func234()), 235, 236)
asyncio.set_event_loop_policy(None)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

UseaddCleanup().

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Seems this test class inherits fromunittest.Test andself.addCleanup(asyncio.set_event_loop_policy, None) doesn't work. Should I useasyncio.set_event_loop_policy(None) or change the base class from which it's inherited?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I tried addingclass TestBuggyCases(GetSourceBase, test_utils.TestCase): but it seems it expects all the test cases to setup an event loop and gives below error. Any other better way to do this?

======================================================================ERROR: test_with_comment_instead_of_docstring (test.test_inspect.TestBuggyCases)----------------------------------------------------------------------Traceback (most recent call last):  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/test/test_asyncio/utils.py", line 533, in tearDown    self.unpatch_get_running_loop()  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/test/test_asyncio/utils.py", line 525, in unpatch_get_running_loop    events._get_running_loop = self._get_running_loopAttributeError: 'TestBuggyCases' object has no attribute '_get_running_loop'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why it doesn't work? There is nounittest.Test, this class inherits fromunittest.TestCase.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Sorry, it works. I think I was having test failures and tested it during that time causing confusion that it's an environment change. Added the change as part of c6e5e2c133069bcb45568b141636b6b78e2348d3 and verified locally.

./python.exe -m test --fail-env-changed test_inspectRun tests sequentially0:00:00 load avg: 2.57 [1/1] test_inspect== Tests result: SUCCESS ==1 test OK.Total duration: 2 sec 345 msTests result: SUCCESS

@@ -0,0 +1,3 @@
:meth:`inspect.getsource` now returns correct source code for inner class
with same name as module level class. class decorators are also returned as
part of the source.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Add "Path by ...".

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Added patch attribute. Thanks :)

Copy link
Member

@serhiy-storchakaserhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Just few nitpicks.

But see also the comment on the tracker.

class_visitor.visit(tree)

if line_number is not None:
line_number = line_number

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Not needed.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Removed. Thanks.

self.assertSourceEqual(asyncio.run(mod2.func225()), 226, 227)
self.assertSourceEqual(mod2.cls226, 231, 235)
self.assertSourceEqual(asyncio.run(mod2.cls226().func232()), 233, 234)
self.addCleanup(asyncio.set_event_loop_policy, None)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It should be called before the firstasyncio.run(). The purpose of usingaddCleanup is that the clean up code should be executed if any code above fails.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks I moved it up before firstasyncio.run and tested the same.

# it's not used for other than this part.
import ast

class ClassVisitor(ast.NodeVisitor):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

ClassVisitor is not very informative name. It visits not only classes. Maybe name itClassFinder or something like?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Renamed toClassFinder and the corresponding variable below toclass_finder. Thanks.

@@ -0,0 +1,3 @@
:meth:`inspect.getsource` now returns correct source code for inner class
with same name as module level class. class decorators are also returned as

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
with same name as module level class.class decorators are also returned as
with same name as module level class.Class decorators are also returned as

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Changed. Thanks.

Copy link
Member

@matrixisematrixise left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

thanks for your contributions

return cls213

# line 217
class cls213:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should becls218

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This to check that the inner class which has the same namecls213 insidefunc212 doesn't conflict with this classcls213. Previously regex was used and hence there were cases where two classes with same name defined under different scopes might conflict with each other returning wrong results.

serhiy-storchaka reacted with thumbs up emoji
return cls226

# line 230
class cls226:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should becls231

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Same as my above comment but this is to check forasync def related code.

This to check that the inner class which has the same namecls226 insidefunc225 doesn't conflict with this classcls226. Previously regex was used and hence there were cases where two classes with same name defined under different scopes might conflict with each other returning wrong results.

serhiy-storchaka reacted with thumbs up emoji
else:
line_number = node.lineno
# decrement by one since lines starts with indexing by zero
line_number = line_number - 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

why not "line_number -= 1" ?

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

No specific reason to be honest :) I will update it along with other review comments in the second round of review. Thanks.

@tirkarthi
Copy link
MemberAuthor

@serhiy-storchaka The AST module takes some noticeable hit for very large files like machine generated python code with a lot of classes. The only optimization I tried was to usedeque instead oflist forstack but for this benchmark there was no noticeable effect which I assume is that deque is helpful for highly nested code whereappend andpop are performant and not needed for small amount ofappend andpop.

I just want to add it here as a known tradeoff and maybe I am benchmarking on a highly pathological case with 60k classes with 160k lines. The largest I came across in the wild was generating Python code classes from XML schema usinggenerateDS that generated 35k line file with around 4k classes. I prefer correctness over wrong results given the trade off but if there are any optimizations I am missing then I can add them and@1st1 might also have thoughts on the approach.

For general files like usinginspect.getsource on it's own module that is 3000 lines there was no significant impact.

# with patch$ ./python.exe -m timeit -s 'import inspect' 'inspect.getsource(inspect.getsource)'1000 loops, best of 5: 276 usec per loop# on 3.7$  python3 -m timeit -s 'import inspect' 'inspect.getsource(inspect.getsource)'1000 loops, best of 5: 279 usec per loop

I generated a large file with around 20000 classes with each 3 layer of nesting. On an optimized build the timings were as below :

withopen('foo_perf.py','a')asf:f.write('import inspect')foriinrange(20000):r='''class cls{0}:    class cls{0}:        class cls{0}:            def func{0}(self):                pass'''.format(i)f.write(r)f.write('print(inspect.getsource(cls1999.cls1999))')

On master (Incorrect result but performant)

time ./python.exe -m timeit -s 'import foo_perf' 'import inspect' 'inspect.getsource(foo_perf.cls999.cls999)'class cls1999:    class cls1999:        class cls1999:            def func1999(self):                pass50 loops, best of 5: 6.89 msec per loop./python.exe -m timeit -s 'import foo_perf' 'import inspect'   3.82s user 0.17s system 98% cpu 4.037 total

With patch (Correct result but very slow)

time ./python.exe -m timeit -s 'import foo_perf' 'import inspect' 'inspect.getsource(foo_perf.cls999.cls999)'    class cls1999:        class cls1999:            def func1999(self):                pass1 loop, best of 5: 914 msec per loop./python.exe -m timeit -s 'import foo_perf' 'import inspect'   7.94s user 0.48s system 94% cpu 8.864 total

@serhiy-storchaka
Copy link
Member

I don't think a deque has advantages over a list here. We push and pop from one end.

Stopping when the first candidate is found will make findingcls1999 in your example 10x faster. But it will not help in case ofcls19999.

Yes, parsing to AST is much slower than using a regex, and uses much more memory, but I don't think this is very large issue. In common cases the performance should be good enough.

tirkarthi reacted with thumbs up emoji

@tirkarthi
Copy link
MemberAuthor

@1st1 It would be great if you can review this

@1st1
Copy link
Member

Looks good to me in general. Here're a few questions:

  1. For the example from bpo:

    ifsys.platform=='win32':classArena(object):        ...else:classArena(object):        ...

    Which class will be returned? Serhiy mentioned that the current implementation returns the first one. Did you fix this implementation to do the same?

  2. This PR also adds returning class decorators as part of the source code instead of just returning the class definition.

    This might be a backwards compatibility issue. I'd prefer the behaviour to stay absolutely the same. We can add another function, or can consider adding a keyword-only flag togetsource to make it return decorators.

@tirkarthi
Copy link
MemberAuthor

Thanks@1st1 for the review.

Which class will be returned? Serhiy mentioned that the current implementation returns the first one. Did you fix this implementation to do the same?

I have modified the PR to behave like the current implementation to return the first class definition inside conditional clauses. Added a test athttps://github.com/python/cpython/pull/10307/files#diff-0258c7716015b67c231e0d2a60ebbd31R697

This might be a backwards compatibility issue. I'd prefer the behaviour to stay absolutely the same. We can add another function, or can consider adding a keyword-only flag to getsource to make it return decorators.

I guess@serhiy-storchaka brought this up since functions return decorators as part ofinspect.getsource. I have modified the PR and NEWS entry to make sure decorators are not returned like the original implementation. I will open a separate enhancement request on bpo for this.

@1st1
Copy link
Member

I guess@serhiy-storchaka brought this up since functions return decorators as part of inspect.getsource. I have modified the PR and NEWS entry to make sure decorators are not returned like the original implementation. I will open a separate enhancement request on bpo for this.

It's an interesting discrepancy. For the sake of making the API consistent we might consider breaking it slightly for classes and start returning decorators too in 3.8. I honestly don't think it's going to be a huge deal. I'm curious what@serhiy-storchaka thinks about that though.

tirkarthi reacted with thumbs up emoji

@serhiy-storchaka
Copy link
Member

I think it is good to start returning decorators too.

@tirkarthi
Copy link
MemberAuthor

Agreed with@serhiy-storchaka that this is a useful enhancement and makes the API consistent with functions. Since the decorator related code is reverted@1st1 please let me know on your decision so that I can add it back if needed.

Thanks

@tirkarthi
Copy link
MemberAuthor

Adding decorators to classes too was an enhancement proposed byhttps://bugs.python.org/issue15856 . I have currently reverted the decorator related code but I think it would be good to add it back to return decorators for consistency.

@tirkarthi
Copy link
MemberAuthor

@1st1 It will be helpful if you can review the changes. I have changed the code to return decorators too to resolvehttps://bugs.python.org/issue15856 and also it makes it consistent with source code for functions. Not sure if this can be merged after beta release given the decorator enhancement but I would be very happy if this makes it part of 3.8 beta 1.

Thanks

@tirkarthi
Copy link
MemberAuthor

@serhiy-storchaka I have rebased the branch with latest master since it was old. It will be helpful to have your review and re-approval so that I can merge this.

Copy link
Member

@serhiy-storchakaserhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

👍

I just have found a way to optimize the visitor, but it may complicate the implementation. Will try after merging this PR.

tirkarthi reacted with thumbs up emoji
@tirkarthitirkarthi merged commit696136b intopython:masterApr 18, 2020
@bedevere-bot
Copy link

@tirkarthi: Please replace# withGH- in the commit message next time. Thanks!

@tirkarthi
Copy link
MemberAuthor

Thanks Serhiy, I have merged the PR. Unfortunately, I had an issue with GitHub UI that my squashed message in the textbox didn't pass through and it just added the intermediate commit messages even after edit :(

@carljm
Copy link
Member

FWIW the performance regression due to this PR madeinspect.getsource unusable for us in an existing use case in a large code base. Just noting it here rather than filing an issue since it seems like this was already considered here and it was decided that performance ofinspect.getsource is not important. And I'm not sure it's really fixable anyway without reintroducing the bugs fixed by this PR, and it's too late for that. Just making a note for historical value to clarify that this scale of performance regression will likely bite someone.

itamaro reacted with thumbs up emoji

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@matrixisematrixisematrixise requested changes

@1st11st11st1 requested changes

@serhiy-storchakaserhiy-storchakaserhiy-storchaka approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

8 participants
@tirkarthi@serhiy-storchaka@1st1@bedevere-bot@pablogsal@carljm@matrixise@the-knights-who-say-ni

[8]ページ先頭

©2009-2025 Movatter.jp