Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: why doesn't ruff complain about CapsWords (PascalCase)? #15069

Open
tribunsky-kir opened this issue Dec 19, 2024 · 7 comments
Open
Labels
needs-decision Awaiting a decision from a maintainer rule Implementing or modifying a lint rule

Comments

@tribunsky-kir
Copy link

tribunsky-kir commented Dec 19, 2024

PEP 8 describes that:

Class names should normally use the CapWords convention.

and that for Method Names and Instance Variables

Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.


Short example.py:

class MyClass:
    WhatIf: str
    WhySo: str
ruff check example.py --select ALL --ignore D
All checks passed!
ruff --version
ruff 0.8.3
@tribunsky-kir tribunsky-kir changed the title Question: why does ruff don't complain on CapsWords (PascalCase)? Question: why doesn't ruff complain about CapsWords (PascalCase)? Dec 19, 2024
@Avasam
Copy link

Avasam commented Dec 19, 2024

Read-as-written, I believe attributes defined at the class-level are not instance variables, but rather class variables (unless you're using a dataclass)

class MyClass:
    class_var = "bar"
    def __init__(self):
        self.instance_var = "foo"

That being said, I have no idea if PEP-8 intended to cover class variables here, or type-annotations-only declaractions.

@tmke8
Copy link
Contributor

tmke8 commented Dec 19, 2024

There is N815 which says:

Bad:

class MyClass:
    myVariable = "hello"
    another_variable = "world"

Good:

class MyClass:
    my_variable = "hello"
    another_variable = "world"

At first glance, it's surprising that it doesn't exclude PascalCase, but maybe there is a reason?

@tribunsky-kir
Copy link
Author

Read-as-written, I believe attributes defined at the class-level are not instance variables, but rather class variables (unless you're using a dataclass)

class MyClass:
class_var = "bar"
def init(self):
self.instance_var = "foo"

That being said, I have no idea if PEP-8 intended to cover class variables here, or type-annotations-only declaractions.

Technically, I can't disagree, that it is class attribute (and I am not sure about explicit PEP-8 opinion on that).

But N815 would complain on mixedCase in the absolutely same case, as mentioned above. Such behavior looks inconsistent and surprising to me.

For popular case like pydantic class attributes are more like instance variables.

@MichaReiser
Copy link
Member

MichaReiser commented Dec 20, 2024

These are just my own notes from looking into this issue:

The difference here doesn't come from whether the variable is attributed. It correctly flags myVariable but it doesn't flag MyVariable. See playground

This sounds very familiar to #14905

Ruff's behavior is consistent with pep8-namiang

class MyClass:
    MyVariable: int
    myVariable: int
❯ uv tool run --with pep8-naming flake8 test.py
test.py:3:6: N815 variable 'myVariable' in class scope should not be mixedCase

@MichaReiser
Copy link
Member

I did search the pep8-naming repository for an explanation but couldn't find any.

My best guess is that CapNames are allowed because flagging them would be in conflict with the PEP8 recommendation for type variables.

@MichaReiser MichaReiser added the question Asking for support or clarification label Dec 20, 2024
@Daverball
Copy link
Contributor

Daverball commented Dec 20, 2024

@MichaReiser Another code pattern that could explain this are nested classes, where the nested class is swapped out using an assignment to a different class by a subclass. I've seen this pattern used in a few places over the years.

E.g. webob.request.Request.ResponseClass or wtforms.Form.Config come to mind, although in the latter case you're generally expected to always define a new nested class, rather than assign one. Although you can think of those as type variables too, since they're referring to a type.

I think we could come up with some heuristics for the given example with AnnAssign though. If the annotation doesn't refer to a type-like, then using PascalCase is wrong. If it's a regular Assignment, then we don't know, without performing type inference.

@tmke8
Copy link
Contributor

tmke8 commented Dec 20, 2024

I also sometimes use this pattern:

from enum import Enum, auto
from typing import TypeAlias

class LoggerOption(Enum):
    INFO = auto()
    WARN = auto()

class Logger:
    Option: TypeAlias = LoggerOption

    def __init__(self, option: Option):  # using the alias
        self.option = option

In another file:

from logger import Logger  # only Logger needs to be imported

logger = Logger(Logger.Option.INFO)

@MichaReiser MichaReiser marked this as a duplicate of #14905 Dec 23, 2024
@MichaReiser MichaReiser added rule Implementing or modifying a lint rule needs-decision Awaiting a decision from a maintainer and removed question Asking for support or clarification labels Dec 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-decision Awaiting a decision from a maintainer rule Implementing or modifying a lint rule
Projects
None yet
Development

No branches or pull requests

5 participants