Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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

EDIT: typo, changed link



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.


If there is state then it makes sense for it to be a class

But the parent comment applies


Using init/a constructor implies state, does it not?


I think that depends on what we understand by 'state'. Do we count immutable data? Consider faking a 'bind' with something like:

    var quadrupler = new ConstantMultiplier(4);
    var output = quadrupler.applyTo(42);
The 'quadrupler' instance is stateless in that it's immutable, but it presumably has a (constant) member to store the value 4.


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.

For example, I recently wrote this:

  const labelizeElement = ((element) => element.type.replace(/\s+/g, ''));
  const labelizeRelation = ((relation) => relation.type.toUpperCase().replace(/\s+/g, '_'));

  const groupByLabels = (labelizer) => (set, element) => (
    set[labelizer(element)]
      ? { ...set, [labelizer(element)]: [element, ...set[labelizer(element)]] }
      : { ...set, [labelizer(element)]: [element] }
  );
  const groupByElement = groupByLabels(labelizeElement);
  const groupByRelation = groupByLabels(labelizeRelation);
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.


Personally I'd avoid currying and go with a function with three params, like:

    const labelizeElement = ...
    const labelizeRelation = ...
    
    const groupByLabels = (labelizer, set, element) => ....
    
    const groupByElement = (set, element) => groupByLabels(labelizeElement, set, element);
    const groupByRelation = (set, element) => groupByLabels(labelizeRelation, set, element);
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).

[0] https://en.cppreference.com/w/cpp/utility/functional/bind

[1] https://docs.python.org/3/library/functools.html


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.


    def __init__(self):
        pass

Not necessarily. Or the constructor is just setting some values that are later used by the calculation but are not modified and used later


> But why?

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/O in constructor ? I thought that is an avoid


And if you have many methods, but only one 'public' method, use a closure.


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?


Your link goes by means of Google; here's a direct link: https://www.youtube.com/watch?v=o9pEzgHorH0


Or it's a named stateful or immutable data container with validation? You don't always want to use primitive types. But I am not a Python dev.


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.


Then build a function from validated parameters (or partially apply some parameters).


Yes, this is the point of `dataclasses` and `attrs`.


Python doesn't have private members, so that doesn't help you as much.


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.


Looks like classes can't be closures, though:

  >>> def f():
  ...     x = 5
  ...     class Blub:
  ...             def incx(self):
  ...                     x += 1
  ...             def getx(self):
  ...                     return x
  ...     return Blub()
  ... 
  >>> j = f()
  >>> j.incx()
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 5, in incx
  UnboundLocalError: local variable 'x' referenced before assignment


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())
This will print 6, as expected.


Python has private members through double underscore methods


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.


I would consider this pattern:

    def x():
        expensive precompute
        def y():
           cheap stuff
        return y
It's a matter of taste I guess.


For comparisons sake:

  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.


It's a warning in pylint by default: "Too few public methods (1/2)"


Here is how to disable it:

# pylint: disable=too-few-public-methods


what if it has just values (like an enum)?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: