Skip to content
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

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

Merged
merged 17 commits into from
Apr 18, 2020

Conversation

tirkarthi
Copy link
Member

@tirkarthi tirkarthi commented Nov 3, 2018

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 with https://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 :

import inspect

class Foo:
    class Bar:
        def foo(self):
            pass

class Spam:
    class Bar:
        def bar(self):
            pass

print(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

ipython
Python 3.6.4 (default, Mar 12 2018, 13:42:53)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import foo

In [2]: foo.Spam.Bar??
Init signature: foo.Spam.Bar()
Docstring:      <no docstring>
Source:
    class Bar:
        def foo(self):
            pass
File:           ~/stuff/python/backups/foo.py
Type:           type

$ python3 foo.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

import inspect

def foo(cls):
    return cls

@foo
@foo
class Foo:
    pass

print(inspect.getsource(Foo))

On master

$ ./python.exe foo.py
class Foo:
    pass

With PR

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

https://bugs.python.org/issue35113

Lib/inspect.py Outdated

class ClassVisitor(ast.NodeVisitor):

def recursive(func):
Copy link
Member

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 in visit_ClassDef().

Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated
set_top_level_class = True
self.stack.append(node.name)

qualname = '.'.join(self.stack)
Copy link
Member

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
Member Author

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 than if self.qualname == '.'join(self.stack) . Let me know if it needs to be inlined.

Copy link
Member

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 be if qualname == '.'join(stack).

Lib/inspect.py Outdated Show resolved Hide resolved
@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 the isinstance(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:
def func123():
    class class234:
        pass
    return class234

class class345:
    def func456(self):
        class class567:
            pass
        return class567

etc (and similar for async functions).

Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated

class ClassVisitor(ast.NodeVisitor):

def __init__(self, qualname):
Copy link
Member

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 of findsource() 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. Add nonlocal line_number because it is set inside the visitor.

Copy link
Member Author

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 of self as in self.stack. But since stack is changed in three functions now I have to declare nonlocal 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.

Copy link
Member

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 use nonlocal with a stack, because you don't assign to this variable in these methods. Only line_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.

Lib/inspect.py Outdated
set_top_level_class = True
self.stack.append(node.name)

qualname = '.'.join(self.stack)
Copy link
Member

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 be if qualname == '.'join(stack).

Lib/inspect.py Outdated Show resolved Hide resolved
Lib/inspect.py Outdated Show resolved Hide resolved
@tirkarthi
Copy link
Member Author

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.

def func123():
    class class234:
        def func(self):
            pass
    return class234

class class345:
    def func456(self):
        class class567:
            pass
        return class567

if __name__ == "__main__":
    import inspect
    print(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.py
Traceback (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
Member Author

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

pydoc's render_doc adds some more content using inspect.getdoc or comments of a class using inspect.getcomments if either of them is present at

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

import pydoc

def func123():
    # 124
    class class234:
        pass
    return class234

class class234:
    pass

if __name__ == "__main__":
    import inspect
    print(inspect.getcomments(func123()))
    print(pydoc.render_doc(func123()))
➜  backups python3 foo_3.py
None
Python 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
# 124

Python 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 at https://github.com/python/cpython/pull/10307/files#diff-0258c7716015b67c231e0d2a60ebbd31R711. Please add in your thoughts.

Thanks!

@tirkarthi
Copy link
Member Author

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

Current test

./python.exe -m test --fail-env-changed test_inspect
Run tests sequentially
0:00:00 load avg: 2.47 [1/1] test_inspect
Warning -- 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

    def test_nested_class_definition_inside_async_function(self):
        import asyncio
        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)
./python.exe -m test --fail-env-changed test_inspect
Run tests sequentially
0:00:00 load avg: 2.33 [1/1] test_inspect

== Tests result: SUCCESS ==

1 test OK.

Total duration: 2 sec 386 ms
Tests result: SUCCESS

@tirkarthi tirkarthi closed this Nov 3, 2018
@tirkarthi tirkarthi reopened this Nov 3, 2018
Lib/inspect.py Outdated

class ClassVisitor(ast.NodeVisitor):

def __init__(self, qualname):
Copy link
Member

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 use nonlocal with a stack, because you don't assign to this variable in these methods. Only line_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.

Lib/inspect.py Outdated
self.stack.pop()
self.stack.pop()

def visit_AsyncFunctionDef(self, node):
Copy link
Member

Choose a reason for hiding this comment

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

Just visit_AsyncFunctionDef = visit_FunctionDef.

Copy link
Member Author

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 that stack.append requires stack variable to be nonlocal since it mutates stack. 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:
Copy link
Member

Choose a reason for hiding this comment

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

Maybe use cls205 for testing classes with same name but different __qualname__?

Copy link
Member Author

Choose a reason for hiding this comment

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

Used cls205 in the test. Thanks.

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

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
Member Author

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):
Copy link
Member

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
Member Author

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)
Copy link
Member

Choose a reason for hiding this comment

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

Use addCleanup().

Copy link
Member Author

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 from unittest.Test and self.addCleanup(asyncio.set_event_loop_policy, None) doesn't work. Should I use asyncio.set_event_loop_policy(None) or change the base class from which it's inherited?

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried adding class 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_loop
AttributeError: 'TestBuggyCases' object has no attribute '_get_running_loop'

Copy link
Member

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 no unittest.Test, this class inherits from unittest.TestCase.

Copy link
Member Author

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_inspect
Run tests sequentially
0:00:00 load avg: 2.57 [1/1] test_inspect

== Tests result: SUCCESS ==

1 test OK.

Total duration: 2 sec 345 ms
Tests 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.
Copy link
Member

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
Member Author

Choose a reason for hiding this comment

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

Added patch attribute. Thanks :)

