Hi,
I tried sending this before, but it I accidentally used HTML and it
looks like it got stripped. See if this works better...
I have been seeing lots of R0903 "too few public methods" with classes
that legitimately have none - context managers mostly. I have
implemented a fix that suppresses that warning when it detects a
well-formed set of __x__ methods, and as a by-product can warn when a
set is only partly implemented. The diff is below. Hope this is the
right procedure for offering a patch, and hope it meets the grade.
Thanks,
Pete.
diff -r f673b9738dcd checkers/design_analysis.py
--- a/checkers/design_analysis.py Mon Jan 09 21:25:10 2012 +0100
+++ b/checkers/design_analysis.py Sun Feb 05 15:31:26 2012 +0000
@@ -30,6 +30,41 @@
# regexp for ignored argument name
IGNORED_ARGUMENT_NAMES = re.compile('_.*')
+class SpecialMethodChecker:
+ """A functor that checks for consistency of a set of special methods"""
+ def __init__(self, methods_found, on_error):
+ """ Stores the set of __x__ method names that were found in the
+ class and a callable that will be called with args to R0024 if
+ the check fails"""
+ self.methods_found = methods_found
+ self.on_error = on_error
+
+ def __call__(self, methods_required, protocol):
+ """Checks the set of method names given to __init__ against the
set required.
+ If they are all present, returns true.
+ If they are all absent, returns false.
+ If some are present, reports the error and returns false"""
+ required_methods_found = methods_required & self.methods_found
+ if required_methods_found == methods_required:
+ return True
+
+ if required_methods_found != set():
+ required_methods_missing = methods_required -
self.methods_found
+ self.on_error((protocol,
+ list(required_methods_found),
+ list(required_methods_missing)))
+ return False
+
+SPECIAL_METHODS = [('Context manager', ('__enter__',
+ '__exit__')),
+ ('Container', ('__len__',
+ '__getitem__',
+ '__setitem__',
+ '__delitem__',
+ '__iter__')),
+ ('Callable', ('__call__',)),
+]
+
def class_is_abstract(klass):
"""return true if the given class node should be considered as an
abstract
class
@@ -76,6 +111,9 @@
ancestor.'),
'R0923': ('Interface not implemented',
'Used when an interface class is not implemented
anywhere.'),
+ 'R0924': ('Badly implemented %s, implements %s but not %s',
+ 'A class implements some of the special methods for a
particular \
+ protocol, but not all of them')
}
@@ -223,9 +261,12 @@
def leave_class(self, node):
"""check number of public methods"""
nb_public_methods = 0
+ special_methods_found=set()
for method in node.methods():
if not method.name.startswith('_'):
nb_public_methods += 1
+ if method.name.startswith("__"):
+ special_methods_found.add(method.name)
# Does the class contain less than 20 public methods ?
if nb_public_methods > self.config.max_public_methods:
self.add_message('R0904', node=node,
@@ -234,6 +275,14 @@
# stop here for exception, metaclass and interface classes
if node.type != 'class':
return
+ # Does the class implement special methods consitently?
+ # If so, don't enforce minimum public methods.
+ check_special = SpecialMethodChecker(special_methods_found,
+ lambda args:
self.add_message ('R0924', node=node, args = args))
+ protocols = [check_special(set(p[1]), p[0]) for p in
SPECIAL_METHODS]
+ if True in protocols:
+ return
+
# Does the class contain more than 5 public methods ?
if nb_public_methods < self.config.min_public_methods:
self.add_message('R0903', node=node,
diff -r f673b9738dcd test/input/func_special_methods.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/input/func_special_methods.py Sun Feb 05 15:31:26 2012 +0000
@@ -0,0 +1,47 @@
+#pylint: disable=C0111
+__revision__ = None
+
+class ContextManager:
+ def __enter__(self):
+ pass
+ def __exit__(self, *args):
+ pass
+ def __init__(self):
+ pass
+
+class BadContextManager:
+ def __enter__(self):
+ pass
+ def __init__(self):
+ pass
+
+class Container:
+ def __init__(self):
+ pass
+ def __len__(self):
+ return 0
+ def __getitem__(self, key):
+ pass
+ def __setitem__(self, key, value):
+ pass
+ def __delitem__(self, key, value):
+ pass
+ def __iter__(self):
+ pass
+
+class BadContainer:
+ def __init__(self):
+ pass
+ def __len__(self):
+ return 0
+ def __setitem__(self, key, value):
+ pass
+ def __iter__(self):
+ pass
+
+class Callable:
+ def __call__(self):
+ pass
+ def __init__(self):
+ pass
+
diff -r f673b9738dcd test/messages/func_special_methods.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/messages/func_special_methods.txt Sun Feb 05 15:31:26 2012
+0000
@@ -0,0 +1,5 @@
+R: 12:BadContextManager: Badly implemented Context manager, implements
['__enter__'] but not ['__exit__']
+R: 12:BadContextManager: Too few public methods (0/2)
+R: 32:BadContainer: Badly implemented Container, implements
['__setitem__', '__iter__', '__len__'] but not ['__delitem__',
'__getitem__']
+R: 32:BadContainer: Too few public methods (0/2)
+
_______________________________________________
Python-Projects mailing list
[email protected]
http://lists.logilab.org/mailman/listinfo/python-projects