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

Commit696136b

Browse files
authored
bpo-35113: Fix inspect.getsource to return correct source for inner classes (#10307)
* Use ast module to find class definition* Add NEWS entry* Fix class with multiple children and move decorator code to the method* Fix PR comments1. Use node.decorator_list to select decorators2. Remove unwanted variables in ClassVisitor3. Simplify stack management as per review* Add test for nested functions and async calls* Fix pydoc test since comments are returned now correctly* Set event loop policy as None to fix environment related change* Refactor visit_AsyncFunctionDef and tests* Refactor to use local variables and fix tests* Add patch attribution* Use self.addCleanup for asyncio* Rename ClassVisitor to ClassFinder and fix asyncio cleanup* Return first class inside conditional in case of multiple definitions. Remove decorator for class source.* Add docstring to make the test correct* Modify NEWS entry regarding decorators* Return decorators too forbpo-15856* Move ast and the class source code to top. Use proper Exception.
1 parentce57883 commit696136b

File tree

5 files changed

+200
-23
lines changed

5 files changed

+200
-23
lines changed

‎Lib/inspect.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'Yury Selivanov <yselivanov@sprymix.com>')
3333

3434
importabc
35+
importast
3536
importdis
3637
importcollections.abc
3738
importenum
@@ -770,6 +771,42 @@ def getmodule(object, _filename=None):
770771
ifbuiltinobjectisobject:
771772
returnbuiltin
772773

