summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-08-04 19:19:46 +0300
committercpopa <devnull@localhost>2014-08-04 19:19:46 +0300
commit2829b834031fdbe7b9c7fda13e25fc91bff17761 (patch)
tree2de14a58892af3d09909f38601046d6b44854ed1
parent712a62d4f9c3d2c2ada9cb100a672472c41953b3 (diff)
downloadpylint-2829b834031fdbe7b9c7fda13e25fc91bff17761.tar.gz
Don't emit 'attribute-defined-outside-init' if the attribute was set by a function call in a defining method. Closes issue #192.
-rw-r--r--ChangeLog3
-rw-r--r--checkers/classes.py33
-rw-r--r--test/input/func_defining-attr-methods_order.py34
3 files changed, 69 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index b4df421..26f417e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -30,6 +30,9 @@ ChangeLog for Pylint
* Properly handle nested format string fields. Closes issue #294.
+ * Don't emit 'attribute-defined-outside-init' if the attribute
+ was set by a function call in a defining method. Closes issue #192.
+
2014-07-26 -- 1.3.0
* Allow hanging continued indentation for implicitly concatenated
diff --git a/checkers/classes.py b/checkers/classes.py
index 61769f2..b0d827a 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -35,6 +35,33 @@ else:
NEXT_METHOD = 'next'
ITER_METHODS = ('__iter__', '__getitem__')
+def _called_by_methods(func, klass, defining_methods):
+ """ Check if the func was called by any method from
+ *defining_methods*, belonging to the *klass*.
+ Returns True if so, False otherwise.
+ """
+ if not isinstance(func, astroid.Function):
+ return False
+ for meth in defining_methods:
+ try:
+ methods = klass.getattr(meth)
+ except astroid.NotFoundError:
+ continue
+ for method in methods:
+ for callfunc in method.nodes_of_class(astroid.CallFunc):
+ try:
+ bound = next(callfunc.func.infer())
+ except (astroid.InferenceError, StopIteration):
+ continue
+ if not isinstance(bound, astroid.BoundMethod):
+ continue
+ func_obj = bound._proxied
+ if isinstance(func_obj, astroid.UnboundMethod):
+ func_obj = func_obj._proxied
+ if func_obj.name == func.name:
+ return True
+ return False
+
def class_is_abstract(node):
"""return true if the given class node should be considered as an abstract
class
@@ -301,6 +328,12 @@ a metaclass class method.'}
except astroid.NotFoundError:
for node in nodes:
if node.frame().name not in defining_methods:
+ # If the attribute was set by a callfunc in any
+ # of the defining methods, then don't emit
+ # the warning.
+ if _called_by_methods(node.frame(), cnode,
+ defining_methods):
+ continue
self.add_message('attribute-defined-outside-init',
args=attr, node=node)
diff --git a/test/input/func_defining-attr-methods_order.py b/test/input/func_defining-attr-methods_order.py
index 888b192..28918f2 100644
--- a/test/input/func_defining-attr-methods_order.py
+++ b/test/input/func_defining-attr-methods_order.py
@@ -1,4 +1,4 @@
-# pylint: disable=C0103
+# pylint: disable=C0103, too-few-public-methods
''' Test that y is defined properly, z is not.
Default defining methods are __init__,
@@ -39,3 +39,35 @@ class B(A):
def test(self):
""" test """
self.z = 44
+
+class C(object):
+ ''' class C '''
+
+ def __init__(self):
+ self._init()
+
+ def _init(self):
+ ''' called by __init__ '''
+ self.z = 44
+
+class D(object):
+ ''' class D '''
+
+ def setUp(self):
+ ''' defining method '''
+ self.set_z()
+
+ def set_z(self):
+ ''' called by the parent. '''
+ self.z = 42
+
+class E(object):
+ ''' Reassign the function. '''
+
+ def __init__(self):
+ i = self._init
+ i()
+
+ def _init(self):
+ ''' called by __init__ '''
+ self.z = 44