To begin, check it out manually, in order to get an idea of what exactly PyLint does. An example of a file to check (let tester.py be):
class MyClass(object): pass class MyAnotherClass(MyClass): pass class MyNormalClass: pass class MyStringInteger(str, int, object): pass
Now an example of a script that will check the source code:
import ast MODULE_PATH = "tester.py" with open(MODULE_PATH, "r", encoding="utf8") as f: MODULE_AS_STRING = f.read() root = ast.parse(source=MODULE_AS_STRING) nodes_gen = ast.walk(root) for node in nodes_gen: if type(node) is ast.ClassDef: print("Yo! Found class definition. Classname: ", node.name) bases_list = [] for bc in node.bases: bases_list.append(bc.id) print("Base classes:", ', '.join(bases_list))
Result:
Yo! Found class definition. Classname: MyClass Base classes: object Yo! Found class definition. Classname: MyAnotherClass Base classes: MyClass Yo! Found class definition. Classname: MyNormalClass Base classes: Yo! Found class definition. Classname: MyStringInteger Base classes: str, int, object
The script opens the module as text, then converts it into an abstract syntax tree (AST) and you can work with this tree already. Pylint works in much the same way, but uses NodeVisitor to travel through a tree. This Visitor calls certain methods when it finds an item. For example, calls visit_classdef when stumbles upon a class definition. Calls visit_callfunc when visit_callfunc into a function call. And so on - constants, assert , mathematical operations and everything that is in a language. In order to attach yourself a fashionable pylint add-on, you need to write such a class (file pylint_plugin.py):
from pylint.checkers import BaseChecker, utils from pylint.interfaces import IAstroidChecker BASE_ID = 56 def register(linter): print("Registering pylint plugin...") linter.register_checker(MyClassChecker(linter)) print("Yo! Registered!") class MyClassChecker(BaseChecker): __implements__ = IAstroidChecker MESSAGE_ID = "bad_classes_with_object" msgs = { 'W%d01' % BASE_ID: ("%s classname has some problems with object...", MESSAGE_ID, "Please rewrite") } # Обязательно необходимо определить этот аттрибут name = "object_inheritance_checker" @utils.check_messages(MESSAGE_ID) def visit_classdef(self, node): for bc in node.bases: # Внимание - это уже класс astroid.Name if bc.name == 'object': print("MY CLASSES PRINT! YO!", node) self.add_message(msg_id=self.MESSAGE_ID, node=node, args=node.name)
The pylint documentation pylint not shine, so I’m not quite sure about the plugin’s correctness and it’s written “just to work” (alas). In order to write your own you will need the following things (all this is described at https://pylint.readthedocs.io/en/latest/reference_guide/custom_checkers.html ):
- The
register(linter) function, which will say that you need to use additional checks. In this function, you must call linter.register_checker for all additional checks. Actually, the classes are verifiers (Visitors). In these classes must be defined:
- Name (attribute
name ) - actually, the name of the check. - The priority (
priority attribute) must be less than zero. - Message dictionary
msgs . This dictionary should have the following structure: msgs = {'message-id': ('displayed-message', 'message-symbol', 'message-help')} . message_id must be unique and not in conflict with existing ones. It consists of an identifier - C, W, E, F, R , meaning Convention - соглашение, Warning - предупреждение, Error - ошибка, Fatal - ужасная ошибка and Refactoring - переделать . Also message_id consists of a 4-digit number. Almost all projects that I have seen use some of their BASE_ID and message number. So message_id limited to 5 characters. What this was done is unthinkable. - Actually, the functions themselves that check certain constructions in the code. Already mentioned
visit_<node_name> We need to check the definition of a class and its heirs - so do just that. In the function itself, it is necessary to note that pylint is not used by the native ast module, but by the astroid module and, accordingly, all the nodes from astroid .
If all this has been done - the plugin is ready and you can call it something like this:
pylint --load-plugins "pylint_plugin" tester.py
At the end there will be a report like this:
Messages -------- +------------------------+------------+ |message id |occurrences | +========================+============+ |missing-docstring |5 | +------------------------+------------+ |too-few-public-methods |3 | +------------------------+------------+ |bad_classes_with_object |2 | +------------------------+------------+
2 bad classes - as described.