5 ms·
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 th
by ttymck 5y ago
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)?
- Arch-TK 5y agoI want to know about this hypothetical extensibility. What extensibility does this approach achieve over just a bunch of functions?
- Jtsummers 5y agoPotential 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.
- Arch-TK 5y agoSo 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.
- Jtsummers 5y agoI 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?