774+
775+
classClassFoundException(Exception):
776+
pass
777+
778+
779+
class_ClassFinder(ast.NodeVisitor):
780+
781+
def__init__(self,qualname):
782+
self.stack= []
783+
self.qualname=qualname
784+
785+
defvisit_FunctionDef(self,node):
786+
self.stack.append(node.name)
787+
self.stack.append('<locals>')
788+
self.generic_visit(node)
789+
self.stack.pop()
790+
self.stack.pop()
791+
792+
visit_AsyncFunctionDef=visit_FunctionDef
793+
794+
defvisit_ClassDef(self,node):
795+
self.stack.append(node.name)
796+
ifself.qualname=='.'.join(self.stack):
797+
# Return the decorator for the class if present
798+
ifnode.decorator_list:
799+
line_number=node.decorator_list[0].lineno
800+
else:
801+
line_number=node.lineno
802+
803+
# decrement by one since lines starts with indexing by zero
804+
line_number-=1
805+
raiseClassFoundException(line_number)
806+
self.generic_visit(node)
807+
self.stack.pop()
808+
809+
773810
deffindsource(object):
774811
"""Return the entire source file and starting line number for an object.
775812
@@ -802,25 +839,15 @@ def findsource(object):
802839
returnlines,0
803840

804841
ifisclass(object):
805-
name=object.__name__
806-
pat=re.compile(r'^(\s*)class\s*'+name+r'\b')
807-
# make some effort to find the best matching class definition:
808-
# use the one with the least indentation, which is the one
809-
# that's most probably not inside a function definition.
810-
candidates= []
811-
foriinrange(len(lines)):
812-
match=pat.match(lines[i])
813-
ifmatch:
814-
# if it's at toplevel, it's already the best one
815-
iflines[i][0]=='c':
816-
returnlines,i
817-
# else add whitespace to candidate list
818-
candidates.append((match.group(1),i))
819-
ifcandidates:
820-
# this will sort by whitespace, and by line number,
821-
# less whitespace first
822-
candidates.sort()
823-
returnlines,candidates[0][1]
842+
qualname=object.__qualname__
843+
source=''.join(lines)
844+
tree=ast.parse(source)
845+
class_finder=_ClassFinder(qualname)
846+
try:
847+
class_finder.visit(tree)
848+
exceptClassFoundExceptionase:
849+
line_number=e.args[0]
850+
returnlines,line_number
824851
else:
825852
raiseOSError('could not find class definition')
826853

‎Lib/test/inspect_fodder2.py

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,124 @@ def func137():
138138
never_reached1
139139
never_reached2
140140

141-
#line 141
141+
# line 141
142+
classcls142:
143+
a="""
144+
class cls149:
145+
...
146+
"""
147+
148+
# line 148
149+
classcls149:
150+
151+
deffunc151(self):
152+
pass
153+
154+
'''
155+
class cls160:
156+
pass
157+
'''
158+
159+
# line 159
160+
classcls160:
161+
162+
deffunc162(self):
163+
pass
164+
165+
# line 165
166+
classcls166:
167+
a='''
168+
class cls175:
169+
...
170+
'''
171+
172+
# line 172
173+
classcls173:
174+
175+
classcls175:
176+
pass
177+
178+
# line 178
179+
classcls179:
180+
pass
181+
182+
# line 182
183+
classcls183:
184+
185+
classcls185:
186+
187+
deffunc186(self):
188+
pass
189+
190+
defclass_decorator(cls):
191+
returncls
192+
193+
# line 193
194+
@class_decorator
195+
@class_decorator
196+
classcls196:
197+
198+
@class_decorator
199+
@class_decorator
200+
classcls200:
201+
pass
202+
203+
classcls203:
204+
classcls204:
205+
classcls205:
206+
pass
207+
classcls207:
208+
classcls205:
209+
pass
210+
211+
# line 211
212+
deffunc212():
213+
classcls213:
214+
pass
215+
returncls213
216+
217+
# line 217
218+
classcls213:
219+
deffunc219(self):
220+
classcls220:
221+
pass
222+
returncls220
223+
224+
# line 224
225+
asyncdeffunc225():
226+
classcls226:
227+
pass
228+
returncls226
229+
230+
# line 230
231+
classcls226:
232+
asyncdeffunc232(self):
233+
classcls233:
234+
pass
235+
returncls233
236+
237+
ifTrue:
238+
classcls238:
239+
classcls239:
240+
'''if clause cls239'''
241+
else:
242+
classcls238:
243+
classcls239:
244+
'''else clause 239'''
245+
pass
246+
247+
#line 247
142248
defpositional_only_arg(a,/):
143249
pass
144250

145-
#line145
251+
#line251
146252
defall_markers(a,b,/,c,d,*,e,f):
147253
pass
148254

149-
# line149
255+
# line255
150256
defall_markers_with_args_and_kwargs(a,b,/,c,d,*args,e,f,**kwargs):
151257
pass
152258

153-
#line153
259+
#line259
154260
defall_markers_with_defaults(a,b=1,/,c=2,d=3,*,e=4,f=5):
155261
pass

‎Lib/test/test_inspect.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ def test_cleandoc(self):
473473
deftest_getcomments(self):
474474
self.assertEqual(inspect.getcomments(mod),'# line 1\n')
475475
self.assertEqual(inspect.getcomments(mod.StupidGit),'# line 20\n')
476+
self.assertEqual(inspect.getcomments(mod2.cls160),'# line 159\n')
476477
# If the object source file is not available, return None.
477478
co=compile('x=1','_non_existing_filename.py','exec')
478479
self.assertIsNone(inspect.getcomments(co))
@@ -709,6 +710,45 @@ def test_getsource_on_method(self):
709710
deftest_nested_func(self):
710711
self.assertSourceEqual(mod2.cls135.func136,136,139)
711712

713+
deftest_class_definition_in_multiline_string_definition(self):
714+
self.assertSourceEqual(mod2.cls149,149,152)
715+
716+
deftest_class_definition_in_multiline_comment(self):
717+
self.assertSourceEqual(mod2.cls160,160,163)
718+
719+
deftest_nested_class_definition_indented_string(self):
720+
self.assertSourceEqual(mod2.cls173.cls175,175,176)
721+
722+
deftest_nested_class_definition(self):
723+
self.assertSourceEqual(mod2.cls183,183,188)
724+
self.assertSourceEqual(mod2.cls183.cls185,185,188)
725+
726+
deftest_class_decorator(self):
727+
self.assertSourceEqual(mod2.cls196,194,201)
728+
self.assertSourceEqual(mod2.cls196.cls200,198,201)
729+
730+
deftest_class_inside_conditional(self):
731+
self.assertSourceEqual(mod2.cls238,238,240)
732+
self.assertSourceEqual(mod2.cls238.cls239,239,240)
733+
734+
deftest_multiple_children_classes(self):
735+
self.assertSourceEqual(mod2.cls203,203,209)
736+
self.assertSourceEqual(mod2.cls203.cls204,204,206)
737+
self.assertSourceEqual(mod2.cls203.cls204.cls205,205,206)
738+
self.assertSourceEqual(mod2.cls203.cls207,207,209)
739+
self.assertSourceEqual(mod2.cls203.cls207.cls205,208,209)
740+
741+
deftest_nested_class_definition_inside_function(self):
742+
self.assertSourceEqual(mod2.func212(),213,214)
743+
self.assertSourceEqual(mod2.cls213,218,222)
744+
self.assertSourceEqual(mod2.cls213().func219(),220,221)
745+
746+
deftest_nested_class_definition_inside_async_function(self):
747+
importasyncio
748+
self.addCleanup(asyncio.set_event_loop_policy,None)
749+
self.assertSourceEqual(asyncio.run(mod2.func225()),226,227)
750+
self.assertSourceEqual(mod2.cls226,231,235)
751+
self.assertSourceEqual(asyncio.run(mod2.cls226().func232()),233,234)
712752

713753
classTestNoEOL(GetSourceBase):
714754
defsetUp(self):

‎Lib/test/test_pydoc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ def test_getpager_with_stdin_none(self):
476476
deftest_non_str_name(self):
477477
# issue14638
478478
# Treat illegal (non-str) name like no name
479+
479480
classA:
480481
__name__=42
481482
classB:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`inspect.getsource` now returns correct source code for inner class
2+
with same name as module level class. Decorators are also returned as part
3+
of source of the class. Patch by Karthikeyan Singaravelan.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp