Contact: fumanchu@aminus.org

Log in as guest/geniusql to create tickets

root/trunk/geniusql/astwalk.py

Revision 310 (checked in by lakin, 4 months ago)

geniusql - fixing whitespcae

  • Property svn:eol-style set to native
Line 
1 """AST visitors, including rewriters and deparsers.
2
3 This work, including the source code, documentation
4 and related data, is placed into the public domain.
5
6 The orginal author is Robert Brewer.
7
8 THIS SOFTWARE IS PROVIDED AS-IS, WITHOUT WARRANTY
9 OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF
10 MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE
11 ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE
12 RESULTING FROM THE USE, MODIFICATION, OR
13 REDISTRIBUTION OF THIS SOFTWARE.
14 """
15
16 from compiler import ast
17 from opcode import cmp_op
18 import operator
19 import types
20 from geniusql import codewalk
21
22
23 ast_to_op = {ast.Power: operator.pow,
24              ast.Mul: operator.mul,
25              ast.Div: operator.div,
26              ast.FloorDiv: operator.floordiv,
27              ast.Mod: operator.mod,
28              ast.Add: operator.add,
29              ast.Sub: operator.sub,
30              ast.Subscript: operator.getitem,
31              ast.LeftShift: operator.lshift,
32              ast.RightShift: operator.rshift,
33              ast.Bitand: operator.and_,
34              ast.Bitor: operator.or_,
35              ast.Bitxor: operator.xor,
36              ast.And: operator.and_,
37              ast.Or: operator.or_,
38              }
39
40 binary_to_ast = {'BINARY_POWER': ast.Power,
41                  'BINARY_MULTIPLY': ast.Mul,
42                  'BINARY_DIVIDE': ast.Div,
43                  'BINARY_FLOOR_DIVIDE': ast.FloorDiv,
44                  'BINARY_TRUE_DIVIDE': ast.Div,
45                  'BINARY_MODULO': ast.Mod,
46                  'BINARY_ADD': ast.Add,
47                  'BINARY_SUBTRACT': ast.Sub,
48                  'BINARY_LSHIFT': ast.LeftShift,
49                  'BINARY_RSHIFT': ast.RightShift,
50                  }
51
52 bit_to_ast = {'BINARY_AND': ast.Bitand,
53               'BINARY_OR': ast.Bitor,
54               'BINARY_XOR': ast.Bitxor,
55               }
56
57 repr_to_op = {'**': operator.pow,
58               '*': operator.mul,
59               '/': operator.div,
60               '//': operator.floordiv,
61               '%': operator.mod,
62               '+': operator.add,
63               '-': operator.sub,
64               '<<': operator.lshift,
65               '>>': operator.rshift,
66               '&': operator.and_,
67               '|': operator.or_,
68               '^': operator.xor,
69               }
70
71
72
73 class AST(object):
74
75     args = None
76     kwargs = None
77     star_args = None
78     dstar_args = None
79     _root = None
80
81     def __init__(self, root=None, args=None, kwargs=None,
82                  star_args=None, dstar_args=None):
83         self.root = root
84         self.args = args or []
85         self.kwargs = kwargs or []
86         self.star_args = star_args
87         self.dstar_args = dstar_args
88
89     def _get_root(self):
90         return self._root
91     def _set_root(self, root):
92         self._root = root
93     root = property(_get_root, _set_root, "The root node of the AST")
94
95     def __repr__(self):
96         return ("AST(%r, args=%r, kwargs=%r, star_args=%r, dstar_args=%r)" %
97                 (self.root, self.args, self.kwargs,
98                  self.star_args, self.dstar_args))
99
100
101 class LambdaParser(codewalk.Visitor):
102     """Produce an AST from a supplied lambda.
103
104     reduce: if True (the default), free variables are turned into Consts
105         wherever possible.
106
107     Example: k = lambda x: x.Date == datetime.date(2004, 1, 1)
108              q = LambdaParser(k, reduce=False).ast()
109              r = LambdaParser(k, reduce=True).ast()
110
111     _____ q _____
112     Lambda(['x'], [], 0,
113       Compare(Getattr(Name('x'), 'Date'),
114               [('==', CallFunc(Getattr(Name('datetime'), 'date'),
115                                [Const(2004), Const(1), Const(1)],
116                                None, None))
117               ]))
118
119     _____ r _____
120     Lambda(['x'], [], 0,
121       Compare(Getattr(Name('x'), 'Date'),
122               [('==', Const(datetime.date(2004, 1, 1)))])
123           )
124
125     reduce also pre-computes binary operations *and all other builtin or
126     free functions* where all operands are constants, globals, or freevars.
127     For example:
128         Mul((3, 4))
129
130     is replaced with:
131         Const(12)
132
133     However, order is important. lambda x: x * 4 * 5 won't see any
134     optimization, because the order of eval is (x * 4) * 5. Rewritten
135     as lambda x: 4 * 5 * x, the "4 * 5" can be replaced with "20".
136
137     irreducible: a list of constants (globals, freevars, or attributes)
138         which should not be reduced. For example, datetime.date.today
139         would usually be called and the result stored in the AST (since
140         it takes no arguments, and therefore has no free variables).
141         If you want the today function to be stored directly in the AST
142         (so it can be called later), include it in this "irreducible" list.
143         This does not control the conversion of globals and free variables
144         into constants--that happens regardless. It only controls the
145         reduction of complex expressions into simpler ones.
146
147     env: a dict of objects which will be used to make Consts out of
148         globals and builtins. This is auto-populated with items in
149         __builtin__ and any globals which were present at the time
150         the function was created, so you usually don't have to add
151         anything. However, it can be a handy way for frameworks to
152         provide globals without forcing every caller to import them.
153     """
154
155     def __init__(self, func, env=None, reduce=True, irreducible=None):
156         codewalk.Visitor.__init__(self, func)
157
158         self.reduce = reduce
159
160         if env is None:
161             self.env = {}
162         else:
163             self.env = env.copy()
164         import __builtin__
165         self.env.update(vars(__builtin__))
166         self.env.update(func.func_globals)
167
168         self.ast = AST()
169
170         fc = func.func_code
171         self.ast.args = list(fc.co_varnames)
172         if fc.co_flags & codewalk.CO_VARKEYWORDS:
173             self.ast.dstar_args = self.ast.args.pop()
174         if fc.co_flags & codewalk.CO_VARARGS:
175             self.ast.star_args = self.ast.args.pop()
176
177         if irreducible is None:
178             irreducible = []
179         self.irreducible = irreducible
180
181     def walk(self):
182         """Walk self and set self.ast.root."""
183         self.stack = []
184         self.targets = {}
185
186         codewalk.Visitor.walk(self)
187
188         self.ast.root = self.stack[0]
189
190         if self.verbose:
191             self.debug("stack:", self.stack)
192
193     def _may_reduce(self, *terms):
194         """Return True if all terms are ast.Const and not marked irreducible."""
195         for term in terms:
196             if not isinstance(term, ast.Const):
197                 return False
198             if term.value in self.irreducible:
199                 return False
200             if getattr(term.value, "irreducible", False):
201                 return False
202         return True
203
204     def visit_instruction(self, op, lo=None, hi=None):
205         # Get the instruction pointer for the current instruction.
206         ip = self.cursor - 3
207         if hi is None:
208             ip += 1
209             if lo is None:
210                 ip += 1
211
212         # This is where we do folding of logical AND and OR operators.
213         # The Python code just writes "a AND b", but the VM (bytecode)
214         # acts more like assembly, using conditional JUMP instructions to
215         # implement logical operators. The map stored in self.targets is
216         # of the form:
217         #     {JUMP target: [(self.stack[-1], 'and'), ...]}
218         # where "JUMP target" is the instruction number of the bytecode
219         # which is the target of the JUMP, and each item in the value list
220         # is a tuple of (top of the calling stack, operation).
221         # It's a list because a single bytecode may be the target of
222         # multiple JUMP instructions.
223         # See visit_JUMP_IF_FALSE / TRUE.
224         terms = self.targets.get(ip)
225         if terms:
226             clause = self.stack[-1]
227             while terms:
228                 term, oper = terms.pop()
229                 if self.reduce and self._may_reduce(term, clause):
230                     op = ast_to_op[oper]
231                     clause = ast.Const(op(term.value, clause.value))
232                 else:
233                     clause = oper((term, clause))
234             self.stack[-1] = clause
235             if self.verbose:
236                 self.debug("clause:", clause, "\n")
237
238             if op == 1:
239                 # Py2.4: The current instruction is POP_TOP, which means
240                 # the previous is probably JUMP_*. If so, we don't want to
241                 # pop the value we just placed on the stack and lose it.
242                 # We need to replace the entry that the JUMP_* made in
243                 # self.targets with our new TOS.
244                 target = self.targets[self.last_target_ip]
245                 target[-1] = ((clause, target[-1][1]))
246                 if self.verbose:
247                     self.debug("newtarget:", self.last_target_ip, target)
248
249     def visit_BUILD_LIST(self, lo, hi):
250         numterms = lo + (hi << 8)
251         if numterms:
252             self.stack[-numterms:] = [ast.List(self.stack[-numterms:])]
253
254     def visit_BUILD_MAP(self, lo, hi):
255         # Add an empty Dict and add to its .items in STORE_SUBSCR
256         self.stack.append(ast.Dict([]))
257
258     def visit_BUILD_TUPLE(self, lo, hi):
259         numterms = lo + (hi << 8)
260         if numterms:
261             self.stack[-numterms:] = [ast.Tuple(self.stack[-numterms:])]
262
263     def visit_CALL_FUNCTION(self, lo, hi):
264         kwargs = []
265         for i in xrange(hi):
266             val = self.stack.pop()
267             key = self.stack.pop()
268             kwargs.append(ast.Keyword(key, val))
269
270         if lo:
271             args = self.stack[-lo:]
272             self.stack[-lo:] = []
273         else:
274             args = []
275
276         func = self.stack[-1]
277
278         if self.reduce and self._may_reduce(func):
279             if func.value is getattr and not isinstance(args[0], ast.Const):
280                 self.stack[-1] = ast.Getattr(args[0], args[1].value)
281                 return
282             else:
283                 # If all args/kwargs are also Const,
284                 # reduce to a single Const.
285                 argvals = [a.value for a in args if self._may_reduce(a)]
286                 if len(argvals) == len(args):
287                     kwargvals = dict([(k.name, k.expr.value) for k, v in kwargs
288                                       if self._may_reduce(k.expr)])
289                     if len(kwargvals) == len(kwargs):
290                         retval = func.value(*tuple(argvals), **kwargvals)
291                         self.stack[-1] = ast.Const(retval)
292                         return
293
294         if kwargs:
295             args += kwargs
296         self.stack[-1] = ast.CallFunc(func, args)
297
298     def visit_COMPARE_OP(self, lo, hi):
299         term1, term2 = self.stack[-2:]
300         op = cmp_op[lo + (hi << 8)]
301         if self.reduce and self._may_reduce(term1, term2):
302             oper = codewalk.comparisons[op]
303             self.stack[-2:] = [ast.Const(oper(term1.value, term2.value))]
304         else:
305             self.stack[-2:] = [ast.Compare(term1, [(op, term2)])]
306         if self.verbose:
307             self.debug(op)
308
309     def visit_DUP_TOP(self):
310         self.stack.append(self.stack[-1])
311
312     def visit_JUMP_IF_FALSE(self, lo, hi):
313         # Note that self.cursor has already advanced to the next instruction.
314         target = self.cursor + (lo + (hi << 8))
315         bucket = self.targets.setdefault(target, [])
316         bucket.append((self.stack[-1], ast.And))
317         if self.verbose:
318             self.debug("target:", target, bucket)
319         # Store target ip for the special code in visit_instruction
320         self.last_target_ip = target
321
322     def visit_JUMP_IF_TRUE(self, lo, hi):
323         # Note that self.cursor has already advanced to the next instruction.
324         target = self.cursor + (lo + (hi << 8))
325         bucket = self.targets.setdefault(target, [])
326         bucket.append((self.stack[-1], ast.Or))
327         if self.verbose:
328             self.debug("target:", target, bucket)
329         # Store target ip for the special code in visit_instruction
330         self.last_target_ip = target
331
332     def visit_LOAD_ATTR(self, lo, hi):
333         term = self.co_names[lo + (hi << 8)]
334         obj = self.stack[-1]
335         if self.reduce and self._may_reduce(obj):
336             self.stack[-1] = ast.Const(getattr(obj.value, term))
337         else:
338             self.stack[-1] = ast.Getattr(obj, term)
339         if self.verbose:
340             self.debug(term)
341
342     def visit_LOAD_CONST(self, lo, hi):
343         val = self.co_consts[lo + (hi << 8)]
344         self.stack.append(ast.Const(val))
345         if self.verbose:
346             self.debug(val)
347
348     def visit_LOAD_DEREF(self, lo, hi):
349         if self.reduce and hasattr(self, '_func'):
350             value = self._func.func_closure[lo + (hi << 8)]
351             self.stack.append(ast.Const(codewalk.deref_cell(value)))
352             return
353
354         name = self.co_freevars[lo + (hi << 8)]
355         self.stack.append(ast.Name(name))
356
357     def visit_LOAD_FAST(self, lo, hi):
358         term = self.co_varnames[lo + (hi << 8)]
359         self.stack.append(ast.Name(term))
360         if self.verbose:
361             self.debug(term)
362
363     def visit_LOAD_GLOBAL(self, lo, hi):
364         name = self.co_names[lo + (hi << 8)]
365         if self.reduce:
366             if name not in self.env:
367                 raise KeyError("'%s' is not present in supplied globals." % name)
368             self.stack.append(ast.Const(self.env[name]))
369             return
370
371         self.stack.append(ast.Name(name))
372
373     def visit_POP_TOP(self):
374         self.stack.pop()
375
376     def visit_ROT_TWO(self):
377         k, v = self.stack[-2:]
378         self.stack[-2:] = [v, k]
379
380     def visit_ROT_THREE(self):
381         x, k, v = self.stack[-3:]
382         self.stack[-3:] = [v, x, k]
383
384     def visit_SLICE_PLUS_0(self):
385         obj = self.stack[-1]
386         if self.reduce and self._may_reduce(obj):
387             self.stack[-1] = ast.Const(obj.value[:])
388         else:
389             self.stack[-1] = ast.Slice(obj, 'OP_APPLY', None, None)
390
391     def visit_SLICE_PLUS_1(self):
392         obj, arg = self.stack[-2:]
393         if self.reduce and self._may_reduce(obj, arg):
394             self.stack[-2:] = [ast.Const(obj.value[arg.value:])]
395         else:
396             self.stack[-2:] = [ast.Slice(obj, 'OP_APPLY', arg, None)]
397
398     def visit_SLICE_PLUS_2(self):
399         obj, arg = self.stack[-2:]
400         if self.reduce and self._may_reduce(obj, arg):
401             self.stack[-2:] = [ast.Const(obj.value[:arg.value])]
402         else:
403             self.stack[-2:] = [ast.Slice(obj, 'OP_APPLY', None, arg)]
404
405     def visit_SLICE_PLUS_3(self):
406         obj, arg1, arg2 = self.stack[-3:]
407         if self.reduce and self._may_reduce(obj, arg1, arg2):
408             self.stack[-3:] = [ast.Const(obj.value[arg1.value:arg2.value])]
409         else:
410             self.stack[-3:] = [ast.Slice(obj, 'OP_APPLY', arg1, arg2)]
411
412     def visit_STORE_SUBSCR(self):
413         # 'x' should be an ast.Dict
414         v, x, k = self.stack[-3:]
415         self.stack[-3:] = []
416         x.items.append((k, v))
417
418     def visit_STORE_MAP(self):
419         k = self.stack.pop()
420         v = self.stack.pop()
421         self.stack[-1].items.append((k, v))
422
423     def visit_UNARY_INVERT(self):
424         term = self.stack[-1]
425         if self.reduce and self._may_reduce(term):
426             self.stack[-1] = ast.Const(~term.value)
427         else:
428             self.stack[-1] = ast.Invert(term)
429
430     def visit_UNARY_NEGATIVE(self):
431         term = self.stack[-1]
432         if self.reduce and self._may_reduce(term):
433             self.stack[-1] = ast.Const(-term.value)
434         else:
435             self.stack[-1] = ast.UnarySub(term)
436
437     def visit_UNARY_NOT(self):
438         term = self.stack[-1]
439         if self.reduce and self._may_reduce(term):
440             self.stack[-1] = ast.Const(not term.value)
441         else:
442             self.stack[-1] = ast.Not(term)
443
444     def visit_UNARY_POSITIVE(self):
445         term = self.stack[-1]
446         if self.reduce and self._may_reduce(term):
447             self.stack[-1] = ast.Const(+term.value)
448         else:
449             self.stack[-1] = ast.UnaryAdd(term)
450
451     def visit_BINARY_SUBSCR(self):
452         op1, op2 = self.stack[-2:]
453         if self.reduce and self._may_reduce(op1, op2):
454             self.stack[-2:] = [ast.Const(op1.value[op2.value])]
455         else:
456             self.stack[-2:] = [ast.Subscript(op1, 'OP_APPLY', [op2])]
457
458     def binary_op(self, op):
459         op1, op2 = self.stack[-2:]
460         if self.reduce and self._may_reduce(op1, op2):
461             self.stack[-2:] = [ast.Const(ast_to_op[op](op1.value, op2.value))]
462         else:
463             # Binary ops like ast.Add take a single tuple as a first arg
464             self.stack[-2:] = [op((op1, op2))]
465
466     def bit_op(self, op):
467         op1, op2 = self.stack[-2:]
468         if self.reduce and self._may_reduce(op1, op2):
469             self.stack[-2:] = [ast.Const(ast_to_op[op](op1.value, op2.value))]
470         else:
471             self.stack[-2:] = [op(op1, op2)]
472
473
474 # Add visit_BINARY methods to LambdaParser.
475 for k, v in binary_to_ast.iteritems():
476     setattr(LambdaParser, "visit_" + k,
477             lambda self, op=v: self.binary_op(op))
478 for k, v in bit_to_ast.iteritems():
479     setattr(LambdaParser, "visit_" + k,
480             lambda self, op=v: self.bit_op(op))
481
482
483 class ASTDeparser(object):
484     """Produce code (as a string) from a supplied AST."""
485
486     def __init__(self, ast):
487         self.verbose = False
488         self.ast = ast
489
490     def debug(self, *messages):
491         for term in messages:
492             print term,
493         print
494
495     def walk(self, node):
496         """Walk the AST and return a string of code."""
497         nodetype = node.__class__.__name__
498         method = getattr(self, "visit_" + nodetype)
499         args = node.getChildren()
500         if self.verbose:
501             self.debug(nodetype, args)
502         return method(*args)
503
504     def visit_And(self, *terms):
505         return " and ".join(["(%s)" % self.walk(term) for term in terms])
506
507     def visit_Or(self, *terms):
508         return " or ".join(["(%s)" % self.walk(term) for term in terms])
509
510     def visit_List(self, *terms):
511         return "[%s]" % ", ".join([self.walk(term) for term in terms])
512
513     def visit_Dict(self, *terms):
514         return "{%s}" % ", ".join(["%s: %s" % (self.walk(terms[i]),
515                                                self.walk(terms[i + 1]))
516                                    for i in xrange(0, len(terms), 2)])
517
518     def visit_Tuple(self, *terms):
519         terms = [self.walk(term) for term in terms]
520         if len(terms) == 1:
521             terms.append('')
522         return "(%s)" % ", ".join(terms)
523
524     def visit_CallFunc(self, func, *args):
525         star_args = args[-1]
526         dstar_args= args[-2]
527
528         posargs = []
529         kwargs = {}
530         for arg in args[:-2]:
531             if isinstance(arg, ast.Keyword):
532                 key, val = arg.name, self.walk(arg.value)
533                 kwargs[key] = val
534             else:
535                 posargs.append(self.walk(arg))
536         kwargs = ", ".join(["%s=%s" % (str(k), v) for k, v in kwargs.iteritems()])
537         posargs = ", ".join([str(x) for x in posargs])
538
539         if kwargs:
540             kwargs = ", " + kwargs
541         if star_args:
542             star_args = ", " + star_args.name
543         else:
544             star_args = ""
545         if dstar_args:
546             dstar_args = ", " + dstar_args.name
547         else:
548             dstar_args = ""
549
550         func = self.walk(func)
551
552         return ("%s(%s%s%s%s)" %
553                 (func, posargs, kwargs, star_args, dstar_args))
554
555     def visit_Compare(self, expr, *ops):
556         expr = [self.walk(expr)]
557         i = 0
558         ops_len = len(ops)
559         while i < ops_len:
560             expr.append(ops[i])
561             expr.append(self.walk(ops[i + 1]))
562             i += 2
563         return " ".join(expr)
564
565     def visit_Getattr(self, expr, attrname):
566         return "%s.%s" % (self.walk(expr), attrname)
567
568     def visit_Const(self, value):
569         mod = getattr(value, "__module__", None)
570         if isinstance(value, (types.FunctionType, type)):
571             # The const in question is a factory function, like int or date.
572             name = value.__name__
573             term = mod + "." + name
574         else:
575             term = repr(value)
576             if mod and not mod.startswith("__"):
577                 if not term.startswith(mod + "."):
578                     term = mod + "." + term
579         return term
580
581     def visit_Name(self, name):
582         return name
583
584     def visit_Slice(self, expr, flags, lower, upper):
585         expr = self.walk(expr)
586         if lower is None:
587             lower = ""
588         else:
589             lower= self.walk(lower)
590         if upper is None:
591             upper = ""
592         else:
593             upper= self.walk(upper)
594         return "%s[%s:%s]" % (expr, lower, upper)
595
596     def visit_Subscript(self, expr, flags, subs):
597         return "%s[%s]" % (self.walk(expr), self.walk(subs))
598
599     def visit_Invert(self, expr):
600         return "~(" + self.walk(expr) + ")"
601
602     def visit_UnarySub(self, expr):
603         return "-(" + self.walk(expr) + ")"
604
605     def visit_Not(self, expr):
606         return "not (" + self.walk(expr) + ")"
607
608     def visit_UnaryAdd(self, expr):
609         return "+(" + self.walk(expr) + ")"
610
611     # ------------------------ Binary Operations ------------------------ #
612
613     def binary_op(self, left, op, right):
614         return "%s %s %s" % (self.walk(left), op, self.walk(right))
615
616     def visit_Power(self, left, right):
617         return self.binary_op(left, '**', right)
618
619     def visit_Mul(self, left, right):
620         return self.binary_op(left, '*', right)
621
622     def visit_Div(self, left, right):
623         return self.binary_op(left, '/', right)
624
625     def visit_FloorDiv(self, left, right):
626         return self.binary_op(left, '//', right)
627
628     def visit_Mod(self, left, right):
629         return self.binary_op(left, '%', right)
630
631     def visit_Add(self, left, right):
632         return self.binary_op(left, '+', right)
633
634     def visit_Sub(self, left, right):
635         return self.binary_op(left, '-', right)
636
637     def visit_LeftShift(self, left, right):
638         return self.binary_op(left, '<<', right)
639
640     def visit_RightShift(self, left, right):
641         return self.binary_op(left, '>>', right)
642
643     def bit_op(self, op, nodes):
644         return (" %s " % op).join(map(self.walk, nodes))
645
646     def visit_Bitand(self, nodes):
647         return self.bit_op("&", nodes)
648
649     def visit_Bitor(self, nodes):
650         return self.bit_op("|", nodes)
651
652     def visit_Bitxor(self, nodes):
653         return self.bit_op("^", nodes)
654
655
656 class LambdaDeparser(ASTDeparser):
657     """Produce code (as a string) for a Python lambda from a supplied AST."""
658
659     def __init__(self, ast, env=None):
660         ASTDeparser.__init__(self, ast)
661         if env is None:
662             self.env = {}
663         else:
664             self.env = env.copy()
665         import __builtin__
666         self.env.update(vars(__builtin__))
667
668     def code(self, include_func_header=True):
669         product = self.walk(self.ast.root)
670         if include_func_header:
671             args = (self.ast.args or [])[:]
672             if self.ast.kwargs:
673                 args.append(self.ast.kwargs)
674             if self.ast.star_args:
675                 args.append("*%s" % self.ast.star_args)
676             if self.ast.dstar_args:
677                 args.append("**%s" % self.ast.dstar_args)
678             args = ", ".join(args)
679             if args:
680                 args = " " + args
681
682             product = "lambda%s: %s" % (args, product)
683         return product
684
685     def visit_Const(self, value):
686         mod = getattr(value, "__module__", None)
687         if isinstance(value, (types.FunctionType, type)):
688             # The const in question is a factory function, like int or date.
689             name = value.__name__
690             # Here's where we differ from the superclass: pull names from
691             # an environment to reduce
692             if name in self.env:
693                 term = name
694             else:
695                 term = mod + "." + name
696         else:
697             term = repr(value)
698             if mod and not mod.startswith("__"):
699                 if not term.startswith(mod + "."):
700                     term = mod + "." + term
701         return term
702
703
704 del k, v
Note: See TracBrowser for help on using the browser.