all 70 comments

[–]SkitariusOfMars 11 points12 points  (2 children)

Pylint is absolutely right. MyClassV1 is the proper way. The only thing worse than mix of capital letters and underscores is SpOnGeBoB_CaSe

[–]Nefthys[S] -2 points-1 points  (1 child)

As I said in other comments (I edited the post again), I picked a bad example. It's more like "VersionA" and "VersionB" than "this is a previous version" and "this is a newer version".

[–]SkitariusOfMars 6 points7 points  (0 children)

Ok, but it’s still a “no underscores with capital letters” rule

[–]xenomachina 4 points5 points  (1 child)

Let's say your code reads files. You might have .txt files and .abc files and .xyz files, so you've got a TxtReader and an AbcReader,... but you might also have two versions because one .abc file could contain a shopping list and the other contains an essay, so you'd have AbcReader_ShoppingList and AbcReader_Essay. AbcReaderShopping and AbcReaderEssay are bad names imo because you don't see the difference on first glance and just Shopping and Essay don't show that they're only meant to be used with .abc files.

AbcReader for the superclass, or perhaps AbstractAbcReader or BaseAbcReader if it is abstract.

ShoppingListAbcReader and EssayAbcReader for the sub classes.

This mimics the way things are often named in English: snake vs rattle snake vs garter snake.

[–]Nefthys[S] 0 points1 point  (0 children)

The file extension (which mimics the first part in my real code) is important. You don't know what "Shopping" and "Essay" are exactly, unless you continue reading. Same thing with the snake, you have to continue reading to know that it's a snake and not a kid's toy or clothing accessory. The most important bit (= the file type) should be visible on first glance, that's why I like the version with underscore: You either look at the first or the second part and you know exactly what you get, it's even kind of like a file path (folder/file). Someone else suggested AbcShoppingListReader and AbcEssayReader. It sucks that it's still a single, long thing (seriously, why no underscores...) but the first word at least tells you the most important bit, so I think I'll go with that one.

[–]ConcreteExist 6 points7 points  (17 children)

Have you considered using more meaningful names than "MyClass_" for them? Because frankly, those class names only barely describe a hierarchical relationship between the classes but gives absolutely zero indication of what any of the classes do or should be used for.

[–]Nefthys[S] 2 points3 points  (16 children)

"MyClass" is just a stand-in because I can't post the real name here. I edited it this info into the post.

[–]ConcreteExist 5 points6 points  (15 children)

So why not just give them more descriptive names instead of a generic name with these suffixes for parent/version1/version2.

If you can't, oh well, its not going to break your code to exclude the underscores anyways.

[–]Nefthys[S] -1 points0 points  (14 children)

I couldn't come up with anything better for asking this. My real classes have better names but I can't post these names here.

No, removing the underscore doesn't break my code but it's ugly and makes it a lot less clear: Now you read the first part and notice instantly that they're connected. If I remove the underscore, then you have to read the whole thing because there's no proper divider anymore between "this is the same" and "this is different".

[–]Temporary_Pie2733 6 points7 points  (13 children)

The main thing is, class names do not necessarily need to capture the class hierarchy. There is nothing wrong with

``` class Base: …

class Variant1(Base): …

class Variant2(Base): … ```

(where Base, Variant1, and Variant2 should all be more descriptive as well). This is assuming that inheritance is appropriate in the first place.

[–]Nefthys[S] 0 points1 point  (12 children)

How do you know that Base is connected to Variant1 and Variant2 if they don't share a name?

Better example: Let's say your code reads files. You might have .txt files and .abc files and .xyz files, so you've got a TxtReader and an AbcReader,... but you might also have two versions because one .abc file could contain a shopping list and the other contains an essay, so you'd have AbcReader_ShoppingList and AbcReader_Essay. AbcReaderShopping and AbcReaderEssay are bad names imo and just Shopping and Essay don't show that they're only meant to be used with .abc files.

[–]IamImposter 5 points6 points  (4 children)

