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.
"Object-Oriented Programming is Bad"
https://www.youtube.com/watch?v=QM1iUe6IofM
Followed by "Object-Oriented Programming is Embarrassing: 4 Short Examples".
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.
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.
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.> 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.
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.
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.
The advice to use dataclasses is good but... dataclasses are, um, classes.
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:
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)