So I come from a OOP approach, specially since I started on Javascript (quickly swicthed to Typescript and have not used JS since), and have learnt Java, C# and C++ in the process. For that reason, I’ve always thinked in a class/object and reusable approach, trying to abstract objects as much as possible.
However, when I started learning python I understood that even though OOP is possible, python is intended to have a functional programming approach. I’ve tried to have this in mind when building some projects but It’s a bit hard, when I say “let’s do just one class” I finish having a buch of OOP structure, and when I go for functions only, I’m not yet fully clear on the “pure function” principle and try to divide each step in a separate function.
My question, and I know this is kind of subjective (personal preference), is: Is there a intended way to use python (OOP vs Func) for an optimal use? And If functional approach is the go, when or what criteria you have present for “stopping” doing a class method but a pure function to accomplish something?.
Your personal experiences would be appreciated here, specially If you have the time to explain why you like/prefer that.
Then, It’d be reasonable to conclude that even though you can do it both ways (classes vs func), It’s a “good practice” to leave classes for more complex task completitions? (With task meaning a project, etc)
It is a functional language in the sense that functions are first-class values, but unlike the general run of functional languages, this is because functions are objects and Python is an object-oriented language. While map, reduce and filter are all within easy reach, a lot of the idioms that are common in functional languages (tail call recursion, currying, composition) are not as natural in Python.
As to when a class is too much, that’s a matter of personal preference. But when approaching a library, I would prefer to encounter as few new data types as possible to effectively work in the space. Functions that operate on primitive data types or target the standard stack of interfaces (collections.abc — Abstract Base Classes for Containers — Python 3.13.1 documentation) will often be more composable with other libraries.
I get both, however in my personal opinion It’s just more clear on the class side, specially on the “closely coupled” case where a state may be involving several other things - That being said, I’m noting a bit that although there’s like a kind of common sense involved, everything gets to the personal preference. Kind of folder structuring, everyone has a preference although there’re some kind of “standards” for It too.
I think OOP and Func are both intended to be supported.
My personal preference is to use features which are as inflexible as possible, which means opting for functions whenever possible. Classes are too powerful, and as a result when you deal with a class you never quite know what too expect. Especially if you allow yourself to use super().__init__, you can do practically anything. At that point you might as well use gotos.
But Python doesn’t quite accommodate my personal preferences. The most predictable class I can imagine would look something like this:
@dataclass(frozen=True, slots=True)
class C:
a: int
b: float = 0.0
@cached_property
def c(self)->float:
return f(self.a, self.b)
(Except it would have more attributes and more methods.)
but the use of slots is incompatible with the use of cached_property.
Speaking further of personal experience, I find it very useful to use methods that only exist as wrappers of functions, as above. It is great for testing.
But classes are useful as interfaces.
Python is not intended to have a functional programming approach. Python is an object-oriented language first, with some functional-style programming possible, but not necessarily always made simple (e.g. due to lambdas only allowing a single statement). You can write functional-style code in any language, but some languages make this very easy and make this the primary way of writing code, while others make it harder or less pleasant for one reason or another.
The main difference is that while Java et al. forces you to always write classes, even if all they contain are static methods, you don’t need to do that in Python — just define functions directly on the top level.
Pure functions are extremely simple: those are functions that do not modify any state. def sum(a: int, b: int) -> int: return a + b is a pure function — you can call this function many times with the same a and b, and you will get the same result. However, list.append is not pure, because the list is mutated, and calling it multiple times with the same arguments will yield a different result. Pure functions are often easier to reason about, but there’s always some impurity or state that needs to be managed somewhere.
I also want to ad a distinction. OOP can be thought about in different ways, and this leaves some marks on each language. In other words, a good OOP approach in one language can be a terrible approach in another (also OOP) language. “OOP approach” bucket can’t be put onto a new language usage, without major changes.
For example, Java thinks in terms of classes, but Python thinks in terms of behavior. In Java, to use an object as an instance of class, it needs to be constructed by that class, or a class explicitly inheriting from it. Aka. if I didn’t call it a duck, then it cannot be a duck. This gives Java programer a very strong guarantee about that object. But in Python, that’s a terrible idea! Instead, Python’s type hints use ABCs. So, to use an object as an instance of a class, it only needs to behave as one. And the class doesn’t need to be declared at all. There can be no type hint, and the methods can be remembered on an instance. Aka. if it looks like a duck, and behaves like a duck, then it must be a duck. This gives Python programer a great freedom to choose (and support) many different libraries.
there are probably more recent talks that make the point, but I really liked these, pretty much back to back at PyCON 2012:
“The Art of Subclassing”: Raymond Hettinger
Catch Phrase: “sublclassing is for code reuse – not for creating taxonomies”
(from memory, I can’t find it right now in the video …)
“Stop Writing Classes” by Jack Diederich:
Catch Phrase: “If a class only has two methods, and one of them is __init__ – it’s not a class.”
From the titles, you might think these two disagree about OOP and Python – but I don’t think so – the point is that you should use classes if they provide a real benefit, but not if they don’t.