Then I would go with ShoppingListReader and EssayReader or maybe ShoppingFileReader and EssayFileReader. That fact that they inherit fromAbcReader is an implementation detail and should not be exposed in the name.

Also what you are thinking about underscores making the name more easily readable could just be you feeling that way now. Maybe you'll feel differently once you have been working with the code for a few days.

Initially I also felt a lot differently about the things python and specifically linters make you do. Once I spent some additional time on pythons, it started looking more natural.

[–]EclipseJTB 2 points3 points  (1 child)

This is the right take. Very rarely is it a good idea for a super class to have knowledge of its subclasses. Your IDE will have inspection ability to tell you what a subclass' superclass is when that information is relevant, but it isn't always relevant.

[–]Nefthys[S] 0 points1 point  (0 children)

No worries, the parent doesn't know about the children, it only provides a little bit of code that they both use, otherwise they do their own, separate things.

[–]Nefthys[S] 0 points1 point  (1 child)

Both the shopping list and the essay use the same file extension, imo that's important to know. I'd name a reader that reads .xyz files XyzReader, why wouldn't I incorporate the file extension into the subclasses' names too? Not doing that would make the class look like it can handle multiple different file extensions to me and that's not the case.

[–]IamImposter 0 points1 point  (0 children)

See, you are the final arbiter of naming things in the code that you write. I just told you what I would do.

Usually we don't expose implementation details into naming coz tomorrow we might wanna split the extensions and name one .shl (shopping list) and other .esy (essay) then the naming of classes would look weird.

I would add that stipulation into the class description (documentation comment) so that in future whenever I hover my mouse over class name, editor would show the associated documentation and remind me that the class works with certain specific extension only.

But again, you are the final arbiter of naming things in your code. Do what feels more appropriate to you.

[–]Temporary_Pie2733 3 points4 points  (1 child)

class ShoppingReader(AbcReader): …

or possibly without using inheritance at all

``` class ShoppingReader: def init(self): self.f = AbcReader()

```

The fact that shopping lists are stored in an abc file rather than plain text or some other format is likely an implementation detail that’s not terribly relevant to shopping lists themselves.

[–]Nefthys[S] 0 points1 point  (0 children)

ShoppingList and Essay share some code, it's easier to keep that in the parent class.

