Emile Anclin wrote:
On Friday 01 October 2010 20:15:32 Daniel Harding wrote:
Currently if pylint is run under Python 2.7 to check a file containing
dictionary comprehensions or set comprehensions, it results in an
exception in the astng code.
The attached patches (against the current hg tip) add support for these
constructs to astng.
fabulous, this is half the job of making astng py3k compatible!
(or at least an important part)
I will apply the patch right now
Thanks for taking my changes so quickly! However, I realized today that
they weren't completely correct. Dict comprehensions and set
comprehensions should create their own scope, just like generator
expressions. I didn't account for this in my changes, so I have
attached a patch against astng that makes DictComp and SetComp subclass
from LocalsDictNodeNG rather than NodeNG. I also moved their
definitions from node_classes.py to scoped_nodes.py.
Once that change was made, I had to make a change to VariableChecker in
pylint to prevent spurious E0602 undefined variable errors. I added
visit_*/leave_* methods for dict and set comprehensions which mirror the
visit_genexpr/leave_genexpr methods. I also changed the scope type used
in visit_genexpr to be 'comprehension' and then reused that for dict and
set comprehensions. Attached is a second patch with these changes
against pylint.
As I was making these changes, I realized that as part of implementing
Python 3 support, both astng and PyLint will need to be able to account
for the fact that under Python 2, list comprehensions use the enclosing
scope, while under Python 3, they create their own scope. I don't know
what the mechanism/architecture will be for making that kind of runtime
decision, so I didn't try to tackle that issue.
Also, I hope to implement some tests pylint this week for dict and set
comprehensions - I'll post a patch once I have it.
Cheers,
Daniel Harding
# HG changeset patch
# User Daniel Harding <[email protected]>
# Date 1286652831 0
# Node ID 5cde27ab8480580b110aba26f587de25650c5e32
# Parent 12e5f7eefe73dff220e4d17b93aa42eabbc4d8d4
make dictionary comprehensions and set comprehensions have their own scope,
like generator expressions
diff -r 12e5f7eefe73 -r 5cde27ab8480 node_classes.py
--- a/node_classes.py Thu Oct 07 18:46:23 2010 +0200
+++ b/node_classes.py Sat Oct 09 19:33:51 2010 +0000
@@ -505,14 +505,6 @@
raise IndexError(key)
-class DictComp(NodeNG):
- """class representing a DictComp node"""
- _astng_fields = ('key', 'value', 'generators')
- key = None
- value = None
- generators = None
-
-
class Discard(StmtMixIn, NodeNG):
"""class representing a Discard node"""
_astng_fields = ('value',)
@@ -709,13 +701,6 @@
return self.elts
-class SetComp(NodeNG):
- """class representing a SetComp node"""
- _astng_fields = ('elt', 'generators')
- elt = None
- generators = None
-
-
class Slice(NodeNG):
"""class representing a Slice node"""
_astng_fields = ('lower', 'upper', 'step')
diff -r 12e5f7eefe73 -r 5cde27ab8480 nodes.py
--- a/nodes.py Thu Oct 07 18:46:23 2010 +0200
+++ b/nodes.py Sat Oct 09 19:33:51 2010 +0000
@@ -54,12 +54,13 @@
from logilab.astng.node_classes import Arguments, AssAttr, Assert, Assign, \
AssName, AugAssign, Backquote, BinOp, BoolOp, Break, CallFunc, Compare, \
Comprehension, Const, Continue, Decorators, DelAttr, DelName, Delete, \
- Dict, DictComp, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, \
- ExtSlice, For, From, Getattr, Global, If, IfExp, Import, Index, Keyword, \
- List, ListComp, Name, Pass, Print, Raise, Return, Set, SetComp, Slice, \
- Subscript, TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \
+ Dict, Discard, Ellipsis, EmptyNode, ExceptHandler, Exec, ExtSlice, For, \
+ From, Getattr, Global, If, IfExp, Import, Index, Keyword, \
+ List, ListComp, Name, Pass, Print, Raise, Return, Set, Slice, Subscript, \
+ TryExcept, TryFinally, Tuple, UnaryOp, While, With, Yield, \
const_factory
-from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, Function, Class
+from logilab.astng.scoped_nodes import Module, GenExpr, Lambda, DictComp, \
+ SetComp, Function, Class
ALL_NODE_CLASSES = (
Arguments, AssAttr, Assert, Assign, AssName, AugAssign,
diff -r 12e5f7eefe73 -r 5cde27ab8480 scoped_nodes.py
--- a/scoped_nodes.py Thu Oct 07 18:46:23 2010 +0200
+++ b/scoped_nodes.py Sat Oct 09 19:33:51 2010 +0000
@@ -19,7 +19,7 @@
# with logilab-astng. If not, see <http://www.gnu.org/licenses/>.
"""This module contains the classes for "scoped" node, i.e. which are opening a
new local scope in the language definition : Module, Class, Function (and
-Lambda and GenExpr to some extends).
+Lambda, GenExpr, DictComp and SetComp to some extent).
"""
from __future__ import generators
@@ -118,7 +118,7 @@
def scope(self):
"""return the first node defining a new scope (i.e. Module,
- Function, Class, Lambda but also GenExpr)
+ Function, Class, Lambda but also GenExpr, DictComp and SetComp)
"""
return self
@@ -415,6 +415,33 @@
GenExpr.scope_lookup = LocalsDictNodeNG._scope_lookup
+class DictComp(LocalsDictNodeNG):
+ _astng_fields = ('key', 'value', 'generators')
+
+ def __init__(self):
+ self.locals = {}
+ self.key = None
+ self.value = None
+ self.generators = []
+
+ def frame(self):
+ return self.parent.frame()
+DictComp.scope_lookup = LocalsDictNodeNG._scope_lookup
+
+
+class SetComp(LocalsDictNodeNG):
+ _astng_fields = ('elt', 'generators')
+
+ def __init__(self):
+ self.locals = {}
+ self.elt = None
+ self.generators = []
+
+ def frame(self):
+ return self.parent.frame()
+SetComp.scope_lookup = LocalsDictNodeNG._scope_lookup
+
+
# Function ###################################################################
# HG changeset patch
# User Daniel Harding <[email protected]>
# Date 1286653946 0
# Node ID 0e616ba6d5042252524396c62469c6ced8f1ad79
# Parent fc7b47be6123e8b37f5c9db35474f165d514a878
update VariablesChecker to take into account the nested scopes of dictionary
and set comprehensions
diff -r fc7b47be6123 -r 0e616ba6d504 checkers/variables.py
--- a/checkers/variables.py Sat Oct 09 19:51:05 2010 +0000
+++ b/checkers/variables.py Sat Oct 09 19:52:26 2010 +0000
@@ -181,7 +181,7 @@
def visit_genexpr(self, node):
"""visit genexpr: update consumption analysis variable
"""
- self._to_consume.append((copy(node.locals), {}, 'genexpr'))
+ self._to_consume.append((copy(node.locals), {}, 'comprehension'))
def leave_genexpr(self, _):
"""leave genexpr: update consumption analysis variable
@@ -189,6 +189,28 @@
# do not check for not used locals here
self._to_consume.pop()
+ def visit_dictcomp(self, node):
+ """visit dictcomp: update consumption analysis variable
+ """
+ self._to_consume.append((copy(node.locals), {}, 'comprehension'))
+
+ def leave_dictcomp(self, _):
+ """leave dictcomp: update consumption analysis variable
+ """
+ # do not check for not used locals here
+ self._to_consume.pop()
+
+ def visit_setcomp(self, node):
+ """visit setcomp: update consumption analysis variable
+ """
+ self._to_consume.append((copy(node.locals), {}, 'comprehension'))
+
+ def leave_setcomp(self, _):
+ """leave setcomp: update consumption analysis variable
+ """
+ # do not check for not used locals here
+ self._to_consume.pop()
+
def visit_function(self, node):
"""visit function: update consumption analysis variable and check
locals
"""
@@ -349,9 +371,9 @@
# scope, ignore it. This prevents to access this scope instead of
# the globals one in function members when there are some common
# names. The only exception is when the starting scope is a
- # genexpr and its direct outer scope is a class
+ # comprehension and its direct outer scope is a class
if scope_type == 'class' and i != start_index and not (
- base_scope_type == 'genexpr' and i == start_index-1):
+ base_scope_type == 'comprehension' and i == start_index-1):
# XXX find a way to handle class scope in a smoother way
continue
# the name has already been consumed, only check it's not a loop
_______________________________________________
Python-Projects mailing list
[email protected]
http://lists.logilab.org/mailman/listinfo/python-projects