Unifying Tool Directives in Python Comments

Dear Friends,

I recently noticed a recurring pattern in Python develop toolchains that seems worth discussing:

Python tooling has accumulated a growing number of tiny, tool-specific DSLs hidden inside comments:

# pylint: ...

# mypy: ...

# pyright: ...

# noqa

# ruff: ...

# fmt: ...

# isort: ...

and many others.

Even Python itself has special comment-based syntax, such as type comments and # type: ignore[...], which are treated specially by the parser and AST.

These constructs are no longer really ordinary comments: they carry structured, machine-readable semantics. Yet every tool has to invent its own mini grammar and parsing rules for essentially the same purpose.

I wonder whether we could approach this in a unified way:

  1. Use a clear, dedicated syntax marker for all tool directives, instead of pseudo comment. May be some unoccupied operator in python syntax like “!” (exclaimation operator) is helpful? for example, to declare lint rules:
!pylint.disable("no-members", "unused-variable")
some_c_module.ide_cannot_resolve_symbols(...)
  1. Instead of letting every tool invent its own tiny DSL, let the directive payload use normal Python expression syntax. Just like the example that I took above.

Type comments have already gone through a process of standardization, eventually becomes type annotations. I wonder if these different comment DSLs invented by various tools could someday go through a similar process and become something standardized.

8 Likes

You’ll have to come up with the actual semantics of the ! operator. Does it produce something that is attached to the code in some way, similar to how docstrings become __doc__ attributes?

If so, then you have the problem of how to do that in all the needed ways. How will you associated one of these markers with a statement, for example?

If you don’t attach anything to the code, then isn’t this just another comment syntax? What have we gained?

1 Like

Alternatively, specify that such special comments start with, say, #!. We already have that with the shebang line, although the shebang line must be the first line. If that’s a problem, then maybe #@, which kind of resembles the decorator syntax.

2 Likes

What is the advantage of insisting the comments all start with #!? The tools need to find the comments themselves anyway.

BTW, for people interested in handling these comment pragmas: coverage.py uses user-defineable regexes, which have proven even more powerful than expected: Coverage.py regex pragmas | Ned Batchelder

4 Likes

I think it should be handled like type_ignore in current python parser: to produce real AST nodes (ast.type_ignore). In this way, it can be post-proceeded by regular ast tools.

This fits nicely with my second suggestion: tool directives should use normal Python expression syntax. That way, the AST for the expression (ast.expr) can be embedded directly in the new AST node.

I’m not particularly attached to ! (or any specific marker). I only mentioned it because ! is currently mostly unused syntax space in Python. Any other clear and intuitive syntax would work just as well.

For a language-level solution, though, I think this eventually needs AST support. Regexes may be enough for an individual tool, but if this grows into a standardized syntax, they would become much harder to maintain.

I think it’s neat that they’re comments, not python code. I also think it’s neat that # type: ignore ends up in the AST.

I don’t see any reason why we couldn’t formalise that the pattern # {rule-catagory}: {instruction} should result in a specific AST node? That way almost all of these examples you’ve collected can be kept as-is. Only # noqa should be adjusted to # noqa: all or something in that direction, and # type: ignore[...] could be adjusted to # type-ignore: ..., but that wouldn’t be strictly necessary and therefore probably wouldn’t happen.

One complication is you’d also catch # TODO: ... and # TODO(peter, 2026-09-15): ..., but is that really a problem? The general pattern is that specific linters specify rule-catagory patterns they care about (for example, mypy cares about #type: ignore[...] and # mypy: ...), so linters that don’t care about TODO can specify that they don’t, and linters that want to can also have the freedom to specify that they do.

Let’s try to think about what kind of expressions there linter-only expressions there are.
All the ones I am familiar with belong to the categories:

  1. rule-catagory: ignore this rule for this line
  2. rule-catagory: begin ignoring this rule
  3. rule-catagory: stop ignoring this rule

Where one complication is that some “begin ignoring this rule” instructions are only allowed to appear at the start of a file and don’t have a corresponding “stop ignoring this rule” tag.

But there’s probably important other use-cases?


Another related question here is whether we could get (for example) mypy and ty to agree on type: ignore[...] catagories. :confused:

1 Like
  • Pylint also has a disable-next= directive affecting the following line which I find very useful. You don’t have to make a long line even longer. Often I wish other tools had such feature too.

  • And I’m not sure if all utilities have the same notion of “line”. There can be a logical line spanning multiple source code lines.

1 Like

I’ve long thought that we should have something “better” or at least more consistent for these pragmas as they’re commonly called. I just have never really been able to come up with any concrete proposal.

Some tools at least honor pragmas on the preceding line, but many don’t, which can lead to long lines for right-hanging comments. The other problem is combining pragmas from multiple tools (say a type checker and an auto-formatter).

3 Likes

Thank you very much for the reply ! — I really appreciate hearing this from a Steering Council member :grinning_face_with_smiling_eyes:

The two issues you mentioned are actually very close to what I had in mind. These issues are hard to coordinate when every tool invents its own little comment DSL. A unified and expressive syntax — and Python’s own expression syntax already gives us exactly that — seems like the most natural way forward.

1 Like

I don’t think comment is the final place that tool directives should go to. For example, the typing standard library has already introduced tool hints that are pure python code, like @no_type_check. It is a normal decorator.

1 Like

Yes, exactly. The biggest problem isn’t whether these tool directives can be neatly categorized, but that they all use different syntaxes, which creates a lot of context-switching overhead for developers and also makes IDE completion much harder.

