CQ010: NoMagicNumbers

Overview

Property Value
ID CQ010
Name NoMagicNumbers
Group code-quality
Severity NOTE

Description

Flags unexplained numeric constants (magic numbers) in code that should be replaced with named constants.

Magic numbers are numeric literals that:

  • Lack context: The meaning of 86400 is unclear without context
  • Reduce maintainability: Changes require finding and updating every occurrence
  • Obscure intent: Named constants like SECONDS_PER_DAY = 86400 are self-documenting
  • Create subtle bugs: Typos in repeated magic numbers are hard to spot

This check uses AST parsing to find numeric literals that should be named constants.

What it checks

The check scans all Python files in the package (excluding test files, hidden directories, and __pycache__), identifies numeric constants, and reports those that aren’t in the allowed list:

  • PASSED: No unexplained magic numbers found
  • FAILED: Magic numbers found in code
  • NOT_APPLICABLE: Package directory not found

Default allowed numbers

The following values are allowed by default:

  • Common values: 0, 1, -1, 2
  • Powers of 2: 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096
  • Common bases: 10, 100, 1000

Ignored contexts

Magic numbers are NOT flagged when used in:

  • Type hints and annotations
  • Default function arguments
  • List/array indexes
# These are flagged as magic numbers
timeout = 3600  # What does 3600 mean?
buffer_size = 65536  # Magic number

# These are NOT flagged (in allowed list or ignored context)
count = 0  # 0 is allowed
items = data[5]  # Index context is ignored
def process(retries: int = 3):  # Default arg is ignored
    pass

How to fix

Replace magic numbers with named constants

# Before
def calculate_delay(attempts):
    return attempts * 1.5 * 3600

# After
BACKOFF_MULTIPLIER = 1.5
SECONDS_PER_HOUR = 3600

def calculate_delay(attempts):
    return attempts * BACKOFF_MULTIPLIER * SECONDS_PER_HOUR

Use module-level constants

# Before
def validate_input(text):
    if len(text) > 255:
        raise ValueError("Too long")
    if len(text) < 3:
        raise ValueError("Too short")

# After
MAX_INPUT_LENGTH = 255
MIN_INPUT_LENGTH = 3

def validate_input(text):
    if len(text) > MAX_INPUT_LENGTH:
        raise ValueError("Too long")
    if len(text) < MIN_INPUT_LENGTH:
        raise ValueError("Too short")

Configuration

Add project-specific allowed numbers

[tool.pycmdcheck.checks.CQ010]
allowed_numbers = [42, 60, 3600, 86400]  # Add to defaults

Skip this check

[tool.pycmdcheck]
skip = ["CQ010"]

CLI

pycmdcheck --skip CQ010

References