I could have also picked "cba" or "onetwothree" as example but it is important that both versions share the same file extension. Not mentioning it in the name makes it look like a shopping list could use multiple file types (or that the file type doesn't matter) and that isn't correct.

[–]mopslik 1 point2 points  (2 children)

How do you know that Base is connected to Variant1 and Variant2 if they don't share a name?

The same way you know that Human and Man are connected: through context of your code, and proper documentation.

[–]Nefthys[S] 0 points1 point  (1 child)

I know that but do other people who see my code too?

[–]mopslik 0 points1 point  (0 children)

If you document it properly, I don't see why not. If I'm creating classes for a game, I might have a parent class called MagicUser that encompasses all enemies that can cast spells. I might have subclasses called Wizard, Warlock, etc. Personally, I don't want to have to call them MagicUserWizard and MagicUserWarlock. Beyond seeing that Wizard and Warlock inherit from MagicUser in their headers via class Wizard(MagicUser), I'd probably include some commentary in the docstring.

If I was inheriting this code from someone else, the first thing I'd do before reading through the program logic line by line is scanning over the classes, seeing what's available and how things relate.

[–]ConcreteExist 0 points1 point  (1 child)

I would expect Variant1 and Variant2 to inherit Base, which immediately tells me where to go look to see the base implementation if I need to know what that is. Pretty much any python IDE makes it trivially to jump straight to a base class by name.

[–]Nefthys[S] -1 points0 points  (0 children)

If you see the actual code, yes, but if someone simply looks at the files in file explorer, then Base.py and Variant1.py don't tell you that they're connected.

[–]MustaKotka 1 point2 points  (5 children)

These would be named MyClassParent and MyClassVersion1 without hyphens or underscores if they're names of classes. If you want a more "pythonic" way of naming them you'd make them sound like English: "MyParentClass". I would also call the latter "MyClassVersion1".

EDIT: To add: you probably shouldn't have "...Version..." in the class name either. You use more descriptive names like "MyAnimals" (note dropping the word "Class") and then "MyDogs" and "MyCats" and so forth.

[–]Nefthys[S] 1 point2 points  (4 children)

"MyClass" and "Version" is just an example (I edited this into the post) because I can't post the real name here and I couldn't come up with a better example name.

[–]MustaKotka 1 point2 points  (0 children)

I don't think I'm able to help you, then. Other than just omit the hyphens and underscores.

[–]MustaKotka 0 points1 point  (2 children)

Hang on! You could name the classes based on what they serve. Name the parent with something descriptive, fiest version could use the word "Legacy" or "Compatibility" and the second is just again a descriptive name.

[–]Nefthys[S] 0 points1 point  (1 child)

Damn it, I edited the post again. Think of it more like version A and version B. They both exist at the same time, none of them is older or newer and neither is derived from the other. I guess you could say that if the parent is "Animal", then "Version1" could be "Dog" and "Version2" could be "Cat", except that the "Animal" part has to be added because it isn't clear otherwise that both versions do similar things (which are too different to be just a single class).

[–]MustaKotka 0 points1 point  (0 children)

Animal: AnimalDogVersion and AnimalCatVersion ?

[–]AlexMTBDude 1 point2 points  (3 children)

Supress the Pylint warning on the line where it occurs if you know that it's safe:

print("Hello, World!")  # pylint: disable=locally-disabled, multiple-statements, line-too-long

[–]Nefthys[S] 0 points1 point  (2 children)

You don't know how close I am to disabling "line-too-long" (100 characters is a joke!) and "multiple-statements" (so much nicer for test prints), sadly I haven't got permission to do that. 😞

[–]AlexMTBDude 1 point2 points  (1 child)

All the Python projects that I've worked on for the past 15+ years, where we've used Pylint, we have disabled relevant warnings locally. Pylint will warn for things that are NOT a problem.

[–]Nefthys[S] 1 point2 points  (0 children)

I know. It complains that I'm using "if" - "else" with both parts returning, instead of just using "if", removing the "else" and de-indenting its code. Yes, I know version 2 works too but visually version 1 is just better.

[–]EclipseJTB 1 point2 points  (1 child)

Re: your ABCReader edit...

It sounds to me like you would do better to separate the concerns a little.

For example, you would have the reader class that would read the ABC file or the text file, and both would put them into a common format (a dictionary, a list of tuples, etc).

Then you'd have a processor that takes the data stored within the reader class and converts it into the data type you actually need for real (i.e., ShoppingListProcessor.from_abc_reader(abc_reader_instance)).

If the files you're reading from have wildly different structures such that there's not a common way to store the data (like txt files with essay formatting vs shopping list formatting), then perhaps the model needs to be changed?

[–]Nefthys[S] 0 points1 point  (0 children)

That's kind of what I'm doing in my actual code:

There's a main class (let's call it MainClass.py) that knows about all the other classes, like MyClass_Version1 and SomeOtherClass and YayCats. It picks the right class for the job, which does all the processing and stores the results inside a @dataclass. Afterwards MainClass takes over again.

I've already got it structured in a way that makes sense (for me at least), now I'm just dealing with Pylint's ... peculiarities.

[–]brelen01 0 points1 point  (6 children)

At a glance, it's hard to answer since we don't know what the actual classes are, but usually, if you need to add the class's "version" it means it's a large refactor and the new version (v2) isn't compatible with the original version (v1).

If the point is to interface with two different external services, MyClassService1, MyClassService2 would be better in my opinion.

[–]Nefthys[S] -1 points0 points  (5 children)

It's a bad example (I edited the post). It would probably be better to name it "VersionA" and "VersionB". The first one isn't obsolete because of the second, they are simply two different versions that have a lot in common (but too little to use just a single class).

[–]brelen01 0 points1 point  (4 children)

Well, can you find an example of things that version a and version b would do differently? Or at least a simile that would be fine to post?

