diff options
author | cpopa <devnull@localhost> | 2014-08-04 19:19:46 +0300 |
---|---|---|
committer | cpopa <devnull@localhost> | 2014-08-04 19:19:46 +0300 |
commit | 2829b834031fdbe7b9c7fda13e25fc91bff17761 (patch) | |
tree | 2de14a58892af3d09909f38601046d6b44854ed1 | |
parent | 712a62d4f9c3d2c2ada9cb100a672472c41953b3 (diff) | |
download | pylint-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-- | ChangeLog | 3 | ||||
-rw-r--r-- | checkers/classes.py | 33 | ||||
-rw-r--r-- | test/input/func_defining-attr-methods_order.py | 34 |
3 files changed, 69 insertions, 1 deletions
@@ -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 |