pycqa / mccabe Goto Github PK
View Code? Open in Web Editor NEWMcCabe complexity checker for Python
Home Page: pypi.python.org/pypi/mccabe
License: Other
McCabe complexity checker for Python
Home Page: pypi.python.org/pypi/mccabe
License: Other
Current flake8
requires mccabe >= 0.6.0, < 0.7.0
, which means that if you use flake8
then there's currently no way to avoid the deprecated requirement of pytest-runner
that was removed over a year ago in 9274755. This seems unfortunate. Is there anything blocking a release, and/or can anyone do anything to help?
$ tox -e py37
...
============================= test session starts ==============================
platform linux -- Python 3.7.0b4, pytest-3.6.0, py-1.5.3, pluggy-0.6.0
rootdir: .../mccabe, inifile:
collected 14 items
test_mccabe.py ..F........... [100%]
=================================== FAILURES ===================================
____________________ McCabeTestCase.test_expr_as_statement _____________________
self = <test_mccabe.McCabeTestCase testMethod=test_expr_as_statement>
def test_expr_as_statement(self):
complexity = get_complexity_number(expr_as_statement, self.strio)
> self.assertEqual(complexity, 1)
E AssertionError: 2 != 1
test_mccabe.py:192: AssertionError
===================== 1 failed, 13 passed in 0.06 seconds ======================
ERROR: InvocationError: '.../mccabe/.tox/py37/bin/python setup.py test -q'
___________________________________ summary ____________________________________
ERROR: py37: commands failed
I have found that if a function name appears more than one time in a file, the output of McCabe complexity for this function name only appear once. I think it should output all of them.
I have downloaded the mccabe source code file mccabe-0.6.1.tar.gz and transfer it to my offline server, and got error when install it using /usr/local/bin/pip3 install mccabe-0.6.1.tar.gz --no-index --find-links /usr/lib64/comheadcloud/pypisoft/sourcecode. It was said in the log that the error was caused by "Could not find a version that satisfies the requirement setuptools>=34.4" . The installation process wanted to get setuptools by Internet, but I have installed setuptools with version 47.3.2, and I used --no-index -find-links options to instruct the installation process to find dependencies locally. Why the installation process still search http://pypi.org.
`
2021-05-26T05:33:10,010 Using pip 21.0.1 from /usr/local/lib/python3.6/site-packages/pip (python 3.6)
2021-05-26T05:33:10,013 Non-user install because site-packages writeable
2021-05-26T05:33:10,050 Created temporary directory: /tmp/pip-ephem-wheel-cache-6uru8aqx
2021-05-26T05:33:10,051 Created temporary directory: /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,051 Initialized build tracking at /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,051 Created build tracker: /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,051 Entered build tracker: /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,052 Created temporary directory: /tmp/pip-install-76xz8yzr
2021-05-26T05:33:10,084 Processing ./setuptools_scm-4.1.2.tar.gz
2021-05-26T05:33:10,085 Created temporary directory: /tmp/pip-req-build-1dkt5ol0
2021-05-26T05:33:10,129 Added file:///usr/lib64/comheadcloud/pypisoft/sourcecode/setuptools_scm-4.1.2.tar.gz to build tracker '/tmp/pip-req-tracker-ptvg3wgt'
2021-05-26T05:33:10,133 Created temporary directory: /tmp/pip-build-env-colihfbk
2021-05-26T05:33:10,134 Running command /usr/bin/python3 /usr/local/lib/python3.6/site-packages/pip install --ignore-installed --no-user --prefix /tmp/pip-build-env-colihfbk/overlay --no-warn-script-location -v --no-binary :none: --only-binary :none: -i https://pypi.o
rg/simple -- 'setuptools>=34.4' wheel
2021-05-26T05:33:10,666 Using pip 21.0.1 from /usr/local/lib/python3.6/site-packages/pip (python 3.6)
2021-05-26T05:33:10,667 Non-user install by explicit request
2021-05-26T05:33:10,695 Created temporary directory: /tmp/pip-ephem-wheel-cache-bpogfslk
2021-05-26T05:33:10,696 Created build tracker: /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,696 Entered build tracker: /tmp/pip-req-tracker-ptvg3wgt
2021-05-26T05:33:10,696 Created temporary directory: /tmp/pip-install-etvadzrp
2021-05-26T05:33:10,711 1 location(s) to search for versions of setuptools:
2021-05-26T05:33:10,711 * https://pypi.org/simple/setuptools/
2021-05-26T05:33:10,711 Fetching project page and analyzing links: https://pypi.org/simple/setuptools/
2021-05-26T05:33:10,711 Getting page https://pypi.org/simple/setuptools/
2021-05-26T05:33:10,712 Found index url https://pypi.org/simple
2021-05-26T05:33:10,714 Looking up "https://pypi.org/simple/setuptools/" in the cache
2021-05-26T05:33:10,714 Request header has "max_age" as 0, cache bypassed
2021-05-26T05:33:10,715 Starting new HTTPS connection (1): pypi.org:443
2021-05-26T05:33:10,717 Incremented Retry for (url='/simple/setuptools/'): Retry(total=4, connect=None, read=None, redirect=None, status=None)
2021-05-26T05:33:10,717 WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f9698c683c8>: Failed to establish a new connect
ion: [Errno -2] Name or service not known',)': /simple/setuptools/
2021-05-26T05:33:10,718 Starting new HTTPS connection (2): pypi.org:443
2021-05-26T05:33:10,718 Incremented Retry for (url='/simple/setuptools/'): Retry(total=3, connect=None, read=None, redirect=None, status=None)
2021-05-26T05:33:11,219 WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f9698c68630>: Failed to establish a new connect
ion: [Errno -2] Name or service not known',)': /simple/setuptools/
2021-05-26T05:33:11,219 Starting new HTTPS connection (3): pypi.org:443
2021-05-26T05:33:11,220 Incremented Retry for (url='/simple/setuptools/'): Retry(total=2, connect=None, read=None, redirect=None, status=None)
2021-05-26T05:33:12,221 WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f9698c68780>: Failed to establish a new connect
ion: [Errno -2] Name or service not known',)': /simple/setuptools/
2021-05-26T05:33:12,222 Starting new HTTPS connection (4): pypi.org:443
2021-05-26T05:33:12,223 Incremented Retry for (url='/simple/setuptools/'): Retry(total=1, connect=None, read=None, redirect=None, status=None)
2021-05-26T05:33:14,226 WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f9698c688d0>: Failed to establish a new connect
ion: [Errno -2] Name or service not known',)': /simple/setuptools/
2021-05-26T05:33:14,226 Starting new HTTPS connection (5): pypi.org:443
2021-05-26T05:33:14,227 Incremented Retry for (url='/simple/setuptools/'): Retry(total=0, connect=None, read=None, redirect=None, status=None)
2021-05-26T05:33:18,232 WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f9698c68a20>: Failed to establish a new connect
ion: [Errno -2] Name or service not known',)': /simple/setuptools/
2021-05-26T05:33:18,232 Starting new HTTPS connection (6): pypi.org:443
2021-05-26T05:33:18,234 Could not fetch URL https://pypi.org/simple/setuptools/: connection error: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/setuptools/ (Caused by NewConnectionError('<pip._vendor.urllib3.connection.HTTPSCo
nnection object at 0x7f9698c68b70>: Failed to establish a new connection: [Errno -2] Name or service not known',)) - skipping
2021-05-26T05:33:18,251 Given no hashes to check 0 links for project 'setuptools': discarding no candidates
2021-05-26T05:33:18,251 ERROR: Could not find a version that satisfies the requirement setuptools>=34.4
2021-05-26T05:33:18,252 ERROR: No matching distribution found for setuptools>=34.4`
When running the self tests on the pypi sdist file, I see:
# setup.py test
running test
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.
running egg_info
writing manifest file 'mccabe.egg-info/SOURCES.txt'
running build_ext
test_mccabe (unittest.loader._FailedTest) ... ERROR
======================================================================
ERROR: test_mccabe (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_mccabe
Traceback (most recent call last):
File "/usr/pkg/lib/python3.10/unittest/loader.py", line 436, in _find_test_path
module = self._get_module_from_name(name)
File "/usr/pkg/lib/python3.10/unittest/loader.py", line 377, in _get_module_from_name
__import__(name)
File "/scratch/devel/py-mccabe/work/mccabe-0.7.0/test_mccabe.py", line 244, in <module>
@settings(
NameError: name 'settings' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.000s
I see that it try
s to import hypothesmith, but this is not installed on my system, and it has an except
clause just ignoring the problem.
Repro script:
while []: pass
if False: pass
Demo:
$ python -m mccabe demo.py
Traceback (most recent call last):
File "/Users/buck/prefices/brew/Cellar/python/2.7.7_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 162, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/Users/buck/prefices/brew/Cellar/python/2.7.7_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/runpy.py", line 72, in _run_code
exec code in run_globals
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 306, in <module>
main(sys.argv[1:])
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 291, in main
visitor.preorder(tree, visitor)
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 45, in preorder
self.dispatch(tree, *args) # XXX *args make sense?
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 39, in dispatch
return meth(node, *args)
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 29, in default
self.dispatch(child, *args)
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 39, in dispatch
return meth(node, *args)
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 181, in visitIf
pathnode = self.appendPathNode(name)
File "/Users/buck/trees/mine/mccabe/mccabe.py", line 146, in appendPathNode
self.graph.connect(self.tail, pathnode)
AttributeError: 'NoneType' object has no attribute 'connect'
Hello,
I'm not sue but it seems to me that mccab
does take local functions into account when calculating the cc score.
Just a stupid example.
def foobar():
def _my_local_function():
return 'local'
return _my_local_function()
IMHO in some cases there are better reasons to keep a function local then reducing the cc score.
Is there a way to configure mccab that way that it ignore local functions when calculating cc score?
I'm using mccab via flake8 in Emacs via python-lsp-server and eglot package.
Right now it generates this error message:
» python -m mccabe
Traceback (most recent call last):
File "/Users/sobolev/.pyenv/versions/3.10.0/lib/python3.10/runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/Users/sobolev/.pyenv/versions/3.10.0/lib/python3.10/runpy.py", line 86, in _run_code
exec(code, run_globals)
File "/Users/sobolev/Desktop/mypy/.venv/lib/python3.10/site-packages/mccabe.py", line 346, in <module>
main(sys.argv[1:])
File "/Users/sobolev/Desktop/mypy/.venv/lib/python3.10/site-packages/mccabe.py", line 327, in main
code = _read(args[0])
IndexError: list index out of range
Compare it with, for example, venv
:
» python -m venv
usage: venv [-h] [--system-site-packages] [--symlinks | --copies] [--clear]
[--upgrade] [--without-pip] [--prompt PROMPT] [--upgrade-deps]
ENV_DIR [ENV_DIR ...]
venv: error: the following arguments are required: ENV_DIR
I think that this can be improved. Showing help by default seems like a reasonable thing:
» python -m mccabe
Usage: mccabe.py [options]
Options:
-h, --help show this help message and exit
-d, --dot output a graphviz dot file
-m THRESHOLD, --min=THRESHOLD
minimum complexity for output
I will send a PR soon.
It would be nice if setup.py
implemented the conditional requirement suggested by pytest-runner: https://github.com/pytest-dev/pytest-runner#conditional-requirement
Assuming you have some code that for historical reason on not may need an exemption from the desired max-complexity
value, you should be able to specify an upper limit for the exception.
Without an upper limit, there is no way to prevent complexity from increasing because the linter will always be happy.
Practical example at https://github.com/ansible/ansible-lint/blob/master/.flake8#L54-L55 which adds exception for specific file.
If working correctly something like noqa: C901(12)
should be allowed, one that states that C901 should be ignored unless is bigger than 12 (assume that your overall project has lower limit).
I am not aware of any workaround for achieving this.
I've been using flake8 for checking complexity of methods. I now set the max complexity to 6, which mostly results in more readable code, but I did find there are some exceptions. Now it's easy to exclude one method from the complexity check, but I would prefer to have a different max complexity just for this method instead. That way it won't go up further without anyone noticing.
I'm not sure what an implementation for this would look like, though. If I had an idea, I could give it a shot.
Perhaps something like # max-complexity: 7 could work. Right now # noqa: C901 is the only option.
As I see that last commit was made more than 6 months ago, that there are open PRs more than two years old and that last release is was about 3 years ago, it makes me feel that the project is not maintained.
Is that true or is only deeply dormant?
The fact that it is not possible to increase complexity limit using # noqa
seems like something that drives people towards dropping it. Currently there is nothing that can prevent a noqa function from increasing its complexity so, while trying to keep complexity limited you are sometimes forced to add exceptions, once added nothing will prevent them from growing, creating a vicious cycle.
There are two minor issues with this:
(1) Having different tarballs (different content or different checksum) with the same name around is a bit problematic for packaging the software (here MacPorts);
(2) I think, having the License file around is required by a MIT like license;
Hi everyone,
I am using mccabe with python command line: python -m mccabe --min 1 ./containers_sugar/sugar.py
and it doesn't raise any error code (>0) when there are functions with complexity greater than the threshold.
Is there anything I am missing? is it the correct behavior?
Currently the below code generates the following graph, giving a complexity of 5:
def f():
try:
print(1)
except TypeA:
print(2)
except TypeB:
print(3)
else:
print(4)
finally:
if x:
print(5.1)
else:
print(5.2)
I don't think this models the try-else clause correctly. The above graph seems to indicate that the else sometimes runs instead of the main clause, which isn't correct. I believe this is the correct model:
This seems to correctly indicate that the else always runs directly after the main try clause, if it runs, and never in a line of execution that involves the exception handlers. This has 1 less complexity than the above graph. This yields one less complexity than the current interpretation.
I'm still seeing the off-by-one-twice issue with flake8 complexity today.
Can we get a release to make it right?
hi all,
guard clauses are a neat way to reduce complexity (and indenting) of code, since the ‘main flow’ is the ‘happy path’, while the ‘unhappy paths’ lead to raising exceptions, returning early, or break
/continue
from a loop.
for example:
def f():
if error_condition:
raise ...
a()
if error_condition:
raise ...
b()
if error_condition:
raise ...
something similar for early returns:
def f():
if check_something():
return
a()
if error:
return
result = ...
return result
and a loop example:
for item in ...:
if ...:
break # or continue
if ...:
break # or continue
actual_work_goes_here()
right now, code like below is an easy way to make the complexity go up (and potentially break flake8
linting, for example):
def f():
if False:
return
if False:
return
if False:
return
if False:
return
if False:
return
if False:
return
if False:
return
if False:
return
would it be useful to make it possible to not count such constructs for complexity metrics?
the generic pattern would be something along those lines:
if
ast node has a single child nodereturn
/ continue
/ break
statementi'm not sure this is even possible or desirable, so i'm just dumping this idea here since others may have useful thoughts to add.
Hi!
Why def inside def increase complexity of outer function?
def inside def looks like method inside class. Maybe, complexity of inner function should be independent from outer function?
For example, complexity of test_nested_functions_snippet equals to 1 instead of 3.
on a file that has the UTF-8-BOM (see this stackoverflow post), mccabe throws something like this:
vagrant@localhost:/vagrant$ python -m mccabe --min 5 evap/grades/templatetags/grades_templatetags.py
Traceback (most recent call last):
File "/usr/lib/python3.4/runpy.py", line 170, in _run_module_as_main
"__main__", mod_spec)
File "/usr/lib/python3.4/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/usr/local/lib/python3.4/dist-packages/mccabe.py", line 310, in <module>
main(sys.argv[1:])
File "/usr/local/lib/python3.4/dist-packages/mccabe.py", line 292, in main
tree = compile(code, args[0], "exec", ast.PyCF_ONLY_AST)
File "evap/grades/templatetags/grades_templatetags.py", line 1
from django.template import Library
^
SyntaxError: invalid character in identifier
before this error, i didn't even know this BOM thing existed and i needed quite a while to figure it out. while i generally don't think such archaic stuff needs to be supported, i think mccabe could handle this more gracefully, especially since it is allowed per the standard and all other tools don't seem to be bothered by those bytes.
example file that has the BOM: https://raw.githubusercontent.com/fsr-itse/EvaP/803ac9825aac1024137220bed631e4302d580d18/evap/grades/templatetags/grades_templatetags.py
There have been some major changes/improvements since 0.2.
Please cut a fresh release.
💖
It looks like mccabe is ignoring python 3.5 coroutines defined like
async def foobar(a, b, c):
whatever(a, b, c)
I tried it via flake8 version:
2.5.1 (pep8: 1.7.0, pyflakes: 1.0.0, mccabe: 0.3.1) CPython 3.5.0+ on Linux
Would it be possible to have the ranges reported by this tool updated to start at the start of the offending identifier?
For example with the following code:
from example import (
f401_unused as unused_module)
def c901_too_complex(x):
if x > 1:
if x > 2:
if x > 3:
if x > 4:
if x > 5:
if x > 6:
if x > 7:
if x > 8:
if x > 9:
if x > 10:
if x > 11:
pass
class Foo:
def foo_c901_too_complex(x):
if x > 1:
if x > 2:
if x > 3:
if x > 4:
if x > 5:
if x > 6:
if x > 7:
if x > 8:
if x > 9:
if x > 10:
if x > 11:
pass
def indent_unaligned():
try:
print('H501 %(self)s' % locals())
except: # <- H201
pass
This module simply reports the start of the line as the problem location:
> python -m mccabe --min 10 customRange.py
5:1: 'c901_too_complex' 12
21:1: 'Foo.foo_c901_too_complex' 12
If the identifier start was reported instead, the positions would be 5:5
and 21:9
respectively. As it stands right now the column number is just plain wrong.
I was updating a port of mccabe and noticed that test_mccabe.py
was missing from the Pypi release.
Given
def func(x):
if x == "a":
return "a"
elif x == "a":
return "a"
elif x == "a":
return "a"
elif x == "a":
return "a"
elif x == "a": # repeat elif/return 160 times total
return "a"
We get the error when running flake8 on it:
ERROR:1:1: X002 File "flake8/main/cli.py", line 16, in main
ERROR:1:1: X002 File "flake8/main/application.py", line 412, in run
ERROR:1:1: X002 File "flake8/main/application.py", line 400, in _run
ERROR:1:1: X002 File "flake8/main/application.py", line 318, in run_checks
ERROR:1:1: X002 File "flake8/checker.py", line 340, in run
ERROR:1:1: X002 File "flake8/checker.py", line 324, in run_serial
ERROR:1:1: X002 File "flake8/checker.py", line 612, in run_checks
ERROR:1:1: X002 File "flake8/checker.py", line 520, in run_ast_checks
ERROR:1:1: X002 File "mccabe.py", line 266, in run
ERROR:1:1: X002 File "mccabe.py", line 47, in preorder
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 File "mccabe.py", line 167, in default
ERROR:1:1: X002 File "mccabe.py", line 31, in default
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 File "mccabe.py", line 135, in visitFunctionDef
ERROR:1:1: X002 File "mccabe.py", line 111, in dispatch_list
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 File "mccabe.py", line 177, in visitIf
ERROR:1:1: X002 File "mccabe.py", line 190, in _subgraph
ERROR:1:1: X002 File "mccabe.py", line 204, in _subgraph_parse
ERROR:1:1: X002 File "mccabe.py", line 111, in dispatch_list
...
ERROR:1:1: X002 File "mccabe.py", line 177, in visitIf
ERROR:1:1: X002 File "mccabe.py", line 190, in _subgraph
ERROR:1:1: X002 File "mccabe.py", line 204, in _subgraph_parse
ERROR:1:1: X002 File "mccabe.py", line 111, in dispatch_list
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 File "mccabe.py", line 177, in visitIf
ERROR:1:1: X002 File "mccabe.py", line 190, in _subgraph
ERROR:1:1: X002 File "mccabe.py", line 204, in _subgraph_parse
ERROR:1:1: X002 File "mccabe.py", line 111, in dispatch_list
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 File "mccabe.py", line 177, in visitIf
ERROR:1:1: X002 File "mccabe.py", line 190, in _subgraph
ERROR:1:1: X002 File "mccabe.py", line 196, in _subgraph_parse
ERROR:1:1: X002 File "mccabe.py", line 111, in dispatch_list
ERROR:1:1: X002 File "mccabe.py", line 41, in dispatch
ERROR:1:1: X002 RecursionError: maximum recursion depth exceeded
Is cyclomatic complexity supposed to count paths that will never be executed?
Observe the following code:
def f(n):
if n > 4:
return "bigger than four"
elif n > 5:
return "is never executed"
else:
return "smaller than or equal to four"
The program outputs a cyclomatic complexity of 3 even though the elif
path will never be executed.
Regardless of if cyclomatic complexity as described in this article does (type I) or does not (type II) count code paths that will never be executed I think it is useful to make this distinction more clear than it is now.
Depending on what cyclomatic complexity is supposed to represent you could name their specific types. Some suggestions:
Type I cc : 'unminimized cc' or 'unreduced cc' (this is what the mccabe program currently evaluates for)
Type II cc : 'minimized cc' or 'reduced cc'
When using third party services libraries, or even on your own code, you may find a scenario were a function may return several different types of exceptions each of them requiring a simple but different action to be taken.
Exhaustive Exception treatment on those cases leads to a high complexity result which is right, as per the complexity calculation strategy, but an undesired result.
def foo(bar):
try:
do_whatever(bar)
except TypeA:
do_something_related_to_TypeA()
except TypeB:
do_something_related_to_TypeB()
except TypeC:
do_something_related_to_TypeC()
except TypeD:
do_something_related_to_TypeD()
except TypeE:
do_something_related_to_TypeE()
except TypeF:
do_something_related_to_TypeF()
except TypeG:
do_something_related_to_TypeG()
except TypeH:
do_something_related_to_TypeH()
except TypeI:
do_something_related_to_TypeI()
except TypeJ:
do_something_related_to_TypeJ()
python -Bm mccabe deffoobar.py
1:0: 'foo' 12
It will be interesting being able to ignore exceptions branching to properly compute the complexity of the actual function logic without the burden of the exception treatment one.
Using flake8, mccabe and setuptools leads to setting wrong max_complexity option.
When you set setup.cfg to
[flake8]
max-complexity = 2
the command
./setup.py flake8
will not produce correct output, while calling
flake8
does.
Setting it to max-complexity=1 works, but the type of the option is set to boolean. When set to 2 it's a string instead of the requested int thus leading to always False comparisons at the mccabe code.
The option is parsed correctly at pep8 but is overwritten at pep8.py in class StyleGuide:1629:
if options_dict:
options.__dict__.update(options_dict)
I don't know if the problem lies in the mccabe code, pep8 or flake8, so reporting it here.
Versions used:
Python 2.7.6
argparse (1.2.1)
autopep8 (0.9.1)
coverage (3.7.1)
flake8 (2.4.0)
matplotlib (1.3.1)
mccabe (0.3)
nose (1.3.1)
numpy (1.8.2)
pandas (0.13.1)
pep8 (1.5.7)
pexpect (3.1)
Pillow (2.3.0)
pip (1.5.4)
ply (3.4)
Pmw (1.3.2)
pyflakes (0.8.1)
Pygments (1.6)
pyinotify (0.9.4)
pyparsing (2.0.1)
python-dateutil (1.5)
pytz (2012c)
PyX (0.12.1)
requests (2.2.1)
ScientificPython (2.9.4)
scikit-learn (0.14.1)
scipy (0.13.3)
SciTools (0.9.0)
setuptools (2.2)
Shapely (1.3.0)
simplegeneric (0.8.1)
simplejson (3.3.1)
six (1.5.2)
SQLAlchemy (0.8.4)
ssh-import-id (3.21)
statsmodels (0.5.0)
sympy (0.7.4.1)
tables (3.1.1)
Twisted-Core (13.2.0)
UFL (1.5.0)
urllib3 (1.7.1)
virtualenv (1.11.4)
VTK (5.8.0)
Werkzeug (0.9.4)
wheel (0.24.0)
wsgiref (0.1.2)
xlrd (0.9.2)
xlwt (0.7.5)
This is generating the following warning in flake8:
WARNING flake8.options.manager:manager.py:207 option --max-complexity: please update from optparse string `type=` to argparse callable `type=` -- this will be an error in the future
The error is here:
Line 245 in 4b287e2
Line 322 in 4b287e2
The flint project is no longer maintained. GitHub will redirect anyone from flintwork/mccabe to PyCQA/mccabe. @florentx has an invitation pending to PyCQA and it will make sense to have flake8 and mccabe under the same organization. I can do this arbitrarily but I won't. I really want everyone's input. And if there isn't unanimous agreement, then I won't do it.
Really minor issue, but I noticed that in the changes section of the README it shows the release date of 0.7.0 to be 2021-01-23 when presumably it should be 2022-01-23?
def f():
"""hi"""
x = 1
def g():
"""hi"""
$ python3.5 mccabe.py test.py
1:0: 'f' 1
6:0: 'g' 2
$ git rev-parse HEAD
682145a37fee41fe7b6640244faa0c3f58e3b496
Line 294 in e92e9e7
mccabe/mccabe.py
294: with open(module_path, "rU") as mod:
301: with open(filename, 'rU') as f:
CPython PR : python/cpython#16959
The minimum complexity on the mccabe scale is one.
The simplest function should pass flake8 when max-complexity is 1.
There's two reasons this doesn't work:
M = π − s + 2
where π is the number of decision points in the program, and s is the number of exit points), I count zero decision points and one exit point, resulting in M==1.$ cat trivial.py
def trivial():
return None
$ flake8 --max-complexity 1 trivial.py
trivial.py:1:1: C901 'trivial' is too complex (2)
I was recently fixing a bunch of flake8 violations in a project. Two such failures were B006
and B008
, from PyCQA/flake8-bugbear. The way I like to fix these is to change the default value to None
and put an if statement within the function that sets the previous default value if the argument value is None
. You can probably see where this is going - each argument that I had to do this for resulted in the mccabe complexity going up by 1, which resulted in hitting our max-complexity
setting on a couple functions. These changes did not modify the actual behavior of the functions, and in general these branches do not increase the complexity of the function in any meaningful way. I don't want to modify the structure of the code just to work around a spurious linting error. It is also possible to use the condition expression syntax (e.g. arg = [] if arg is None else arg
), which this package does not count as a branch, but I find the if statement syntax more readable and prefer it.
Right now the only way to silence this error is to tell flake8 to ignore violations from this package (which flake8 designates as C901
) on the offending functions. However, it will continue to be suppressed if the function does happen to grow too complex in the future. I don't want that either. I'm wondering whether it might be possible to add a feature to this package that enables ignoring specific marked branches for the purposes of the complexity measure. This would probably be done with a comment, similar to how flake8's control comments work.
You might argue that the addition of this feature will encourage people to just ignore branches that are inconvenient to them. But this is already the case - many developers will simply set the C901
violation to ignored the moment they see it instead of taking it as a sign that the function has grown too complex and should be simplified. They may even do it on the file or project level. And I've done the same thing - in the case of the work I've been doing recently, these errors are mostly just annoying and do not indicate actual problems, and my only choice was to add flake8 ignores. Adding a way to specify finer-grained ignores will allow those developers, such as myself, who care in principle about measuring complexity to continue making use of this tool.
like # noqa
for PyFlakes and # pragma: no cover
for coverage it would be great if mccabe had a way of ignoring certain functions.
Usage:
max-complexity
which is way to high to have a real impact.(I kind of assumed there would be an existing issue about this but I couldn't see it, sorry if I'm being blind)
Any complexity added by a module-level try
block is not currently measured by mccabe.
It would be great if we'd have a GitHub Action for mccabe
.
Having asdf.py
:
elements = [0, 1, 2, 3, 4]
ignore = [1, 2]
def loop():
for x in elements:
if x in ignore:
continue
print(x)
def comprehension():
filtered = [x for x in elements if x not in ignore]
for x in filtered:
print(x)
loop()
print('----')
comprehension()
The McCabe complexity in loop()
seems to be higher than in comprehension()
:
$ python -m mccabe asdf.py
5:0: 'loop' 3
12:0: 'comprehension' 2
Is that really expected? Should not for
s and if
s in list comprehensions count towards total complexity?
Is it possible to set a different complexity limit for a specific package or module? We have an application that has a particularly complicated subpackage (rendering engine related) which really needs a much higher limit, than the remainder of the code.
If not, please point me in the right direction so I can submit a PR that adds the functionality!
Hey!
When I have a function with several helper functions, I want to group them in a common context for readability. If only the main function is called from outside and all those helper functions are just readability helpers (so this main function isn't huge), this gives me:
Then I create a function with inner functions:
_run()
that is the highest abstraction level inner functionreturn _run()
Details why this is probably the best solution here: https://stackoverflow.com/questions/50253517/how-to-group-functions-without-side-effects
The problem is that currently mccabe
sums up every inner function complexity with outer function complexity and outputs this as only one, huge result. Please consider:
def inner_uncalled_functions():
def _run():
return 0
def _f1():
return 0
def _f2():
return 0
return 0
def inner_uncalled_branched_functions():
def _run():
return 0
def _f1():
if True:
return 0
else:
return 1
def _f2():
if True:
return 0
else:
return 1
return 0
def inner_connected_functions():
def _run():
return _f1()
def _f1():
return _f2()
def _f2():
return 0
return _run()
$ python -m mccabe mccabe_nested_test.py
1:0: 'inner_uncalled_functions' 4
14:0: 'inner_uncalled_branched_functions' 6
33:0: 'inner_connected_functions' 4
(sorted for readability)
I'd love if there was a flag to treat inner functions as separate from outer function.
Since pytest version 7.0.0 there were some deprecations to API that emit warnings like:
../.venv/lib/python3.11/site-packages/_pytest/nodes.py:146
/Users/slava/Space/Emporus/emporus-core-runtime/.venv/lib/python3.11/site-packages/_pytest/nodes.py:146: PytestDeprecationWarning: <class 'pytest_mccabe.McCabeItem'> is not using a cooperative constructor and only takes {'fspath', 'parent', 'complexity'}.
See https://docs.pytest.org/en/stable/deprecations.html#constructors-of-custom-pytest-node-subclasses-should-take-kwargs for more details.
warnings.warn(
../.venv/lib/python3.11/site-packages/_pytest/nodes.py:686
/Users/slava/Space/Emporus/emporus-core-runtime/.venv/lib/python3.11/site-packages/_pytest/nodes.py:686: PytestRemovedIn8Warning: The (fspath: py.path.local) argument to McCabeItem is deprecated. Please use the (path: pathlib.Path) argument instead.
See https://docs.pytest.org/en/latest/deprecations.html#fspath-argument-for-node-constructors-replaced-with-pathlib-path
super().__init__(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================================================================================== 7 passed, 14 warnings in 0.03s ===========================================================================================
Pytest warns about:
fspath
to pathlib.Path
The latest working pytest version is 7.1.4 after which pytest integration fails with an internal error on any version higher.
I have the following code:
# noqa: C901
def complex_function():
if True:
if True:
pass
else:
pass
else:
if True:
pass
else:
pass
I disabled mccabe
for the function, so I should not be seeing any error. Instead, I get this:
$ flake8 --max-complexity 3 cyclo.py
cyclo.py:2:1: C901 'complex_function' is too complex (4)
How do I disable mccabe
for a single function?
$ flake8 --version
3.7.6 (mccabe: 0.6.1, pycodestyle: 2.5.0, pyflakes: 2.1.0) CPython 3.7.2 on Darwin
I am just about to package this peace of software for Macports and was inclined to name the port py-flake8-mccabe
instead of just py-mccabe
, account for the fact that this is a related plug-in.
But I think this naming issue is better addressed upstream.
Any thoughts?
It would be nice to keep PyPI releases and git tags in sync :)
Any complexity in an else block of a try statement is not taken into consideration.
According to this article cyclomatic complexity directly measures the number of linearly independent paths through a program's source code.
Now observe the following code:
def f():
for i in range(10):
print i
This code outputs a complexity of 2 with the program.
However, there is only one possible path of execution here.
The one existing test for mccabe is broken.
PR #6 fixes this.
From @sigmavirus24
Actually now that I think of a more complex example
def f():
try:
function_one(1)
function_two(arg)
except TypeA:
print(2)
except TypeB:
print(3)
else:
print(4)
finally:
if x:
print(5.1)
else:
print(5.2)
I see that actually you could branch to any of the except clauses from either of the statements in the try block. I still don't think that the except branches coming from the try block is correct so maybe something like
-----------------> except clause 1
/ / \
try -> function_one -> function_two ----> else
\ \ /
-----------------> except clause 2
The current behaviour of python -m mccabe
(no positional argument provided) is an IndexError
File "/home/junior/.local/lib/python3.10/site-packages/mccabe.py", line 327, in main
code = _read(args[0])
IndexError: list index out of range
Also, for python -m mccabe file1 file2
it just ignores file2
.
Would it be possible to add a check for a missing positional argument, and another for multiple ones?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.