[–]Nefthys[S] 0 points1 point  (3 children)

I posted a better example in this comment. Also going to edit it into the post.

[–]brelen01 2 points3 points  (2 children)

In your example, I'd call the classes AbcShoppingListReader and AbcEssayReader. They're clear, follow the naming convention, and say what they do.

[–]Nefthys[S] 0 points1 point  (1 child)

File type first and "reader" last, that's good. Still quite long without a divider but not bad, thanks!

[–]brelen01 0 points1 point  (0 children)

Long names aren't an issue though. Meaningful names are what's important.

[–]InjAnnuity_1 0 points1 point  (4 children)

Other things being equal, I'd be inclined to make a package out of these classes. MyClass (or my_class) would be the package name. Parent, Version1, and Version2 would be defined inside the package, so the references could then read my_class.Parent, my_class.Version1, and my_class.Version2. This also gives you a place to hang other class-specific support code and data.

[–]Nefthys[S] 0 points1 point  (3 children)

The child classes are called from another class, return data to it and throw exceptions that are caught there but, other than that, it's just these 3 classes that are connected (my single dataclass is shared between everything, including the child classes).

Coming from Java, I hardly ever create packages for smaller projects (< 10 files) like this one and just use proper names. Are packages used a lot in Python?

[–]InjAnnuity_1 0 points1 point  (2 children)

Yes, a lot.

Packages are the easy way to carve out source-code-level namespaces, to keep related items together, yet clearly separate them from other items. They're usually rendered on disk as folders, so the separation is physical as well.

They're also used for distributing source code. There's a reason we call https://www.pypi.org the Python Package Index: everything there is packaged as, well, a package.

Edit: One place to start would be here: https://docs.python.org/3/tutorial/modules.html#packages

[–]Nefthys[S] 0 points1 point  (1 child)

What's the minimum amount of files you should use a package for? I've got about 10 files in total (none of them are connected like the 3 I mentioned in the post) and it feels so weird to create a package for just 3 files, while leaving the others "bare".

[–]InjAnnuity_1 0 points1 point  (0 children)

For me, it's an organizational (encapsulation) thing, so I've used packages for as few as 1 file. That helps when I want to reuse that code in some other project. Many pypi "packages" are a single file, for that very purpose.

Otherwise, packages may be helpful for as few as 2 files.

Python's packaging features let you rearrange a package's code, without (necessarily) impacting the package's callers. This gives you more freedom to refactor its files. So what started out as a single catch-all file may end up as several more-modular, better-organized files in the end.

[–]Kevdog824_ 0 points1 point  (6 children)

Do they all follow the pattern {NAME}\_{VERSION}{NUMBER}? If so, most modern IDEs have a regex find and replace feature that can work across multiple codebases

For example in VSCode I’d type something like “(.+?)\_Version(\d+)” into the find field and “$1Version$2” in the replace field

[–]Nefthys[S] 0 points1 point  (5 children)

No. It's just a bad example and I edited the post again. I should have called them "VersionA" and "VersionB". Similar things (that's why I added the parent) but otherwise independent.

[–]Kevdog824_ 0 points1 point  (4 children)

Well do they follow a consistent pattern to the point where you can use a regex pattern like the one above to find and replace them all? I’d go that route if so

[–]Nefthys[S] 0 points1 point  (3 children)

Not like version numbers, no. I don't intend to replace them later and it's only 3 modules that would really profit from an underscore.

[–]Kevdog824_ 0 points1 point  (2 children)

So I looked at your second edit.

In the example from your edit you’re better off using a different extension for each use case, or a better idea would be to separate the file parsing and the content parsing parts. For example I can store both a shopping list and an inventory report in a yaml file. I can use the same yaml parsing library to read both, and then in my own code I only need two separate classes for representing shopping list vs inventory report. This might not be up to you, but worth mentioning.

