Classes aren't outright bad, but I've seen them misused so many times that I knee-jerk cringe when I see the keyword `class` in any programming language; too often have I seen classes misused and misapplied (in my opinion).
If what you're writing is fundamentally a singular self-described action, there's almost certainly no reason to apply thinginess to it. But I see this practice all the time in open-source code, and it describes Java to a tee.
Is the mutation of your data best described as a behavior? If not, then a simpler data structure and functions that act upon that structure may be easier to understand and be more composable in the long run.
And almost never use inheritance, especially if that inheritance chain goes beyond one ancestor.
I almost never use classes these days. If I were writing a game, I imagine that classes would be conceptually helpful in making sense of ideas that manifest as things with behavior in a virtual world. For general programming tasks, I find them largely unhelpful and to be a potential trap.
EDIT: When I said I "almost never" use classes, of course I do work with classes at my job because it would be obnoxious of me to deviate from the collective pattern. With my own personal code, the only time I end up using classes is if I'm forced to (ex. web components).
sgt · 16h ago
Django's use of inheritance (with mixins) makes sense to me.
Class based views and that sort of thing is vastly simplified as opposed to building all those views from scratch. One has to spend some time learning what they do though.
Sohcahtoa82 · 1d ago
> And almost never use inheritance, especially if that inheritance chain goes beyond one ancestor.
With Python's duck typing, I'm not even sure of a case where inheritance is even strictly necessary beyond custom exceptions.
librasteve · 15h ago
with Raku’s powerful, yet simple, OO you get role composition for code organization and can reserve inheritance to reflect real structure in your data model
moomoo11 · 1d ago
I use TypeScript a lot recently, and I've been using class with static methods. No internal states.
It is more of an organizing decision, and I wanted to avoid DI and additional configuration up front. KISS.
librasteve · 15h ago
in Raku this is called role composition :-)
dkarl · 1d ago
Since this seems to be a guide for programmers new to Python, I wish it made it clear that dataclasses are classes; @dataclass just provides a concise way of defining classes with default functionality for instance construction, equality, hash, and string representation.
Otherwise, this is a decent set of reminders for programmers coming from more class-oriented languages.
smartscience · 1d ago
Good point. I wasn't sure from the given examples that overuse of classes in this way was all that harmful, e.g. the performance penalty hopefully shouldn't be all that significant. But then I remembered all the Python code I've seen that was obviously written by capable and experienced C++ programmers.
hidelooktropic · 23h ago
True. Same could be said of namedtuples.
Calavar · 1d ago
You can use classes withput doing full blown OO. Classes are not only for encapsulating data with behavior or code reuse through inheritance hierachies. Sure, you can use them for that, but you can also use them for code as documentation.
IMHO using adhoc data structures like dicts and named tuples is schemaless programming - the equivalent of choosing MongoDB over a SQL database, but without any of the performance arguments. It's fine for small one off scripts, but as your software's complexity grows the implicit (and often entirely undocumented) schema turns into tech debt.
cjs_ac · 1d ago
> It's fine for small one off scripts, but as your software's complexity grows the implicit (and often entirely undocumented) schema turns into tech debt.
It's a great way to start off a project, when you're still filling in the details of how everything will fit together. Once you've worked out what the schema is, then you can solidify it into classes.
gkze · 21h ago
This is kind of a poor argument. Its examples are unrealistic, so it’s hard to take the author seriously. It doesn’t delve deep into things like the risks of a strict inheritance hierarchy vs. the benefits of functional composition, or more interesting things like the subtleties of using module-scoped state versus function or class-scoped state. The list goes on.
Instead of a slightly dogmatic and mostly unsubstantiated stance, I find less opinionated guidance on what to do when much more helpful. After all the language has all of these constructs for a reason.
While I may see a strong argument for a lighter-weight programming model, it is not laid out in this article.
Yeah, was going to post this - great talk that I've recommended to incoming devs on our team.
jollyllama · 1d ago
> Managing State with Simple Structures: Use Dictionaries or Lists
This is bad advice because you wind up hardcoding (or using vars for your key names, slightly better) your behavior on the state of the dict. Seriously, why not just make a class? You get better IDE and possibly better LLM support.
fluidcruft · 21h ago
It's trivial to subclass dict and list anyway.
rcarmo · 15h ago
One of the things that annoys me about a lot of “modern” Python code is that is reads like Java cosplay: entire classes that are nothing but properties, “helpers” for iteration that make a shambles of ordinary for loops, etc. I have always preferred clean modules and a functional style with well defined signatures, and find it more maintainable to boot.
rosmax_1337 · 1d ago
I fail to see any argument why the namedtuple beats the usage of a class here. A class in a development view of the problem, is also a structure to build and expand upon, a tuple is a promise this won't be expanded upon. Clearly case dependent which is better.
SamuelAdams · 1d ago
It also makes the code easier to read, and that is what our org optimizes for, all else being equal. Code is read 10x more than it is modified, so if you can make it a little quicker to understand it’s generally recommended.
sgt · 16h ago
But then why not use dataclasses and get constructor, equality etc included?
JohnKemeny · 1d ago
namedtuple is not a replacement for a class, but for a tuple. It is a tuple with named accessors.
You can also argue that you don't need a Pair type since you can simple make a class for the two things you want to contain.
specproc · 23h ago
There just comes a point where they make sense. The article uses some very minimal examples, but the point at which I find myself reaching for classes is the point at which it's gotten complex enough to need them.
I find them useful when my code has become a big, sprawling mess of functions throwing dicts around, and I'm losing track of what the structure should be.
They're useful when they make sense. I can't think of many projects I've started off with them, but they've helped me straighten a few out.
Neither love them nor hate them, and struggle to see why folks get animated about them. They're a useful tool, sometimes.
ozkatz · 1d ago
I agree grouping behaviors (functions) doesn't require classes, but since Python does not have structs, classes are the only way to provide types (or at least type hints) for non-scalar data. Dicts can only have a key type and value types, while lists/tuples can only carry one type.
class Person:
age: int
name: str
Would have to be boiled down to `dict[str, Any]` (or worse, `tuple[Any]`). You could use `typing.NamedTuple` without defining a class (`NamedTuple('Person', [('name', str), ('age', int)])`) - but subjectively, this is much less readable than simply using a data class.
Arch-TK · 1d ago
The typing argument no longer holds. There are typed dicts where you specify field names and the respective key types. But in most cases you're still best off with just a dataclass. Bare classes shouldn't be the first thing you reach for when you want structure.
ozkatz · 1d ago
I agree dataclasses are a better alternative when you want a class that represents data :)
> There are typed dicts where you specify field names and the respective key types
The only way to achieve this that I'm aware of is PEP 589: subclassing TypedDict. Which I believe negates the argument in the post.
Arch-TK · 23h ago
I don't think using TypedDict to create a type hint is against the spirit of the post.
Although reading a lot of what has been written here, it seems people aren't really getting the spirit of the post in the first place.
the__alchemist · 20h ago
Dataclasses are effectively structs.
stared · 1d ago
Sure, some people use classes when all they need is a namespace - and for that having a file is enough.
If there is a collection of related data, it is (often) good to have it as a class. In the config example, a class may work better - as then you can pass it as an argument somewhere.
Pydantic is everything I want Python dataclasses to be.
While Pydantic `BaseModel`s, like dataclasses, are still "classes", I consider that an implementation detail of how records are implemented in Python - thus not really contradicting the article's recommendation against using a class.
breatheoften · 1d ago
classes are a bad way to model data imo
I wish python had a clean way to define types without defining classes. Think a _good_ mechanism to define the shape of data contained within primitives/builtins containers without classes -- ala json/typescript (ideally extended with ndarray and a sane way to represent time)
Python classes wrapped around the actual "data" are sometimes necessary but generally always bad boilerplate in my experience.
simonw · 1d ago
> I wish python had a clean way to define types without defining classes
dataclasses are pretty much that. They use the class mechanism but they're culturally accepted now as a clean way to build the equivalent of structs.
isjustintime · 21h ago
Yes, dataclasses are great. I often use them as a pretty clear and adopted way to handle serialization and deserialization, configs, etc.
Not the one you’re replying to, but my take: The main problem with classes is that they conflate a lot of concepts that should be separate and you usually need only one or a few at once, but are 5orced into all of them. Namely:
• Product type
• Reference semantics
• Namespacing
• Pipeline-style call syntax
• Redefinition of infix operators
• Dynamic dispatch
• Subtyping
For example, you cannot have a product type with value semantics or a type that redefines infix operators but is not a product type.
anttiharju · 1d ago
pydantic?
quietbritishjim · 1d ago
Wow, that is an impressively uninsightful article.
Apart from the first option, all of those are really obviously not classes.
The first option is a suggestion to use named tuples or dataclasses. That is sensible, but you are using a class in that case – those are just helpers for making them. You can even add your own methods to dataclasses.
For named tuples, you are better off using the typed version typing.NamedTuple [1] instead of the classical one suggested in the article. Aside from providing typing, the typed version has a much nicer syntax to define it and lets you add methods (like dataclasses but unlike classical named tuples)
It's great if you know more than others and have time to share some of what you know in the comments. But the putdown aspect is unnecessary and can really hit people hard sometimes.
quietbritishjim · 3h ago
Fair point. Sorry.
In my defence, vacuous articles are sometimes posted here and sometimes (rarely) reach the front page. I can see from other comments that this post doesn't count. I like to I think I got mixed up, not because I've forgotten what it's like to be a beginner, but because I started with procedural languages before purely class-based ones like Java.
hidelooktropic · 23h ago
Thanks, @dang
kubb · 1d ago
Chill, people are learning and not everyone has the luxury of reading the manual or going to school or using Python at work or whatever.
quietbritishjim · 1d ago
Are people really making classes with a single static method and nothing else? It just feels like filler material.
Sohcahtoa82 · 1d ago
Yes. They probably learned Java first.
callc · 1d ago
+1. I learned Java first, in school and on my own. I needed to unlearn the OOP-first mindset.
AstroBen · 23h ago
This has nothing to do with OOP. A class with a single static method isn't OOP
You can use the 'class' keyword and write your program functionally, or procedurally
Jtsummers · 1d ago
So you actually thought you needed, in Python, classes with static methods instead of just plain old modules? What was your first Python "hello world" like?
pydry · 1d ago
There is harmful advice in this article. "Just chill and use a dict or a namedtuple" leads inexorably to buggy code.
The advice to use dataclasses is good but... dataclasses are, um, classes.
hidelooktropic · 23h ago
I thought readers would understand that an alternative to a class just meant you don't have to create one from scratch. Maybe not.
ackfoobar · 1d ago
> dataclasses are, um, classes
So is the case when you use `namedtuple`, which creates a new class. This is not an interesting gotcha.
Classes (the Python language construct) are how you implement records (the language-neutral concept) in Python.
It's ironic that the "There Is Only One Way to Do It" language has multiple bad ways to implement records though.
piker · 1d ago
> For named tuples, you are better off using the typed version typing.NamedTuple [1] instead of the classical one suggested in the article. Aside from providing typing, the typed version has a much nicer syntax to define it and lets you add methods (like dataclasses but unlike classical named tuples)
It's been a while since I worked in python, but aren't the original namedtuples populated with __slots__ instead of a __dict__ which makes them a much better choice for very very large datasets? Albeit at the cost of duck typing.
ok_computer · 22h ago
As an aside, my preference for a class container is dataclass(slots=True) for typing. I’d normally used dataclasses as np array containers vs serialized tuple row objects.
auscompgeek · 1d ago
typing.NamedTuple also sets `__slots__ = ()`, just like collections.namedtuple.
lyu07282 · 1d ago
@dataclass(slots=True) works too
the__alchemist · 1d ago
Data classes and enums are the move, IMO, in python, to address the considerations of the article. Enums in particular have room for improvement, but I think they're good enough. (To use in place of common python patterns like strings for choices, or sets of booleans)
danofsteel32 · 1d ago
This. You can get very far with just dataclasses, enums, and functions. I use dataclasses like I would use structs in C or go.
clickety_clack · 1d ago
There’s also pyrsistent for immutable data structures with some nice methods for working with them.
d_burfoot · 1d ago
For people who think classes are terrible, can you explain how you would implement the functionality associated with a Pandas DataFrame, without using classes? Did the Pandas developers simply get it wrong when choosing how to build this?
I don't think there are many people that say classes are bad outright. After all, you can write Go code that look identical to code in another language with classes. It's more about misusing OOP and unnecessary amount of abstraction people complain about.
Also, patterns in data science often don't transfer to general programming. Very few data scientists annotate their python types, let alone writing unit tests. Source control of Jupyter notebooks is often non-existent. It's a different way of doing things, and I would exclude numpy/pandas from discussions unless necessary.
hidelooktropic · 23h ago
To be clear, "You might not need" does not mean "You shouldn't use"
echelon_musk · 1d ago
I thought this was going to about how one won't need to take a class to learn Python in the inevitable AI future.
bee_rider · 1d ago
Similar… Python does look a lot like runable pseudocode. If you already know any type of programming, you might not need a Python class. Haha.
hooverd · 1d ago
Python is deceptively simple looking.
bee_rider · 21h ago
It can be just straightforwardly simple! Of course, you can also write some pretty confusing one-liners.
ac130kz · 18h ago
Apart from dataclasses (which generally should have slots and/or be frozen), this article gives way too much inapplicable advice, which is not scalable even in a small sized project.
tcdent · 1d ago
Rule of thumb:
If you find yourself implementing a singleton in Python, you probably wanted a module.
chaz6 · 9h ago
Who needs a singleton when there is @lru_cache(maxsize=1) /s
odyssey7 · 1d ago
Python is neither object-oriented nor functional. It’s pythonic. It was popularized by academia because it’s good for beginner programming assignments, and unfortunately the legacy of object-oriented Python in academia forced ML to inherit it.
nyrikki · 1d ago
Python is a good glue language, which helps with cleaning and interfacing with other languages that are more effective in specific contexts.
The fact that it isn't dogmatic in it's opinions is a feature for the use case.
It isn't great at many things but it can do most things well enough.
It's popularity as a teaching language lagged way behind it's adoption for real world needs BTW.
odyssey7 · 18h ago
Python is extremely dogmatic in its opinions. Both its objects and its lambdas were intentionally made too difficult to enjoy. There is a depth limit to recursion that is artificially imposed so that programmers will not use recursion. The interface for map is inconvenient. The language’s dogma is pythonic, which was at one time egalitarian, but is now just inconvenient. It is a language that discourages programming.
samrus · 1d ago
The examples provided here cant be real, right? Like look at the MathUtils one. No one is so braindead that they would add 2 redundant lines on top of their function def right?
xigoi · 40m ago
> No one is so braindead that they would add 2 redundant lines on top of their function def right?
Apparently Grant Sanderson (3Blue1Brown), the creator of Manim, is braindead:
class ContinuousMotion(Scene):
def construct(self):
func = lambda pos: np.sin(pos[0] / 2) * UR + np.cos(pos[1] / 2) * LEFT
stream_lines = StreamLines(func, stroke_width=2, max_anchors_per_line=30)
self.add(stream_lines)
stream_lines.start_animation(warm_up=False, flow_speed=1.5)
self.wait(stream_lines.virtual_time / stream_lines.flow_speed)
hidelooktropic · 23h ago
Correct, the examples here are not real. They are examples.
nodesocket · 1d ago
I’m relative newbie to Python (come from Javascript) but I’ve been building a Flask web app for the last couple of months. Almost my entire Flask codebase is just packages with functions. There are only a handful of classes and that’s because I am overriding some Exception classes with custom behavior.
If what you're writing is fundamentally a singular self-described action, there's almost certainly no reason to apply thinginess to it. But I see this practice all the time in open-source code, and it describes Java to a tee.
Is the mutation of your data best described as a behavior? If not, then a simpler data structure and functions that act upon that structure may be easier to understand and be more composable in the long run.
And almost never use inheritance, especially if that inheritance chain goes beyond one ancestor.
I almost never use classes these days. If I were writing a game, I imagine that classes would be conceptually helpful in making sense of ideas that manifest as things with behavior in a virtual world. For general programming tasks, I find them largely unhelpful and to be a potential trap.
EDIT: When I said I "almost never" use classes, of course I do work with classes at my job because it would be obnoxious of me to deviate from the collective pattern. With my own personal code, the only time I end up using classes is if I'm forced to (ex. web components).
Class based views and that sort of thing is vastly simplified as opposed to building all those views from scratch. One has to spend some time learning what they do though.
With Python's duck typing, I'm not even sure of a case where inheritance is even strictly necessary beyond custom exceptions.
It is more of an organizing decision, and I wanted to avoid DI and additional configuration up front. KISS.
Otherwise, this is a decent set of reminders for programmers coming from more class-oriented languages.
IMHO using adhoc data structures like dicts and named tuples is schemaless programming - the equivalent of choosing MongoDB over a SQL database, but without any of the performance arguments. It's fine for small one off scripts, but as your software's complexity grows the implicit (and often entirely undocumented) schema turns into tech debt.
It's a great way to start off a project, when you're still filling in the details of how everything will fit together. Once you've worked out what the schema is, then you can solidify it into classes.
Instead of a slightly dogmatic and mostly unsubstantiated stance, I find less opinionated guidance on what to do when much more helpful. After all the language has all of these constructs for a reason.
While I may see a strong argument for a lighter-weight programming model, it is not laid out in this article.
[1] - https://www.youtube.com/watch?v=o9pEzgHorH0
"Object-Oriented Programming is Bad"
https://www.youtube.com/watch?v=QM1iUe6IofM
Followed by "Object-Oriented Programming is Embarrassing: 4 Short Examples".
https://www.youtube.com/watch?v=IRTfhkiAqPw
This is bad advice because you wind up hardcoding (or using vars for your key names, slightly better) your behavior on the state of the dict. Seriously, why not just make a class? You get better IDE and possibly better LLM support.
You can also argue that you don't need a Pair type since you can simple make a class for the two things you want to contain.
I find them useful when my code has become a big, sprawling mess of functions throwing dicts around, and I'm losing track of what the structure should be.
They're useful when they make sense. I can't think of many projects I've started off with them, but they've helped me straighten a few out.
Neither love them nor hate them, and struggle to see why folks get animated about them. They're a useful tool, sometimes.
> There are typed dicts where you specify field names and the respective key types
The only way to achieve this that I'm aware of is PEP 589: subclassing TypedDict. Which I believe negates the argument in the post.
Although reading a lot of what has been written here, it seems people aren't really getting the spirit of the post in the first place.
If there is a collection of related data, it is (often) good to have it as a class. In the config example, a class may work better - as then you can pass it as an argument somewhere.
At the same time, in many cases, instead of a dataclass, it is worth considering Pydantic (https://docs.pydantic.dev/latest/concepts/dataclasses/). It can deal with validation when data comes from external sources.
While Pydantic `BaseModel`s, like dataclasses, are still "classes", I consider that an implementation detail of how records are implemented in Python - thus not really contradicting the article's recommendation against using a class.
I wish python had a clean way to define types without defining classes. Think a _good_ mechanism to define the shape of data contained within primitives/builtins containers without classes -- ala json/typescript (ideally extended with ndarray and a sane way to represent time)
Python classes wrapped around the actual "data" are sometimes necessary but generally always bad boilerplate in my experience.
dataclasses are pretty much that. They use the class mechanism but they're culturally accepted now as a clean way to build the equivalent of structs.
And e.g. you can add dataclasses-json for JSON serializability: https://pypi.org/project/dataclasses-json/
Genuinely curious: why?
• Product type
• Reference semantics
• Namespacing
• Pipeline-style call syntax
• Redefinition of infix operators
• Dynamic dispatch
• Subtyping
For example, you cannot have a product type with value semantics or a type that redefines infix operators but is not a product type.
Apart from the first option, all of those are really obviously not classes.
The first option is a suggestion to use named tuples or dataclasses. That is sensible, but you are using a class in that case – those are just helpers for making them. You can even add your own methods to dataclasses.
For named tuples, you are better off using the typed version typing.NamedTuple [1] instead of the classical one suggested in the article. Aside from providing typing, the typed version has a much nicer syntax to define it and lets you add methods (like dataclasses but unlike classical named tuples)
[1] https://docs.python.org/3/library/typing.html#typing.NamedTu...
It's great if you know more than others and have time to share some of what you know in the comments. But the putdown aspect is unnecessary and can really hit people hard sometimes.
In my defence, vacuous articles are sometimes posted here and sometimes (rarely) reach the front page. I can see from other comments that this post doesn't count. I like to I think I got mixed up, not because I've forgotten what it's like to be a beginner, but because I started with procedural languages before purely class-based ones like Java.
You can use the 'class' keyword and write your program functionally, or procedurally
The advice to use dataclasses is good but... dataclasses are, um, classes.
So is the case when you use `namedtuple`, which creates a new class. This is not an interesting gotcha.
Classes (the Python language construct) are how you implement records (the language-neutral concept) in Python.
It's ironic that the "There Is Only One Way to Do It" language has multiple bad ways to implement records though.
It's been a while since I worked in python, but aren't the original namedtuples populated with __slots__ instead of a __dict__ which makes them a much better choice for very very large datasets? Albeit at the cost of duck typing.
https://pandas.pydata.org/docs/reference/api/pandas.DataFram...
Also, patterns in data science often don't transfer to general programming. Very few data scientists annotate their python types, let alone writing unit tests. Source control of Jupyter notebooks is often non-existent. It's a different way of doing things, and I would exclude numpy/pandas from discussions unless necessary.
If you find yourself implementing a singleton in Python, you probably wanted a module.
The fact that it isn't dogmatic in it's opinions is a feature for the use case.
It isn't great at many things but it can do most things well enough.
It's popularity as a teaching language lagged way behind it's adoption for real world needs BTW.
Apparently Grant Sanderson (3Blue1Brown), the creator of Manim, is braindead: