mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
gh-118465: Add __firstlineno__ attribute to class (GH-118475)
It is set by compiler with the line number of the first line of the class definition.
This commit is contained in:
parent
716ec4bfcf
commit
153b3f7530
17 changed files with 61 additions and 89 deletions
|
@ -1035,79 +1035,6 @@ class ClassFoundException(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class _ClassFinder(ast.NodeVisitor):
|
||||
|
||||
def __init__(self, cls, tree, lines, qualname):
|
||||
self.stack = []
|
||||
self.cls = cls
|
||||
self.tree = tree
|
||||
self.lines = lines
|
||||
self.qualname = qualname
|
||||
self.lineno_found = []
|
||||
|
||||
def visit_FunctionDef(self, node):
|
||||
self.stack.append(node.name)
|
||||
self.stack.append('<locals>')
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
self.stack.pop()
|
||||
|
||||
visit_AsyncFunctionDef = visit_FunctionDef
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
self.stack.append(node.name)
|
||||
if self.qualname == '.'.join(self.stack):
|
||||
# Return the decorator for the class if present
|
||||
if node.decorator_list:
|
||||
line_number = node.decorator_list[0].lineno
|
||||
else:
|
||||
line_number = node.lineno
|
||||
|
||||
# decrement by one since lines starts with indexing by zero
|
||||
self.lineno_found.append((line_number - 1, node.end_lineno))
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
|
||||
def get_lineno(self):
|
||||
self.visit(self.tree)
|
||||
lineno_found_number = len(self.lineno_found)
|
||||
if lineno_found_number == 0:
|
||||
raise OSError('could not find class definition')
|
||||
elif lineno_found_number == 1:
|
||||
return self.lineno_found[0][0]
|
||||
else:
|
||||
# We have multiple candidates for the class definition.
|
||||
# Now we have to guess.
|
||||
|
||||
# First, let's see if there are any method definitions
|
||||
for member in self.cls.__dict__.values():
|
||||
if (isinstance(member, types.FunctionType) and
|
||||
member.__module__ == self.cls.__module__):
|
||||
for lineno, end_lineno in self.lineno_found:
|
||||
if lineno <= member.__code__.co_firstlineno <= end_lineno:
|
||||
return lineno
|
||||
|
||||
class_strings = [(''.join(self.lines[lineno: end_lineno]), lineno)
|
||||
for lineno, end_lineno in self.lineno_found]
|
||||
|
||||
# Maybe the class has a docstring and it's unique?
|
||||
if self.cls.__doc__:
|
||||
ret = None
|
||||
for candidate, lineno in class_strings:
|
||||
if self.cls.__doc__.strip() in candidate:
|
||||
if ret is None:
|
||||
ret = lineno
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if ret is not None:
|
||||
return ret
|
||||
|
||||
# We are out of ideas, just return the last one found, which is
|
||||
# slightly better than previous ones
|
||||
return self.lineno_found[-1][0]
|
||||
|
||||
|
||||
def findsource(object):
|
||||
"""Return the entire source file and starting line number for an object.
|
||||
|
||||
|
@ -1140,11 +1067,11 @@ def findsource(object):
|
|||
return lines, 0
|
||||
|
||||
if isclass(object):
|
||||
qualname = object.__qualname__
|
||||
source = ''.join(lines)
|
||||
tree = ast.parse(source)
|
||||
class_finder = _ClassFinder(object, tree, lines, qualname)
|
||||
return lines, class_finder.get_lineno()
|
||||
try:
|
||||
firstlineno = object.__firstlineno__
|
||||
except AttributeError:
|
||||
raise OSError('source code not available')
|
||||
return lines, object.__firstlineno__ - 1
|
||||
|
||||
if ismethod(object):
|
||||
object = object.__func__
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue