Also known in python as "if your class has only two methods, one of which is init, it's a function" in the "stop writing classes" https://www.youtube.com/watch?v=o9pEzgHorH0
I have a few little classes that are clients for network services. They have a constructor which sets up an HTTP client or socket or something, and maybe prepares some metadata, and then a method to make a call to the service.
I could write these clients as lambdas or nested functions which close over the stuff which init creates. But why? An object makes it much clearer that there is state.
I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.
In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.
Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.
I prefer structuring code as functor objects as it lets you access the bound parameters as attributes (if you want to expose them) which can also sometimes be useful in code or in test
> I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.
> Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.
It's not the same, though, because a socket etc is being constructed in the constructor. Here's an abridged (and possibly wrong!) version of a monitoring client:
class Monitor:
def __init__(self, monitoring_url, app_name):
self.monitoring_url = monitoring_url
self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
self.details = ujson.dumps({'app_name': app_name, 'pid': os.getpid()})
async def send(self):
async with self.session.post(monitoring_url, data=self.details, raise_for_status=True) as resp:
pass
If you treat that as currying, you will create a new ClientSession every time you call ping(). A ClientSession contains a connection pool, so that means you will create a new socket instead of reusing one.
> In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.
In Python and Java, you can easily refer to bound methods to produce callables from objects, so this seems like unnecessary work.
One of the great tools to use here is `functools.partial`
After that, it's a question of taste. Agreed, callable objects can be useful but if you don't modify your "self" during function calls, partial funciton aplication might be clearer.
After ES6, I jumped at the opportunity to write classes in javascript, but lately I've realised I barely write classes anymore, but I write a lot of functions that return functions. And this example is perfect for that.
I needed two functions that did almost the same thing, but not quite. I don't want to write a ton of nearly-duplicate code, so instead I extract the bits that are different, and write a function that I actually need.
So yes, I'm inclined to say that only mutable state needs a class. And the more you get used to working with constants and immutables, the less mutable state you're going to find.
I used to be a big fan of OO, but somehow experience seems to have landed me in the functional programming camp.
This makes it slightly simpler to create a binding function around groupByLabels which fixes, say, the middle argument. I also think that avoiding the chained use of the => operator makes the code more readable to the average JavaScript dev.
C++ has (strange looking) support for the bind pattern in its standard-library now [0], and Python3 has functools.partial but it only supports left-to-right binding [1] (not that there's any particular reason to avoid lambdas).
Yes. If nothing else, the calculation of that immutable data may be costly enough to motivate not calculating it multiple times. Hence you want to treat it as state. But that doesn't mean you want to expose it to the user of the class which may have a nice simple interface of cheap operations after the init is done.
This is my key question, and I ask with minimal assumption that either one is better.
What are the criteria we are even using to judge? Clarity that there is state is one. Elsewhere there are arguments about ABI that I fail to have practical knowledge of. There was some discussion of the data structure describing your program that completely lost me. Explicitness is lost when discussing closures.
What makes one better than the other in ways that arent entirely subjective?
I think before you do that, you should be careful to ask why the methods are private. Frequently, code like that exists because the private methods are reusable code that is not related to the title of this class. So the solution is first to make the private methods into public methods on separate classes.
And then the resulting classes probably meet some other rule that says "reduce trivial classes to pure functions or closures".
Generally speaking, before a person writes a private function, they should remember that it means "if you misuse it you'll break my otherwise unprotected invariants." Whenever that's not true, you shouldn't write "private". In particular, it doesn't mean "I was too lazy to structure my code correctly so I tried to hide it from the public implementation".
If the code could be extracted into generic top-level pure functions, I generally don’t unless its used in more than once in the codebase. Otherwise I find a number of contextless generic functions/classes without any idea of their use exhausting to look at eventually.
I have some functions that live in closures that are fairly generic. I’m always contemplating extracting them into a higher level. But they’re currently only used in one place, and they currently reside close to that one place, and this seems sensible.
Classes with one public function can be converted into a closure. Classes with two or more public functions can be converted to a closure that return those functions through an object. Then classes and closures become closer.
Classes, of course, offer inheritance. Yet most prefer composition over inheritance. Classes, too, offer polymorphism through typing. But dynamic languages use duck typing. In most dynamic languages where duck typing and composition are used, I find little need for traditional object orientated programming.
I frankly prefer closures, composition and duck-typing over the classes, inheritance and polymorphism through typing. The only thing missing is type checking and Golang's interfaces offer a way through that without the overhead of traditional object orientated design.
I often run into that situation because I'm trying to make the main method a lot simpler by breaking it up into smaller methods with good names. Why should I avoid it?
Or anything that needs validation before you try to run the main part. e.g. the init() method may give you a shopping list of data you're going to need to pass to run() but which couldn't have been deduced until init() validated its parameters.
Sure, you could handle this the functional way by passing a getData() function to run() so it can do so itself but I'm not sure that's any more readable.
Members in Python are never private. Variables that are supposed to be used internally only are marked with and underscore, but that just conveys intent and isn't enforced by the interpreter/runtime. But you can emulate private data like this:
>>> def a(u, v):
... def b():
... return u + v
... return b
...
>>> b = a(1,2)
>>> b()
3
Like this, there is no way to access u & v from b.
When using it like this, the interpreter opens a new namespace where the variables are bound. The x is declared in the outer scope, so it can't find it. However, you can ask python to keep searching vor the name `x` in the closest outer scope, that is what the `nonlocal` statement is for:
def f():
x = 5
class Blub:
def incx(self):
nonlocal x
x += 1
def getx(self):
return x
return Blub()
j = f()
j.incx()
print(j.getx())
That’s not their purpose and they’re not really private. Leading double underscore methods cause name mangling (the class name gets added to the method name) and they’re used for solving specific problems with inheritance. Don’t use them just to make something private. You’re screwing up your inheritance if you don’t realize what you’re doing.
Leading single underscore is private by convention. Still should generally be avoided, but it’s the proper use.
They're only private in a sense that you're not supposed to access them, there's no enforcement by the language in any way beyond renaming them `_<class>__<attr>`. They're still accessible and they still show up in `dir`, `__dict__`, etc.
At the same time, if your function has an expensive initialization step that only needs to be called once (and doesn't change between calls), then the pattern of using `__init__()` + `__call__()` can be very nice.
class Thing:
def __init__(self, arg):
# expensive precompute
self.result = ...
def __call__(self, arg):
# cheap stuff that uses self.result for something
...
And then usage is:
f = Foo("some val")
bar = f("another val")
So yeah, both definitely do the same job. And your pattern is probably faster. But I generally like using Python's dunder methods as I think they allow for more consistent/recognizable code patterns.
Do python linters catch that? It is not a code error and sometimes not a design error either, but a warning would be useful, and linters may have the tools necessary for that.
EDIT: typo, changed link