Current patterns are typically

# name
# name: value

where name could probably be limited to valid python identifiers. value can be more complex. Maybe accept anything to the end of the line.
As a thought experiment, that could be enough for a very basic specification. Multiple pragmas for a line and multi-line pragmas could be considered lated.

There’s a major issue that both patterns can also occur as regular comments. We have no way to structurally identify current pragmas. This means

  • we only know whether a comment is a pragma if we know that a tool has defined it. E.g. # fmt: skip is only special because ruff has defined it. This means there cannot be a be syntax highlighting or tool-agnostic parser, which makes it a bad spec
  • or a spec has to be breaking with the status quo, for example through introducing special comments like #! name: vlaue

Both are IMHO at the border to prohibitively bad design choices.

1 Like

I’m not sure what problem we are really trying to solve, or whether we are approaching a solution.

I’m not sure it’s a burden for tools to parse comments. If we replace them with some kind of bang syntax, they will all have to decode the AST that results? That might be a little bit easier, but it will also be more restrictive (see below).

In terms of the actual syntax of these pragmas, so far we haven’t made them very uniform. Sure, they will all start with an exclamation point, but then what?:

!pylint.disable("no-members", "unused-variable")
!pychecker_suppress["no-members" | "unused variable"]
!ty.ignore(unused_variable=True)
!pyright.report(unused_variable=False)
!ruff.format(False)

I think it’s likely we’ll have standardized the shallow part of the syntax (start with bang, it has to be a Python expression), but then the variations continue from there, and have we really made anything uniform?

The mini grammars will still be different, and similar overlapping tools will still require different pragmas.

Comments have flexibility that will be hard to match. How many places will the Python syntax and AST to be updated to accommodate all of the possibilities? Today I can put a pragma on one parameter in a function signature:

def capture_warning(
    msg: str,
    slug: str | None = None,
    once: bool = False,  # pylint: disable=unused-argument
) -> None:

I haven’t done a complete census of tools’ comment syntax, there may be other associations being used. I know this topic started because comments are unregulated, but that freedom has also been a flexibility that tools have made use of.

This I think is a huge pain point for users that this proposal does nothing to address.

Couldn’t the syntax and parsing problem also be solved without changing the language at all by agreeing on a comment syntax and providing a third-party parsing library that all tools could use? It would be a big job: it would require a lot of discussion, compromise, agreement, consensus, and adoption. But changing the language will require all of that as well.

A third-party library would have the advantage of working on all current versions of Python. Changing the language means none of this would be available until at least 3.16. So all tools would continue to support their comment syntax(es) as well as the new bang-syntax, adding to the diversity of pragmas, and further confusing users.

5 Likes

I don’t remember which checker exactly, but they devised a way to do the following:

# type: ignore[mypy:<mypy error>, pyright: <pyright error>]

EDIT: either It was implemented or it was proposed, I can’t recall exactly.

I think that this (while it might become pretty long) becomes quite flexible.

To reduce the length of the comment, maybe each checker can map each error to a numeric number (similar to black, isort, ruff rules)

So for example:

# type: ignore[mypy:E001, ty:TY001, pyright: E003]

Each type checker can map (if they so wish to do so) existing error codes to numeric values (unless it’s already like that), they have a way of parsing wether it’s for their type checker or not and act on that.

A blanket # type: ignore stays as it is: whoever you are, ignore this type error

1 Like

I agree that parsing comment pragmas may not be a hard work for tools; but, for developers who use these tools, it is quite a heavy burden to keep in mind the special pragmas rules, which grows when more tools are involved.

There’s a very similar example here: pytest, pylint, tox, and flake8 all used to have their own rc files (pytest.ini, .pylintrc, tox.ini, .etc), each with a different format (I experienced, the expression of list vary quite a lot). Now they can all be configured through pyproject.toml, which has made things much easier for developers. It is the power of standardization.

This doesn’t conflict with standardization. A standardized syntax could cover most use cases, and for the few special cases, str literal can always serve as an escape hatch.

True, but why must the standardization require changing the syntax and AST of the language, maintenance by the core team, waiting for 3.16, and living with two styles for the next decade or two?

What do you think of the idea of standardizing comment syntax and providing a common parsing implementation that can be maintained by a third party and adopted immediately?

1 Like

Standardization from official can act as kind of push for third-party tools to evolve toward a unified way. After all, third-party tools have been evolving continuously. Mypy, ruff and other tools have been changing their pragmas these years (for example, ruff preview introduced support for human-readable linter rule names in addition to rule codes).

As for what I mentioned earlier about parsing pragmas to AST, one benefit for third-party tools is that they could take advantage of existing AST infrastructure, especially in LSPs, making integration with editors and IDEs much smoother.

In my experience, this is backwards. Standards require buy-in from the people expected to adopt them, and this generally means that those people need to agree there’s a problem, commit to finding a mutually agreeable solution and then adopt it. Even agreements in principle to support a new standard can fail if the benefit is too small.

Once you get the buy-in, a purpose-built library is not a serious barrier, and the 5-year wait time for a language feature will be.

1 Like

In fact, once you get the buy-in, we don’t even need a common library. So far I haven’t heard a tool-maker here say that parsing the comments is a problem for them. If we agree on a standard for pragma comments, and tool makers adopt the standard, they can parse them however they like, and the users’ problems will be solved.

1 Like