gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / astroid / bases.py @ 745
History | View | Annotate | Download (21.7 KB)
1 |
# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
|
---|---|
2 |
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
#
|
4 |
# This file is part of astroid.
|
5 |
#
|
6 |
# astroid is free software: you can redistribute it and/or modify it
|
7 |
# under the terms of the GNU Lesser General Public License as published by the
|
8 |
# Free Software Foundation, either version 2.1 of the License, or (at your
|
9 |
# option) any later version.
|
10 |
#
|
11 |
# astroid is distributed in the hope that it will be useful, but
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
13 |
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
14 |
# for more details.
|
15 |
#
|
16 |
# You should have received a copy of the GNU Lesser General Public License along
|
17 |
# with astroid. If not, see <http://www.gnu.org/licenses/>.
|
18 |
"""This module contains base classes and functions for the nodes and some
|
19 |
inference utils.
|
20 |
"""
|
21 |
import functools |
22 |
import sys |
23 |
import warnings |
24 |
|
25 |
import wrapt |
26 |
|
27 |
from astroid import context as contextmod |
28 |
from astroid import decorators as decoratorsmod |
29 |
from astroid import exceptions |
30 |
from astroid import util |
31 |
|
32 |
|
33 |
if sys.version_info >= (3, 0): |
34 |
BUILTINS = 'builtins'
|
35 |
else:
|
36 |
BUILTINS = '__builtin__'
|
37 |
PROPERTIES = {BUILTINS + '.property', 'abc.abstractproperty'} |
38 |
# List of possible property names. We use this list in order
|
39 |
# to see if a method is a property or not. This should be
|
40 |
# pretty reliable and fast, the alternative being to check each
|
41 |
# decorator to see if its a real property-like descriptor, which
|
42 |
# can be too complicated.
|
43 |
# Also, these aren't qualified, because each project can
|
44 |
# define them, we shouldn't expect to know every possible
|
45 |
# property-like decorator!
|
46 |
# TODO(cpopa): just implement descriptors already.
|
47 |
POSSIBLE_PROPERTIES = {"cached_property", "cachedproperty", |
48 |
"lazyproperty", "lazy_property", "reify", |
49 |
"lazyattribute", "lazy_attribute", |
50 |
"LazyProperty", "lazy"} |
51 |
|
52 |
|
53 |
def _is_property(meth): |
54 |
if PROPERTIES.intersection(meth.decoratornames()):
|
55 |
return True |
56 |
stripped = {name.split(".")[-1] for name in meth.decoratornames() |
57 |
if name is not util.YES} |
58 |
return any(name in stripped for name in POSSIBLE_PROPERTIES) |
59 |
|
60 |
|
61 |
class Proxy(object): |
62 |
"""a simple proxy object"""
|
63 |
|
64 |
_proxied = None # proxied object may be set by class or by instance |
65 |
|
66 |
def __init__(self, proxied=None): |
67 |
if proxied is not None: |
68 |
self._proxied = proxied
|
69 |
|
70 |
def __getattr__(self, name): |
71 |
if name == '_proxied': |
72 |
return getattr(self.__class__, '_proxied') |
73 |
if name in self.__dict__: |
74 |
return self.__dict__[name] |
75 |
return getattr(self._proxied, name) |
76 |
|
77 |
def infer(self, context=None): |
78 |
yield self |
79 |
|
80 |
|
81 |
def _infer_stmts(stmts, context, frame=None): |
82 |
"""Return an iterator on statements inferred by each statement in *stmts*."""
|
83 |
stmt = None
|
84 |
inferred = False
|
85 |
if context is not None: |
86 |
name = context.lookupname |
87 |
context = context.clone() |
88 |
else:
|
89 |
name = None
|
90 |
context = contextmod.InferenceContext() |
91 |
|
92 |
for stmt in stmts: |
93 |
if stmt is util.YES: |
94 |
yield stmt
|
95 |
inferred = True
|
96 |
continue
|
97 |
context.lookupname = stmt._infer_name(frame, name) |
98 |
try:
|
99 |
for inferred in stmt.infer(context=context): |
100 |
yield inferred
|
101 |
inferred = True
|
102 |
except exceptions.UnresolvableName:
|
103 |
continue
|
104 |
except exceptions.InferenceError:
|
105 |
yield util.YES
|
106 |
inferred = True
|
107 |
if not inferred: |
108 |
raise exceptions.InferenceError(str(stmt)) |
109 |
|
110 |
|
111 |
class Instance(Proxy): |
112 |
"""a special node representing a class instance"""
|
113 |
def getattr(self, name, context=None, lookupclass=True): |
114 |
try:
|
115 |
values = self._proxied.instance_attr(name, context)
|
116 |
except exceptions.NotFoundError:
|
117 |
if name == '__class__': |
118 |
return [self._proxied] |
119 |
if lookupclass:
|
120 |
# class attributes not available through the instance
|
121 |
# unless they are explicitly defined
|
122 |
if name in ('__name__', '__bases__', '__mro__', '__subclasses__'): |
123 |
return self._proxied.local_attr(name) |
124 |
return self._proxied.getattr(name, context) |
125 |
raise exceptions.NotFoundError(name)
|
126 |
# since we've no context information, return matching class members as
|
127 |
# well
|
128 |
if lookupclass:
|
129 |
try:
|
130 |
return values + self._proxied.getattr(name, context) |
131 |
except exceptions.NotFoundError:
|
132 |
pass
|
133 |
return values
|
134 |
|
135 |
def igetattr(self, name, context=None): |
136 |
"""inferred getattr"""
|
137 |
if not context: |
138 |
context = contextmod.InferenceContext() |
139 |
try:
|
140 |
# avoid recursively inferring the same attr on the same class
|
141 |
context.push((self._proxied, name))
|
142 |
# XXX frame should be self._proxied, or not ?
|
143 |
get_attr = self.getattr(name, context, lookupclass=False) |
144 |
return _infer_stmts(
|
145 |
self._wrap_attr(get_attr, context),
|
146 |
context, |
147 |
frame=self,
|
148 |
) |
149 |
except exceptions.NotFoundError:
|
150 |
try:
|
151 |
# fallback to class'igetattr since it has some logic to handle
|
152 |
# descriptors
|
153 |
return self._wrap_attr(self._proxied.igetattr(name, context), |
154 |
context) |
155 |
except exceptions.NotFoundError:
|
156 |
raise exceptions.InferenceError(name)
|
157 |
|
158 |
def _wrap_attr(self, attrs, context=None): |
159 |
"""wrap bound methods of attrs in a InstanceMethod proxies"""
|
160 |
for attr in attrs: |
161 |
if isinstance(attr, UnboundMethod): |
162 |
if _is_property(attr):
|
163 |
for inferred in attr.infer_call_result(self, context): |
164 |
yield inferred
|
165 |
else:
|
166 |
yield BoundMethod(attr, self) |
167 |
elif hasattr(attr, 'name') and attr.name == '<lambda>': |
168 |
# This is a lambda function defined at class level,
|
169 |
# since its scope is the underlying _proxied class.
|
170 |
# Unfortunately, we can't do an isinstance check here,
|
171 |
# because of the circular dependency between astroid.bases
|
172 |
# and astroid.scoped_nodes.
|
173 |
if attr.statement().scope() == self._proxied: |
174 |
if attr.args.args and attr.args.args[0].name == 'self': |
175 |
yield BoundMethod(attr, self) |
176 |
continue
|
177 |
yield attr
|
178 |
else:
|
179 |
yield attr
|
180 |
|
181 |
def infer_call_result(self, caller, context=None): |
182 |
"""infer what a class instance is returning when called"""
|
183 |
inferred = False
|
184 |
for node in self._proxied.igetattr('__call__', context): |
185 |
if node is util.YES or not node.callable(): |
186 |
continue
|
187 |
for res in node.infer_call_result(caller, context): |
188 |
inferred = True
|
189 |
yield res
|
190 |
if not inferred: |
191 |
raise exceptions.InferenceError()
|
192 |
|
193 |
def __repr__(self): |
194 |
return '<Instance of %s.%s at 0x%s>' % (self._proxied.root().name, |
195 |
self._proxied.name,
|
196 |
id(self)) |
197 |
def __str__(self): |
198 |
return 'Instance of %s.%s' % (self._proxied.root().name, |
199 |
self._proxied.name)
|
200 |
|
201 |
def callable(self): |
202 |
try:
|
203 |
self._proxied.getattr('__call__') |
204 |
return True |
205 |
except exceptions.NotFoundError:
|
206 |
return False |
207 |
|
208 |
def pytype(self): |
209 |
return self._proxied.qname() |
210 |
|
211 |
def display_type(self): |
212 |
return 'Instance of' |
213 |
|
214 |
|
215 |
# TODO(cpopa): this is set in inference.py
|
216 |
# The circular dependency hell goes deeper and deeper.
|
217 |
# pylint: disable=unused-argument
|
218 |
def getitem(self, index, context=None): |
219 |
pass
|
220 |
|
221 |
class UnboundMethod(Proxy): |
222 |
"""a special node representing a method not bound to an instance"""
|
223 |
def __repr__(self): |
224 |
frame = self._proxied.parent.frame()
|
225 |
return '<%s %s of %s at 0x%s' % (self.__class__.__name__, |
226 |
self._proxied.name,
|
227 |
frame.qname(), id(self)) |
228 |
|
229 |
def is_bound(self): |
230 |
return False |
231 |
|
232 |
def getattr(self, name, context=None): |
233 |
if name == 'im_func': |
234 |
return [self._proxied] |
235 |
return self._proxied.getattr(name, context) |
236 |
|
237 |
def igetattr(self, name, context=None): |
238 |
if name == 'im_func': |
239 |
return iter((self._proxied,)) |
240 |
return self._proxied.igetattr(name, context) |
241 |
|
242 |
def infer_call_result(self, caller, context): |
243 |
# If we're unbound method __new__ of builtin object, the result is an
|
244 |
# instance of the class given as first argument.
|
245 |
if (self._proxied.name == '__new__' and |
246 |
self._proxied.parent.frame().qname() == '%s.object' % BUILTINS): |
247 |
infer = caller.args[0].infer() if caller.args else [] |
248 |
return ((x is util.YES and x or Instance(x)) for x in infer) |
249 |
return self._proxied.infer_call_result(caller, context) |
250 |
|
251 |
|
252 |
class BoundMethod(UnboundMethod): |
253 |
"""a special node representing a method bound to an instance"""
|
254 |
def __init__(self, proxy, bound): |
255 |
UnboundMethod.__init__(self, proxy)
|
256 |
self.bound = bound
|
257 |
|
258 |
def is_bound(self): |
259 |
return True |
260 |
|
261 |
def infer_call_result(self, caller, context=None): |
262 |
|
263 |
if context is None: |
264 |
context = contextmod.InferenceContext() |
265 |
context = context.clone() |
266 |
context.boundnode = self.bound
|
267 |
return super(BoundMethod, self).infer_call_result(caller, context) |
268 |
|
269 |
|
270 |
class Generator(Instance): |
271 |
"""a special node representing a generator.
|
272 |
|
273 |
Proxied class is set once for all in raw_building.
|
274 |
"""
|
275 |
def callable(self): |
276 |
return False |
277 |
|
278 |
def pytype(self): |
279 |
return '%s.generator' % BUILTINS |
280 |
|
281 |
def display_type(self): |
282 |
return 'Generator' |
283 |
|
284 |
def __repr__(self): |
285 |
return '<Generator(%s) l.%s at 0x%s>' % (self._proxied.name, self.lineno, id(self)) |
286 |
|
287 |
def __str__(self): |
288 |
return 'Generator(%s)' % (self._proxied.name) |
289 |
|
290 |
|
291 |
# decorators ##################################################################
|
292 |
|
293 |
def path_wrapper(func): |
294 |
"""return the given infer function wrapped to handle the path"""
|
295 |
@functools.wraps(func)
|
296 |
def wrapped(node, context=None, _func=func, **kwargs): |
297 |
"""wrapper function handling context"""
|
298 |
if context is None: |
299 |
context = contextmod.InferenceContext() |
300 |
context.push(node) |
301 |
yielded = set()
|
302 |
for res in _func(node, context, **kwargs): |
303 |
# unproxy only true instance, not const, tuple, dict...
|
304 |
if res.__class__ is Instance: |
305 |
ares = res._proxied |
306 |
else:
|
307 |
ares = res |
308 |
if ares not in yielded: |
309 |
yield res
|
310 |
yielded.add(ares) |
311 |
return wrapped
|
312 |
|
313 |
@wrapt.decorator
|
314 |
def yes_if_nothing_inferred(func, instance, args, kwargs): |
315 |
inferred = False
|
316 |
for node in func(*args, **kwargs): |
317 |
inferred = True
|
318 |
yield node
|
319 |
if not inferred: |
320 |
yield util.YES
|
321 |
|
322 |
@wrapt.decorator
|
323 |
def raise_if_nothing_inferred(func, instance, args, kwargs): |
324 |
inferred = False
|
325 |
for node in func(*args, **kwargs): |
326 |
inferred = True
|
327 |
yield node
|
328 |
if not inferred: |
329 |
raise exceptions.InferenceError()
|
330 |
|
331 |
|
332 |
# Node ######################################################################
|
333 |
|
334 |
class NodeNG(object): |
335 |
"""Base Class for all Astroid node classes.
|
336 |
|
337 |
It represents a node of the new abstract syntax tree.
|
338 |
"""
|
339 |
is_statement = False
|
340 |
optional_assign = False # True for For (and for Comprehension if py <3.0) |
341 |
is_function = False # True for FunctionDef nodes |
342 |
# attributes below are set by the builder module or by raw factories
|
343 |
lineno = None
|
344 |
fromlineno = None
|
345 |
tolineno = None
|
346 |
col_offset = None
|
347 |
# parent node in the tree
|
348 |
parent = None
|
349 |
# attributes containing child node(s) redefined in most concrete classes:
|
350 |
_astroid_fields = () |
351 |
# instance specific inference function infer(node, context)
|
352 |
_explicit_inference = None
|
353 |
|
354 |
def infer(self, context=None, **kwargs): |
355 |
"""main interface to the interface system, return a generator on infered
|
356 |
values.
|
357 |
|
358 |
If the instance has some explicit inference function set, it will be
|
359 |
called instead of the default interface.
|
360 |
"""
|
361 |
if self._explicit_inference is not None: |
362 |
# explicit_inference is not bound, give it self explicitly
|
363 |
try:
|
364 |
return self._explicit_inference(self, context, **kwargs) |
365 |
except exceptions.UseInferenceDefault:
|
366 |
pass
|
367 |
|
368 |
if not context: |
369 |
return self._infer(context, **kwargs) |
370 |
|
371 |
key = (self, context.lookupname,
|
372 |
context.callcontext, context.boundnode) |
373 |
if key in context.inferred: |
374 |
return iter(context.inferred[key]) |
375 |
|
376 |
return context.cache_generator(key, self._infer(context, **kwargs)) |
377 |
|
378 |
def _repr_name(self): |
379 |
"""return self.name or self.attrname or '' for nice representation"""
|
380 |
return getattr(self, 'name', getattr(self, 'attrname', '')) |
381 |
|
382 |
def __str__(self): |
383 |
return '%s(%s)' % (self.__class__.__name__, self._repr_name()) |
384 |
|
385 |
def __repr__(self): |
386 |
return '<%s(%s) l.%s [%s] at 0x%x>' % (self.__class__.__name__, |
387 |
self._repr_name(),
|
388 |
self.fromlineno,
|
389 |
self.root().name,
|
390 |
id(self)) |
391 |
|
392 |
|
393 |
def accept(self, visitor): |
394 |
func = getattr(visitor, "visit_" + self.__class__.__name__.lower()) |
395 |
return func(self) |
396 |
|
397 |
def get_children(self): |
398 |
for field in self._astroid_fields: |
399 |
attr = getattr(self, field) |
400 |
if attr is None: |
401 |
continue
|
402 |
if isinstance(attr, (list, tuple)): |
403 |
for elt in attr: |
404 |
yield elt
|
405 |
else:
|
406 |
yield attr
|
407 |
|
408 |
def last_child(self): |
409 |
"""an optimized version of list(get_children())[-1]"""
|
410 |
for field in self._astroid_fields[::-1]: |
411 |
attr = getattr(self, field) |
412 |
if not attr: # None or empty listy / tuple |
413 |
continue
|
414 |
if isinstance(attr, (list, tuple)): |
415 |
return attr[-1] |
416 |
else:
|
417 |
return attr
|
418 |
return None |
419 |
|
420 |
def parent_of(self, node): |
421 |
"""return true if i'm a parent of the given node"""
|
422 |
parent = node.parent |
423 |
while parent is not None: |
424 |
if self is parent: |
425 |
return True |
426 |
parent = parent.parent |
427 |
return False |
428 |
|
429 |
def statement(self): |
430 |
"""return the first parent node marked as statement node"""
|
431 |
if self.is_statement: |
432 |
return self |
433 |
return self.parent.statement() |
434 |
|
435 |
def frame(self): |
436 |
"""return the first parent frame node (i.e. Module, FunctionDef or
|
437 |
ClassDef)
|
438 |
|
439 |
"""
|
440 |
return self.parent.frame() |
441 |
|
442 |
def scope(self): |
443 |
"""return the first node defining a new scope (i.e. Module,
|
444 |
FunctionDef, ClassDef, Lambda but also GenExpr)
|
445 |
|
446 |
"""
|
447 |
return self.parent.scope() |
448 |
|
449 |
def root(self): |
450 |
"""return the root node of the tree, (i.e. a Module)"""
|
451 |
if self.parent: |
452 |
return self.parent.root() |
453 |
return self |
454 |
|
455 |
def child_sequence(self, child): |
456 |
"""search for the right sequence where the child lies in"""
|
457 |
for field in self._astroid_fields: |
458 |
node_or_sequence = getattr(self, field) |
459 |
if node_or_sequence is child: |
460 |
return [node_or_sequence]
|
461 |
# /!\ compiler.ast Nodes have an __iter__ walking over child nodes
|
462 |
if (isinstance(node_or_sequence, (tuple, list)) |
463 |
and child in node_or_sequence): |
464 |
return node_or_sequence
|
465 |
|
466 |
msg = 'Could not find %s in %s\'s children'
|
467 |
raise exceptions.AstroidError(msg % (repr(child), repr(self))) |
468 |
|
469 |
def locate_child(self, child): |
470 |
"""return a 2-uple (child attribute name, sequence or node)"""
|
471 |
for field in self._astroid_fields: |
472 |
node_or_sequence = getattr(self, field) |
473 |
# /!\ compiler.ast Nodes have an __iter__ walking over child nodes
|
474 |
if child is node_or_sequence: |
475 |
return field, child
|
476 |
if isinstance(node_or_sequence, (tuple, list)) and child in node_or_sequence: |
477 |
return field, node_or_sequence
|
478 |
msg = 'Could not find %s in %s\'s children'
|
479 |
raise exceptions.AstroidError(msg % (repr(child), repr(self))) |
480 |
# FIXME : should we merge child_sequence and locate_child ? locate_child
|
481 |
# is only used in are_exclusive, child_sequence one time in pylint.
|
482 |
|
483 |
def next_sibling(self): |
484 |
"""return the next sibling statement"""
|
485 |
return self.parent.next_sibling() |
486 |
|
487 |
def previous_sibling(self): |
488 |
"""return the previous sibling statement"""
|
489 |
return self.parent.previous_sibling() |
490 |
|
491 |
def nearest(self, nodes): |
492 |
"""return the node which is the nearest before this one in the
|
493 |
given list of nodes
|
494 |
"""
|
495 |
myroot = self.root()
|
496 |
mylineno = self.fromlineno
|
497 |
nearest = None, 0 |
498 |
for node in nodes: |
499 |
assert node.root() is myroot, \ |
500 |
'nodes %s and %s are not from the same module' % (self, node) |
501 |
lineno = node.fromlineno |
502 |
if node.fromlineno > mylineno:
|
503 |
break
|
504 |
if lineno > nearest[1]: |
505 |
nearest = node, lineno |
506 |
# FIXME: raise an exception if nearest is None ?
|
507 |
return nearest[0] |
508 |
|
509 |
# these are lazy because they're relatively expensive to compute for every
|
510 |
# single node, and they rarely get looked at
|
511 |
|
512 |
@decoratorsmod.cachedproperty
|
513 |
def fromlineno(self): |
514 |
if self.lineno is None: |
515 |
return self._fixed_source_line() |
516 |
else:
|
517 |
return self.lineno |
518 |
|
519 |
@decoratorsmod.cachedproperty
|
520 |
def tolineno(self): |
521 |
if not self._astroid_fields: |
522 |
# can't have children
|
523 |
lastchild = None
|
524 |
else:
|
525 |
lastchild = self.last_child()
|
526 |
if lastchild is None: |
527 |
return self.fromlineno |
528 |
else:
|
529 |
return lastchild.tolineno
|
530 |
|
531 |
# TODO / FIXME:
|
532 |
assert self.fromlineno is not None, self |
533 |
assert self.tolineno is not None, self |
534 |
|
535 |
def _fixed_source_line(self): |
536 |
"""return the line number where the given node appears
|
537 |
|
538 |
we need this method since not all nodes have the lineno attribute
|
539 |
correctly set...
|
540 |
"""
|
541 |
line = self.lineno
|
542 |
_node = self
|
543 |
try:
|
544 |
while line is None: |
545 |
_node = next(_node.get_children())
|
546 |
line = _node.lineno |
547 |
except StopIteration: |
548 |
_node = self.parent
|
549 |
while _node and line is None: |
550 |
line = _node.lineno |
551 |
_node = _node.parent |
552 |
return line
|
553 |
|
554 |
def block_range(self, lineno): |
555 |
"""handle block line numbers range for non block opening statements
|
556 |
"""
|
557 |
return lineno, self.tolineno |
558 |
|
559 |
def set_local(self, name, stmt): |
560 |
"""delegate to a scoped parent handling a locals dictionary"""
|
561 |
self.parent.set_local(name, stmt)
|
562 |
|
563 |
def nodes_of_class(self, klass, skip_klass=None): |
564 |
"""return an iterator on nodes which are instance of the given class(es)
|
565 |
|
566 |
klass may be a class object or a tuple of class objects
|
567 |
"""
|
568 |
if isinstance(self, klass): |
569 |
yield self |
570 |
for child_node in self.get_children(): |
571 |
if skip_klass is not None and isinstance(child_node, skip_klass): |
572 |
continue
|
573 |
for matching in child_node.nodes_of_class(klass, skip_klass): |
574 |
yield matching
|
575 |
|
576 |
def _infer_name(self, frame, name): |
577 |
# overridden for ImportFrom, Import, Global, TryExcept and Arguments
|
578 |
return None |
579 |
|
580 |
def _infer(self, context=None): |
581 |
"""we don't know how to resolve a statement by default"""
|
582 |
# this method is overridden by most concrete classes
|
583 |
raise exceptions.InferenceError(self.__class__.__name__) |
584 |
|
585 |
def inferred(self): |
586 |
'''return list of inferred values for a more simple inference usage'''
|
587 |
return list(self.infer()) |
588 |
|
589 |
def infered(self): |
590 |
warnings.warn('%s.infered() is deprecated and slated for removal '
|
591 |
'in astroid 2.0, use %s.inferred() instead.'
|
592 |
% (type(self).__name__, type(self).__name__), |
593 |
PendingDeprecationWarning, stacklevel=2) |
594 |
return self.inferred() |
595 |
|
596 |
def instanciate_class(self): |
597 |
"""instanciate a node if it is a ClassDef node, else return self"""
|
598 |
return self |
599 |
|
600 |
def has_base(self, node): |
601 |
return False |
602 |
|
603 |
def callable(self): |
604 |
return False |
605 |
|
606 |
def eq(self, value): |
607 |
return False |
608 |
|
609 |
def as_string(self): |
610 |
from astroid.as_string import to_code |
611 |
return to_code(self) |
612 |
|
613 |
def repr_tree(self, ids=False): |
614 |
from astroid.as_string import dump |
615 |
return dump(self) |
616 |
|
617 |
|
618 |
class Statement(NodeNG): |
619 |
"""Statement node adding a few attributes"""
|
620 |
is_statement = True
|
621 |
|
622 |
def next_sibling(self): |
623 |
"""return the next sibling statement"""
|
624 |
stmts = self.parent.child_sequence(self) |
625 |
index = stmts.index(self)
|
626 |
try:
|
627 |
return stmts[index +1] |
628 |
except IndexError: |
629 |
pass
|
630 |
|
631 |
def previous_sibling(self): |
632 |
"""return the previous sibling statement"""
|
633 |
stmts = self.parent.child_sequence(self) |
634 |
index = stmts.index(self)
|
635 |
if index >= 1: |
636 |
return stmts[index -1] |