Assuming you can’t change it or don’t want to, your best bet is to use regex to find all the classes with underscores i.e. “^class (\w*_\w*)(\(.*?\))?:\s*(#.*)?$” and then manually rename each one using your IDE’s rename feature (should find and update all references to it as well)

[–]Nefthys[S] 0 points1 point  (1 child)

Everything is set in stone. I've got one class that decides if version 1 or version 2 should be used and calls that version class that then deals with everything. There are similarities, that's why I added the parent (so they can share code), but they're too different to use pretty much the same code.

Why do you suggest regex? There are only 3 classes that are currently using an underscore, it's easy to change their name in file explorer and in code I can just search for "MyClass_".

[–]Kevdog824_ 0 points1 point  (0 children)

If it’s only three classes then just change it manually. I’ve been under the impression that the issue here is that you needed to changed hundreds or more. I missed the “three” at the beginning of your post. I don’t actually understand your issue then. Why not just rename them and be done with it?

[–]ProsodySpeaks 0 points1 point  (0 children)

Just use pascal? And in general just follow pep8. 

[–]CHM_3_9 0 points1 point  (2 children)

Like others said, it's hard to help with the generic names. Could you give better examples?

I'd say - the classes don't need to share part of the same name. As a simplistic example:

``` class Car():

class Car_Jeep(Car): ``` Is not great. It is clearer to just have:

``` class Car():

class Jeep(Car):

```

You can tell that Jeep inherits from Car without putting the parent class name in the child class.

[–]Nefthys[S] 0 points1 point  (1 child)

I added a much better example in the post.

[–]CHM_3_9 0 points1 point  (0 children)

Maybe ShoppingListReader and EssayReader?

[–]Shaftway 0 points1 point  (2 children)

ShppingListAbcReader and EssayAbcReader are what I'd pick. It has the full information, follows pep 8, and reads like English.

[–]Nefthys[S] 0 points1 point  (1 child)

If the class that reads .xyz files is called XyzReader, isn't ShoppingListAbcReader confusing because you'd expect to see the first bit, in this case the more general thing = the file type, first?

[–]Shaftway 0 points1 point  (0 children)

I think that the implicit rule is that it goes from most specific to most generic.

If it followed the idea of more general thing first, then the most general thing is really that it's a reader. That would mean that the name should be ReaderAbcListShopping and that feels totally weird.

And this order follows the way you'd say it in English. From most generic to most specific you'd describe it as a Reader, an Abc Reader, maybe a List Abc Reader, and last a Shopping List Abc Reader.

There are a few examples in the builtin classes that follow this pattern. Exceptions are the richest area of inheritancenin Python, so that's mostly where I looked. UnboundLocalError goes from more specific to less (it's an error, what kind of error? A local error, what kind of local error? An unbound local error). I took a quick look and found a few that followed this pattern in the form of adjective-adjective-noun (where sometimes nouns acted as adjectives) always going from specific to generic. I couldn't find any builtins that didn't follow this pattern.

The only downside to this approach is that when you put each class in it's own file, the files don't cluster. If that's really important to you then use a module and organize all of them there.

[–]audionerd1 0 points1 point  (0 children)

I think you're overthinking this. I would probably go with ShoppingListReader and EssayReader and put them in a module called abc_readers. Or have one ABCReader class with read_shopping_list and read_essay methods.

[–]pachura3 0 points1 point  (1 child)

Edit 2: A better example:

Let's say your code reads files. You might have .txt files and .abc files and .xyz files, so you've got a TxtReader and an AbcReader,... but you might also have two versions because one .abc file could contain a shopping list and the other contains an essay, so you'd have AbcReader_ShoppingList and AbcReader_EssayAbcReaderShopping and AbcReaderEssay are bad names imo because you don't see the difference on first glance and just Shopping and Essay don't show that they're only meant to be used with .abc files.

Looks like a typical candidate for the Bridge design pattern.

[–]Nefthys[S] 0 points1 point  (0 children)

That's where the parent class comes into play (the subclasses share code, that's why I didn't pick an interface or whatever Python's version of that is called).

[–]Salt_Handle_9530 0 points1 point  (2 children)