@serhiy-storchaka serhiy-storchaka requested a review from 1st1 November 3, 2018 18:17
Copy link
Member

@serhiy-storchaka serhiy-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.

Lib/inspect.py Outdated
class_visitor.visit(tree)

if line_number is not None:
line_number = line_number
Copy link
Member

Choose a reason for hiding this comment

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

Not needed.

Copy link
Member Author

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)
Copy link
Member

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 first asyncio.run(). The purpose of using addCleanup is that the clean up code should be executed if any code above fails.

Copy link
Member Author

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 first asyncio.run and tested the same.

Lib/inspect.py Outdated
# it's not used for other than this part.
import ast

class ClassVisitor(ast.NodeVisitor):
Copy link
Member

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 it ClassFinder or something like?

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed to ClassFinder and the corresponding variable below to class_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
Copy link
Member

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
Member Author

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

@matrixise matrixise 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 be cls218

Copy link
Member Author

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 name cls213 inside func212 doesn't conflict with this class cls213. 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.

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 be cls231

Copy link
Member Author

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 for async def related code.

This to check that the inner class which has the same name cls226 inside func225 doesn't conflict with this class cls226. 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.

Lib/inspect.py Outdated
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
Member Author

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
Member Author

@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 use deque instead of list for stack but for this benchmark there was no noticeable effect which I assume is that deque is helpful for highly nested code where append and pop are performant and not needed for small amount of append and pop.

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 using generateDS 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 using inspect.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 :

with open('foo_perf.py', 'a') as f:
    f.write('import inspect')
    for i in range(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):
                pass

50 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):
                pass

1 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 finding cls1999 in your example 10x faster. But it will not help in case of cls19999.

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
Copy link
Member Author

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

@1st1
Copy link
Member

1st1 commented Jan 25, 2019

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

  1. For the example from bpo:

    if sys.platform == 'win32':
        class Arena(object):
            ...
    else:
        class Arena(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 to getsource to make it return decorators.

@tirkarthi
Copy link
Member Author

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 at https://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 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.

@1st1
Copy link
Member

1st1 commented Jan 25, 2019

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.

@serhiy-storchaka
Copy link
Member

I think it is good to start returning decorators too.

@tirkarthi
Copy link
Member Author

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
Member Author

Adding decorators to classes too was an enhancement proposed by https://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
Member Author

@1st1 It will be helpful if you can review the changes. I have changed the code to return decorators too to resolve https://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

Lib/inspect.py Outdated Show resolved Hide resolved
@tirkarthi
Copy link
Member Author

@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-storchaka serhiy-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 tirkarthi merged commit 696136b into python:master Apr 18, 2020
@bedevere-bot
Copy link

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

@tirkarthi
Copy link
Member Author

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

carljm commented Dec 15, 2022

FWIW the performance regression due to this PR made inspect.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 of inspect.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants