Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/py/_code/code.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import py
2import sys
3from inspect import CO_VARARGS, CO_VARKEYWORDS, isclass
5builtin_repr = repr
7reprlib = py.builtin._tryimport('repr', 'reprlib')
9if sys.version_info[0] >= 3:
10 from traceback import format_exception_only
11else:
12 from py._code._py2traceback import format_exception_only
14import traceback
17class Code(object):
18 """ wrapper around Python code objects """
19 def __init__(self, rawcode):
20 if not hasattr(rawcode, "co_filename"):
21 rawcode = py.code.getrawcode(rawcode)
22 try:
23 self.filename = rawcode.co_filename
24 self.firstlineno = rawcode.co_firstlineno - 1
25 self.name = rawcode.co_name
26 except AttributeError:
27 raise TypeError("not a code object: %r" % (rawcode,))
28 self.raw = rawcode
30 def __eq__(self, other):
31 return self.raw == other.raw
33 def __ne__(self, other):
34 return not self == other
36 @property
37 def path(self):
38 """ return a path object pointing to source code (note that it
39 might not point to an actually existing file). """
40 p = py.path.local(self.raw.co_filename)
41 # maybe don't try this checking
42 if not p.check():
43 # XXX maybe try harder like the weird logic
44 # in the standard lib [linecache.updatecache] does?
45 p = self.raw.co_filename
46 return p
48 @property
49 def fullsource(self):
50 """ return a py.code.Source object for the full source file of the code
51 """
52 from py._code import source
53 full, _ = source.findsource(self.raw)
54 return full
56 def source(self):
57 """ return a py.code.Source object for the code object's source only
58 """
59 # return source only for that part of code
60 return py.code.Source(self.raw)
62 def getargs(self, var=False):
63 """ return a tuple with the argument names for the code object
65 if 'var' is set True also return the names of the variable and
66 keyword arguments when present
67 """
68 # handfull shortcut for getting args
69 raw = self.raw
70 argcount = raw.co_argcount
71 if var:
72 argcount += raw.co_flags & CO_VARARGS
73 argcount += raw.co_flags & CO_VARKEYWORDS
74 return raw.co_varnames[:argcount]
76class Frame(object):
77 """Wrapper around a Python frame holding f_locals and f_globals
78 in which expressions can be evaluated."""
80 def __init__(self, frame):
81 self.lineno = frame.f_lineno - 1
82 self.f_globals = frame.f_globals
83 self.f_locals = frame.f_locals
84 self.raw = frame
85 self.code = py.code.Code(frame.f_code)
87 @property
88 def statement(self):
89 """ statement this frame is at """
90 if self.code.fullsource is None:
91 return py.code.Source("")
92 return self.code.fullsource.getstatement(self.lineno)
94 def eval(self, code, **vars):
95 """ evaluate 'code' in the frame
97 'vars' are optional additional local variables
99 returns the result of the evaluation
100 """
101 f_locals = self.f_locals.copy()
102 f_locals.update(vars)
103 return eval(code, self.f_globals, f_locals)
105 def exec_(self, code, **vars):
106 """ exec 'code' in the frame
108 'vars' are optiona; additional local variables
109 """
110 f_locals = self.f_locals.copy()
111 f_locals.update(vars)
112 py.builtin.exec_(code, self.f_globals, f_locals)
114 def repr(self, object):
115 """ return a 'safe' (non-recursive, one-line) string repr for 'object'
116 """
117 return py.io.saferepr(object)
119 def is_true(self, object):
120 return object
122 def getargs(self, var=False):
123 """ return a list of tuples (name, value) for all arguments
125 if 'var' is set True also include the variable and keyword
126 arguments when present
127 """
128 retval = []
129 for arg in self.code.getargs(var):
130 try:
131 retval.append((arg, self.f_locals[arg]))
132 except KeyError:
133 pass # this can occur when using Psyco
134 return retval
137class TracebackEntry(object):
138 """ a single entry in a traceback """
140 _repr_style = None
141 exprinfo = None
143 def __init__(self, rawentry):
144 self._rawentry = rawentry
145 self.lineno = rawentry.tb_lineno - 1
147 def set_repr_style(self, mode):
148 assert mode in ("short", "long")
149 self._repr_style = mode
151 @property
152 def frame(self):
153 return py.code.Frame(self._rawentry.tb_frame)
155 @property
156 def relline(self):
157 return self.lineno - self.frame.code.firstlineno
159 def __repr__(self):
160 return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno+1)
162 @property
163 def statement(self):
164 """ py.code.Source object for the current statement """
165 source = self.frame.code.fullsource
166 return source.getstatement(self.lineno)
168 @property
169 def path(self):
170 """ path to the source code """
171 return self.frame.code.path
173 def getlocals(self):
174 return self.frame.f_locals
175 locals = property(getlocals, None, None, "locals of underlaying frame")
177 def reinterpret(self):
178 """Reinterpret the failing statement and returns a detailed information
179 about what operations are performed."""
180 if self.exprinfo is None:
181 source = str(self.statement).strip()
182 x = py.code._reinterpret(source, self.frame, should_fail=True)
183 if not isinstance(x, str):
184 raise TypeError("interpret returned non-string %r" % (x,))
185 self.exprinfo = x
186 return self.exprinfo
188 def getfirstlinesource(self):
189 # on Jython this firstlineno can be -1 apparently
190 return max(self.frame.code.firstlineno, 0)
192 def getsource(self, astcache=None):
193 """ return failing source code. """
194 # we use the passed in astcache to not reparse asttrees
195 # within exception info printing
196 from py._code.source import getstatementrange_ast
197 source = self.frame.code.fullsource
198 if source is None:
199 return None
200 key = astnode = None
201 if astcache is not None:
202 key = self.frame.code.path
203 if key is not None:
204 astnode = astcache.get(key, None)
205 start = self.getfirstlinesource()
206 try:
207 astnode, _, end = getstatementrange_ast(self.lineno, source,
208 astnode=astnode)
209 except SyntaxError:
210 end = self.lineno + 1
211 else:
212 if key is not None:
213 astcache[key] = astnode
214 return source[start:end]
216 source = property(getsource)
218 def ishidden(self):
219 """ return True if the current frame has a var __tracebackhide__
220 resolving to True
222 mostly for internal use
223 """
224 try:
225 return self.frame.f_locals['__tracebackhide__']
226 except KeyError:
227 try:
228 return self.frame.f_globals['__tracebackhide__']
229 except KeyError:
230 return False
232 def __str__(self):
233 try:
234 fn = str(self.path)
235 except py.error.Error:
236 fn = '???'
237 name = self.frame.code.name
238 try:
239 line = str(self.statement).lstrip()
240 except KeyboardInterrupt:
241 raise
242 except:
243 line = "???"
244 return " File %r:%d in %s\n %s\n" % (fn, self.lineno+1, name, line)
246 def name(self):
247 return self.frame.code.raw.co_name
248 name = property(name, None, None, "co_name of underlaying code")
251class Traceback(list):
252 """ Traceback objects encapsulate and offer higher level
253 access to Traceback entries.
254 """
255 Entry = TracebackEntry
257 def __init__(self, tb):
258 """ initialize from given python traceback object. """
259 if hasattr(tb, 'tb_next'):
260 def f(cur):
261 while cur is not None:
262 yield self.Entry(cur)
263 cur = cur.tb_next
264 list.__init__(self, f(tb))
265 else:
266 list.__init__(self, tb)
268 def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
269 """ return a Traceback instance wrapping part of this Traceback
271 by provding any combination of path, lineno and firstlineno, the
272 first frame to start the to-be-returned traceback is determined
274 this allows cutting the first part of a Traceback instance e.g.
275 for formatting reasons (removing some uninteresting bits that deal
276 with handling of the exception/traceback)
277 """
278 for x in self:
279 code = x.frame.code
280 codepath = code.path
281 if ((path is None or codepath == path) and
282 (excludepath is None or not hasattr(codepath, 'relto') or
283 not codepath.relto(excludepath)) and
284 (lineno is None or x.lineno == lineno) and
285 (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
286 return Traceback(x._rawentry)
287 return self
289 def __getitem__(self, key):
290 val = super(Traceback, self).__getitem__(key)
291 if isinstance(key, type(slice(0))):
292 val = self.__class__(val)
293 return val
295 def filter(self, fn=lambda x: not x.ishidden()):
296 """ return a Traceback instance with certain items removed
298 fn is a function that gets a single argument, a TracebackItem
299 instance, and should return True when the item should be added
300 to the Traceback, False when not
302 by default this removes all the TracebackItems which are hidden
303 (see ishidden() above)
304 """
305 return Traceback(filter(fn, self))
307 def getcrashentry(self):
308 """ return last non-hidden traceback entry that lead
309 to the exception of a traceback.
310 """
311 for i in range(-1, -len(self)-1, -1):
312 entry = self[i]
313 if not entry.ishidden():
314 return entry
315 return self[-1]
317 def recursionindex(self):
318 """ return the index of the frame/TracebackItem where recursion
319 originates if appropriate, None if no recursion occurred
320 """
321 cache = {}
322 for i, entry in enumerate(self):
323 # id for the code.raw is needed to work around
324 # the strange metaprogramming in the decorator lib from pypi
325 # which generates code objects that have hash/value equality
326 #XXX needs a test
327 key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
328 #print "checking for recursion at", key
329 l = cache.setdefault(key, [])
330 if l:
331 f = entry.frame
332 loc = f.f_locals
333 for otherloc in l:
334 if f.is_true(f.eval(co_equal,
335 __recursioncache_locals_1=loc,
336 __recursioncache_locals_2=otherloc)):
337 return i
338 l.append(entry.frame.f_locals)
339 return None
341co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
342 '?', 'eval')
344class ExceptionInfo(object):
345 """ wraps sys.exc_info() objects and offers
346 help for navigating the traceback.
347 """
348 _striptext = ''
349 def __init__(self, tup=None, exprinfo=None):
350 if tup is None:
351 tup = sys.exc_info()
352 if exprinfo is None and isinstance(tup[1], AssertionError):
353 exprinfo = getattr(tup[1], 'msg', None)
354 if exprinfo is None:
355 exprinfo = str(tup[1])
356 if exprinfo and exprinfo.startswith('assert '):
357 self._striptext = 'AssertionError: '
358 self._excinfo = tup
359 #: the exception class
360 self.type = tup[0]
361 #: the exception instance
362 self.value = tup[1]
363 #: the exception raw traceback
364 self.tb = tup[2]
365 #: the exception type name
366 self.typename = self.type.__name__
367 #: the exception traceback (py.code.Traceback instance)
368 self.traceback = py.code.Traceback(self.tb)
370 def __repr__(self):
371 return "<ExceptionInfo %s tblen=%d>" % (
372 self.typename, len(self.traceback))
374 def exconly(self, tryshort=False):
375 """ return the exception as a string
377 when 'tryshort' resolves to True, and the exception is a
378 py.code._AssertionError, only the actual exception part of
379 the exception representation is returned (so 'AssertionError: ' is
380 removed from the beginning)
381 """
382 lines = format_exception_only(self.type, self.value)
383 text = ''.join(lines)
384 text = text.rstrip()
385 if tryshort:
386 if text.startswith(self._striptext):
387 text = text[len(self._striptext):]
388 return text
390 def errisinstance(self, exc):
391 """ return True if the exception is an instance of exc """
392 return isinstance(self.value, exc)
394 def _getreprcrash(self):
395 exconly = self.exconly(tryshort=True)
396 entry = self.traceback.getcrashentry()
397 path, lineno = entry.frame.code.raw.co_filename, entry.lineno
398 return ReprFileLocation(path, lineno+1, exconly)
400 def getrepr(self, showlocals=False, style="long",
401 abspath=False, tbfilter=True, funcargs=False):
402 """ return str()able representation of this exception info.
403 showlocals: show locals per traceback entry
404 style: long|short|no|native traceback style
405 tbfilter: hide entries (where __tracebackhide__ is true)
407 in case of style==native, tbfilter and showlocals is ignored.
408 """
409 if style == 'native':
410 return ReprExceptionInfo(ReprTracebackNative(
411 traceback.format_exception(
412 self.type,
413 self.value,
414 self.traceback[0]._rawentry,
415 )), self._getreprcrash())
417 fmt = FormattedExcinfo(
418 showlocals=showlocals, style=style,
419 abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
420 return fmt.repr_excinfo(self)
422 def __str__(self):
423 entry = self.traceback[-1]
424 loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
425 return str(loc)
427 def __unicode__(self):
428 entry = self.traceback[-1]
429 loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
430 return loc.__unicode__()
433class FormattedExcinfo(object):
434 """ presenting information about failing Functions and Generators. """
435 # for traceback entries
436 flow_marker = ">"
437 fail_marker = "E"
439 def __init__(self, showlocals=False, style="long",
440 abspath=True, tbfilter=True, funcargs=False):
441 self.showlocals = showlocals
442 self.style = style
443 self.tbfilter = tbfilter
444 self.funcargs = funcargs
445 self.abspath = abspath
446 self.astcache = {}
448 def _getindent(self, source):
449 # figure out indent for given source
450 try:
451 s = str(source.getstatement(len(source)-1))
452 except KeyboardInterrupt:
453 raise
454 except:
455 try:
456 s = str(source[-1])
457 except KeyboardInterrupt:
458 raise
459 except:
460 return 0
461 return 4 + (len(s) - len(s.lstrip()))
463 def _getentrysource(self, entry):
464 source = entry.getsource(self.astcache)
465 if source is not None:
466 source = source.deindent()
467 return source
469 def _saferepr(self, obj):
470 return py.io.saferepr(obj)
472 def repr_args(self, entry):
473 if self.funcargs:
474 args = []
475 for argname, argvalue in entry.frame.getargs(var=True):
476 args.append((argname, self._saferepr(argvalue)))
477 return ReprFuncArgs(args)
479 def get_source(self, source, line_index=-1, excinfo=None, short=False):
480 """ return formatted and marked up source lines. """
481 lines = []
482 if source is None or line_index >= len(source.lines):
483 source = py.code.Source("???")
484 line_index = 0
485 if line_index < 0:
486 line_index += len(source)
487 space_prefix = " "
488 if short:
489 lines.append(space_prefix + source.lines[line_index].strip())
490 else:
491 for line in source.lines[:line_index]:
492 lines.append(space_prefix + line)
493 lines.append(self.flow_marker + " " + source.lines[line_index])
494 for line in source.lines[line_index+1:]:
495 lines.append(space_prefix + line)
496 if excinfo is not None:
497 indent = 4 if short else self._getindent(source)
498 lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
499 return lines
501 def get_exconly(self, excinfo, indent=4, markall=False):
502 lines = []
503 indent = " " * indent
504 # get the real exception information out
505 exlines = excinfo.exconly(tryshort=True).split('\n')
506 failindent = self.fail_marker + indent[1:]
507 for line in exlines:
508 lines.append(failindent + line)
509 if not markall:
510 failindent = indent
511 return lines
513 def repr_locals(self, locals):
514 if self.showlocals:
515 lines = []
516 keys = [loc for loc in locals if loc[0] != "@"]
517 keys.sort()
518 for name in keys:
519 value = locals[name]
520 if name == '__builtins__':
521 lines.append("__builtins__ = <builtins>")
522 else:
523 # This formatting could all be handled by the
524 # _repr() function, which is only reprlib.Repr in
525 # disguise, so is very configurable.
526 str_repr = self._saferepr(value)
527 #if len(str_repr) < 70 or not isinstance(value,
528 # (list, tuple, dict)):
529 lines.append("%-10s = %s" %(name, str_repr))
530 #else:
531 # self._line("%-10s =\\" % (name,))
532 # # XXX
533 # pprint.pprint(value, stream=self.excinfowriter)
534 return ReprLocals(lines)
536 def repr_traceback_entry(self, entry, excinfo=None):
537 source = self._getentrysource(entry)
538 if source is None:
539 source = py.code.Source("???")
540 line_index = 0
541 else:
542 # entry.getfirstlinesource() can be -1, should be 0 on jython
543 line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
545 lines = []
546 style = entry._repr_style
547 if style is None:
548 style = self.style
549 if style in ("short", "long"):
550 short = style == "short"
551 reprargs = self.repr_args(entry) if not short else None
552 s = self.get_source(source, line_index, excinfo, short=short)
553 lines.extend(s)
554 if short:
555 message = "in %s" %(entry.name)
556 else:
557 message = excinfo and excinfo.typename or ""
558 path = self._makepath(entry.path)
559 filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
560 localsrepr = None
561 if not short:
562 localsrepr = self.repr_locals(entry.locals)
563 return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
564 if excinfo:
565 lines.extend(self.get_exconly(excinfo, indent=4))
566 return ReprEntry(lines, None, None, None, style)
568 def _makepath(self, path):
569 if not self.abspath:
570 try:
571 np = py.path.local().bestrelpath(path)
572 except OSError:
573 return path
574 if len(np) < len(str(path)):
575 path = np
576 return path
578 def repr_traceback(self, excinfo):
579 traceback = excinfo.traceback
580 if self.tbfilter:
581 traceback = traceback.filter()
582 recursionindex = None
583 if excinfo.errisinstance(RuntimeError):
584 if "maximum recursion depth exceeded" in str(excinfo.value):
585 recursionindex = traceback.recursionindex()
586 last = traceback[-1]
587 entries = []
588 extraline = None
589 for index, entry in enumerate(traceback):
590 einfo = (last == entry) and excinfo or None
591 reprentry = self.repr_traceback_entry(entry, einfo)
592 entries.append(reprentry)
593 if index == recursionindex:
594 extraline = "!!! Recursion detected (same locals & position)"
595 break
596 return ReprTraceback(entries, extraline, style=self.style)
598 def repr_excinfo(self, excinfo):
599 reprtraceback = self.repr_traceback(excinfo)
600 reprcrash = excinfo._getreprcrash()
601 return ReprExceptionInfo(reprtraceback, reprcrash)
603class TerminalRepr:
604 def __str__(self):
605 s = self.__unicode__()
606 if sys.version_info[0] < 3:
607 s = s.encode('utf-8')
608 return s
610 def __unicode__(self):
611 # FYI this is called from pytest-xdist's serialization of exception
612 # information.
613 io = py.io.TextIO()
614 tw = py.io.TerminalWriter(file=io)
615 self.toterminal(tw)
616 return io.getvalue().strip()
618 def __repr__(self):
619 return "<%s instance at %0x>" %(self.__class__, id(self))
622class ReprExceptionInfo(TerminalRepr):
623 def __init__(self, reprtraceback, reprcrash):
624 self.reprtraceback = reprtraceback
625 self.reprcrash = reprcrash
626 self.sections = []
628 def addsection(self, name, content, sep="-"):
629 self.sections.append((name, content, sep))
631 def toterminal(self, tw):
632 self.reprtraceback.toterminal(tw)
633 for name, content, sep in self.sections:
634 tw.sep(sep, name)
635 tw.line(content)
637class ReprTraceback(TerminalRepr):
638 entrysep = "_ "
640 def __init__(self, reprentries, extraline, style):
641 self.reprentries = reprentries
642 self.extraline = extraline
643 self.style = style
645 def toterminal(self, tw):
646 # the entries might have different styles
647 last_style = None
648 for i, entry in enumerate(self.reprentries):
649 if entry.style == "long":
650 tw.line("")
651 entry.toterminal(tw)
652 if i < len(self.reprentries) - 1:
653 next_entry = self.reprentries[i+1]
654 if entry.style == "long" or \
655 entry.style == "short" and next_entry.style == "long":
656 tw.sep(self.entrysep)
658 if self.extraline:
659 tw.line(self.extraline)
661class ReprTracebackNative(ReprTraceback):
662 def __init__(self, tblines):
663 self.style = "native"
664 self.reprentries = [ReprEntryNative(tblines)]
665 self.extraline = None
667class ReprEntryNative(TerminalRepr):
668 style = "native"
670 def __init__(self, tblines):
671 self.lines = tblines
673 def toterminal(self, tw):
674 tw.write("".join(self.lines))
676class ReprEntry(TerminalRepr):
677 localssep = "_ "
679 def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
680 self.lines = lines
681 self.reprfuncargs = reprfuncargs
682 self.reprlocals = reprlocals
683 self.reprfileloc = filelocrepr
684 self.style = style
686 def toterminal(self, tw):
687 if self.style == "short":
688 self.reprfileloc.toterminal(tw)
689 for line in self.lines:
690 red = line.startswith("E ")
691 tw.line(line, bold=True, red=red)
692 #tw.line("")
693 return
694 if self.reprfuncargs:
695 self.reprfuncargs.toterminal(tw)
696 for line in self.lines:
697 red = line.startswith("E ")
698 tw.line(line, bold=True, red=red)
699 if self.reprlocals:
700 #tw.sep(self.localssep, "Locals")
701 tw.line("")
702 self.reprlocals.toterminal(tw)
703 if self.reprfileloc:
704 if self.lines:
705 tw.line("")
706 self.reprfileloc.toterminal(tw)
708 def __str__(self):
709 return "%s\n%s\n%s" % ("\n".join(self.lines),
710 self.reprlocals,
711 self.reprfileloc)
713class ReprFileLocation(TerminalRepr):
714 def __init__(self, path, lineno, message):
715 self.path = str(path)
716 self.lineno = lineno
717 self.message = message
719 def toterminal(self, tw):
720 # filename and lineno output for each entry,
721 # using an output format that most editors unterstand
722 msg = self.message
723 i = msg.find("\n")
724 if i != -1:
725 msg = msg[:i]
726 tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
728class ReprLocals(TerminalRepr):
729 def __init__(self, lines):
730 self.lines = lines
732 def toterminal(self, tw):
733 for line in self.lines:
734 tw.line(line)
736class ReprFuncArgs(TerminalRepr):
737 def __init__(self, args):
738 self.args = args
740 def toterminal(self, tw):
741 if self.args:
742 linesofar = ""
743 for name, value in self.args:
744 ns = "%s = %s" %(name, value)
745 if len(ns) + len(linesofar) + 2 > tw.fullwidth:
746 if linesofar:
747 tw.line(linesofar)
748 linesofar = ns
749 else:
750 if linesofar:
751 linesofar += ", " + ns
752 else:
753 linesofar = ns
754 if linesofar:
755 tw.line(linesofar)
756 tw.line("")
760oldbuiltins = {}
762def patch_builtins(assertion=True, compile=True):
763 """ put compile and AssertionError builtins to Python's builtins. """
764 if assertion:
765 from py._code import assertion
766 l = oldbuiltins.setdefault('AssertionError', [])
767 l.append(py.builtin.builtins.AssertionError)
768 py.builtin.builtins.AssertionError = assertion.AssertionError
769 if compile:
770 l = oldbuiltins.setdefault('compile', [])
771 l.append(py.builtin.builtins.compile)
772 py.builtin.builtins.compile = py.code.compile
774def unpatch_builtins(assertion=True, compile=True):
775 """ remove compile and AssertionError builtins from Python builtins. """
776 if assertion:
777 py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
778 if compile:
779 py.builtin.builtins.compile = oldbuiltins['compile'].pop()
781def getrawcode(obj, trycall=True):
782 """ return code object for given function. """
783 try:
784 return obj.__code__
785 except AttributeError:
786 obj = getattr(obj, 'im_func', obj)
787 obj = getattr(obj, 'func_code', obj)
788 obj = getattr(obj, 'f_code', obj)
789 obj = getattr(obj, '__code__', obj)
790 if trycall and not hasattr(obj, 'co_firstlineno'):
791 if hasattr(obj, '__call__') and not isclass(obj):
792 x = getrawcode(obj.__call__, trycall=False)
793 if hasattr(x, 'co_firstlineno'):
794 return x
795 return obj