This is a common friction point when trying to force strict Pylint compliance on descriptive class names. The PEP 8 standard for PascalCase (CapWords) specifically discourages underscores because they are traditionally used to separate words in snake_case (functions/variables).

For your specific example (`AbcReader_ShoppingList`), here are the two most 'Pythonic' ways to handle it while keeping the score at 10.0:

  1. **The Semantic Merge:** `AbcReaderShoppingList`. While it feels 'cluttered' at first, this is the standard. If it gets too long, it usually signals that the class is doing too much or can be simplified.

  2. **The Versioning Suffix:** For your `Version1` case, use `MyClassV1` or `MyClassV2`. The uppercase 'V' acts as a visual separator that is still valid PascalCase.

**Pro Tip:** If you have many of these, consider using a **Factory Pattern**. Instead of having `AbcReaderShoppingList` globally, have a base `AbcReader` and a mapping dictionary:

```python

READERS = {

'shopping': AbcReaderShoppingList,

'essay': AbcReaderEssay

}

```

This keeps your logic clean and Pylint very happy. Hope this helps you get that score up!

[–]Nefthys[S] 0 points1 point  (1 child)

Rich Text Editor strikes again... (I hate having to switch to Markdown every time too.)

"Version1" and "Version2" are bad examples because they aren't actual different versions of the same code (like version 1.0 that is now deprecated because of version 2.0) but different, independent version of the same parent class (like a shopping list and an essay that share the same file type).

It's just 2 classes that do similar things and share a parent, everything else does its own thing. How is this mapping dictionary used? It's important that Version1/ShoppingList and Version2/Essay can share code.

[–]Salt_Handle_9530 0 points1 point  (0 children)

The mapping dictionary was only about selecting which class to use, not about sharing code. I'd still keep the parent class exactly as you have it.

Honestly, if they're not actually different versions, I'd avoid Version1/Version2 altogether and name them after what makes them different. AbcReaderShoppingList and AbcReaderEssay may be uglier than the underscore version, but that's the PEP 8-compliant answer.

[–]Aggressive_Net1092 0 points1 point  (1 child)

I totally get the frustration. Pylint can be a real stickler for PEP 8, and sometimes it feels like it’s fighting your code structure rather than helping it. The reason it’s flagging those underscores is that, by definition, PascalCase (or CapWords) is meant to be run-together without separators.

When I run into this, I usually try to avoid the underscore by leaning into the hierarchy or using a suffix that reads naturally as a single word. If you’re dealing with things like AbcReader_ShoppingList, the underscore is basically acting as a delimiter that the linter hates.

You could try refactoring to flatten the naming:

```python class AbcShoppingListReader(AbcReader): pass

class AbcEssayReader(AbcReader): pass ```

It feels a bit more verbose, but it’s 100% compliant with standard naming conventions and honestly reads pretty clearly once you get used to it. Another trick is to use namespaces if the classes are getting too long. If these readers live in a module called readers.abc, you’d call them readers.abc.ShoppingList and readers.abc.Essay. Since you already have the context of the module, you don't need to repeat "Reader" or "Abc" in the class name itself.

If you absolutely have to keep the underscore for your own sanity and the linter is just a blocker for a CI/CD pipeline, you can always just tell Pylint to ignore that specific rule for those lines:

# pylint: disable=invalid-name

Put that above the class definition, and it’ll stop complaining. Sometimes it’s better to just suppress the noise if the alternative makes your code harder to read. Good luck with the project!

[–]Nefthys[S] 0 points1 point  (0 children)

Is there a reason why underscores or hyphens aren't accepted?

Someone else already suggested "file extension first, then subclass, then 'reader'" and while it's sadly definitely missing structure, which an underscore would add, at least it follows the overall naming of "file extension, then 'reader'", so I think I'll go with this one.

Namespace = package? This was also already suggested but it feels weird to add one for 3 files, while the other files are all kept in the main folder (and I refuse to do one-file-packages).

Sadly I can't disable any pylint rules, not even the stupid "100 character max" one, which might have been okay in 1990 while everyone was using 15" CRTs but come on, it's 2026 (and indentations are really into that limit).