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

I like the article. This is a great idea. I'm going to implement it over the next week. I have high hopes.

That being said, I have absolutely no clue what the advantage is to the python programming style exemplified in the article. Can someone explain why on earth I would ever want to convert:

    def do_something(context):
        pass
    ...
    do_something(c)
into

    class DoSomething(object):
        def run(self, context):
            pass
    ...
    DoSomething().run(c)
What is the point here?


In that contrived example, there is no point. In the real world, there are plenty of reasons to use a class instead of a function. Extensibility is the first that comes to mind. What answers are you expecting (i.e. performance)?


I want to know about this hypothetical extensibility. What extensibility does this approach achieve over just a bunch of functions?


Potential reasons to use classes over just a bunch of functions (and the syntax is rough and probably wrong, I don't use Python often):

1. You have a complex step that you want to refactor. Without classes you end up with something like:

  def Actual_Step(context):
    part1(context)
    part2(context)
    part3(context)

  def part1... 
  def part2...
  def part3...
Great, you've now polluted the larger namespace and further exacerbated the need to thread data through a bunch of function calls. With a class, you could do something like:

  class Actual_Step(object):
    var1, var2, var3 # things needed for this
    def run(self, context):
      # set the vars somehow
      self.part1()
      self.part2()
      self.part3()
    def part1(self):
      use var1
    def part2(self):
      use var2
    def part3(self):
      use var1, var2, var3
It's not stateless, but it works just fine. No polluting the larger namespace, and less threading a bunch of data through a bunch of calls. (Do you need the self.part1() bits in Python?)

2. You want to inherit. Maybe you have a collection of steps that all have the same setup/teardown bits, you can do this:

  class Common_Base(object):
    def setup(self, context):
      ...
    def teardown(self, context):
      ...
    def custom(self, context): # should be implemented by each derived class
      ...
    def run(self, context):
      self.setup(context)
      self.custom(context)
      self.teardown(context)

  class Specific_Instance(Common_Base):
    def custom(self, context):
      ...
And reuse that in multiple places.

3. Customizing at construction time. Maybe you have a step that goes out to some database, but what database may vary depending on what you're doing (for instance, at work we have test and ops modes):

  class Get_Data(object):
    def __init__(self, db_name):
      self.db_name = db_name
    def run(self, context):
      ...
And of course, since this is Python, you can change all of this to use a decorator or decorators and remove most of the boilerplate, but still retain the potential benefits.

In theory you could grow this (though at that point you may be better off with another system) so that different reports can be constructed from the steps themselves, without the need to execute them. Add elements like documentation strings that can be returned and prettified for different user interfaces or reporting purposes. All without ever having to touch the actual methods doing the work, just the way you interact with the classes holding them.


So the first example can be done like so:

    def do_thing(context):
        def step1():
            pass
        def step2():
            pass
        def step3():
            pass
        step1()
        step2()
        step3()
The second example can be replaced with:

    def make_thing_doer(db_name):
        def f(context):
            pass
        return f
I think both examples are clearer and involve less boilerplate.

But I specifically asked why that initial example posted to the blog was written the way it was. I will stress that "because in the future you may want to do X or Y with it" is not a really good reason because there is absolutely no reason why you couldn't just replace the function when it came to that point.

Moreover, the database example you gave would require changing the code just as much as the function example I gave.

specifically, in your case:

    tasks = [
        ThingDoer1(),
        ThingDoer2(db_name),
        ThingDoer3(),
    ]
or in my case:

    tasks = [
        do_something_1,
        make_thing_doer_2(db_name),
        do_something_3,
    ]
Once again, less code, fewer noisy parentheses, I don't see the advantage.

Edit: I missed the inheritance example at first read but this can trivially be implemented like so:

    def make_thing_doer(custom_step):
        def step1():
            pass
        def step3():
            pass
        def f(context):
            step1()
            custom_step(context)
            step2()
        return f
or more simply:

    def do_thing(custom_step, context):
        ...
        step1()
        custom_step(context)
        step3()
    
    def special_step(context):
        pass
    
    do_special_thing = lambda c: do_thing(special_step, c)
That doesn't implicitly inherit all the state. In which case I would concede that a class may be worthwhile. But as I already said, making the code so ugly initially is not justified by the possibility of eventually needing it to do this specific thing.

You can still make the code do this specific thing without ever needing to make it so ugly to begin with.

Nothing stops you from mixing the style and implementing __call__ in the class (or subclassing from a class which implements __call__ and calls run() if you don't like double underscores.


I mean, if you don’t like classes then don’t use classes. It’s not forced here. But if the author’s article is correct, they’re planning to change the script. Presumably they’ve done it enough to know a basic structure that’s worked well for them once they’ve started making changes. Why not set up that structure at the start if you know it’ll be the end result?


> Why not set up that structure at the start if you know it’ll be the end result?

Precisely because it's an unnecessary amount of code overhead.

There's no indication at all that IF he needed to expand the code that classes would be necessary UNTIL there were very specific requirements and there's also no reason why he should put that code in place in case of that requirement popping up.

It's always cheaper to write the simplest code you need at the time and then delete it (or rework some of it) and write the more complicated code you need in the future than it is to write more complicated code up front and expand less on some of it in the future.

If the point of the article is effectively "minimising the activation energy required to start automating a task" then surely the code should also be minimal?


> Precisely because it's an unnecessary amount of code overhead.

One extra line per step is not a lot of overhead.


It's more than one line though is it, it's a lot more _text_ even ignoring whitespace it's almost twice as much text. And that's the least important metric! Classes carry a lot more baggage. In the sense that if you see one, your expectations change and expand. The realm of possibilities expands too. This puts extra mental load when anyone new to the codebase has to look at it.

The fact that I looked at that code and spent 5 minutes trying to figure out why you would want classes there and what exactly it achieves is precisely what you want to avoid in codebases.

I audit code for a living, a lot of it, it takes far more work to audit code where the authors put a lot of unnecessary boilerplate or unnecessarily flexible code in place because it means I have to start figuring out what the authors intentions were. The expanded realm of possibilities means I have to check for more behaviors. It slows down the reading process.


There is no benefit in using classes that way. Technically, it's even harmful, because it has slight overhead. It's just poor style to have a consistent look for the script. You could just do the same with functions, and replace them one by one with classes when the demand for more complex code comes up.

Though, it's also possible that the writer simple did'nt know better and has not enough understanding of python, or thinks other people who might extent them lack those knowledge.


This script is ridiculous in python with classes. It would be much cleaner as a sequence of prints and inputs.




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

Search: