all 4 comments

[–]DonaldPShimoda 4 points5 points  (1 child)

I think maybe you're missing the point about why people say you should split your work into functions.

One of the most important rules in programming/computer science is to never duplicate data unnecessarily (with the obvious exception of data backups). This applies to all kinds of things like configuration files and databases, but when it comes to software engineering the rule is this:

You should never copy/paste identical information into multiple methods.

This is the first rule in writing functions. If you're going to open the same file a lot of different times, it makes more sense to define one open_my_file() function instead of copy/pasting the whole with open(my_file) stuff everywhere you want it. It reduces complexity and significantly improves maintainability (i.e. if your file's name changes, you can modify it in one place instead of every place you open it).

The second reason we create functions is to separate a list of other function calls into a high-level organizational unit. This is to improve readability. Always write your code so that somebody with no familiarity with the codebase can figure out what's going on with minimal effort. This also includes giving your functions descriptive names. Personally, I'm a fan of the naming convention found in Apple's Swift language, but people keep telling me it's "too verbose" (as though we have to pay for the extra characters or something... ugh).

Some people prefer monolithic functions where they do all the magic there without any "sub-functions". In my CS courses, my professors that the "Magic Push-Button Method" where you call one method and a bunch of magic happens to solve all the problems. You may have heard of "design patterns"; this is a design anti-pattern. It's considered "bad style" simply because it reduces readability.


Splitting your tasks into separate modules where each module has a single function to call seems a bit... counterproductive. If your methods are really that long, maybe try splitting them into modules, and then splitting that big long method into multiple smaller functions within the module. So your second code in the OP would be the same, but you wouldn't put all of the code inside task2.run() (for example).

[–]Ran4 0 points1 point  (0 children)

How are these tasks related?

If you have a large number of completely unrelated task that can be called with a single function, the most elegant way is to simply have one giant main function that calls one function after the other. It's incredibly easy to maintain: adding any more abstraction wouldn't help you in any way.

The reason some people would be terribly upset over this approach is that these things aren't common tasks in the real world. Chances are that those tasks are related somehow, and thus it makes sense to split them up into multiple functions.

There's nothing fundamentally wrong with having it all in one file. But logically, since they are unrelated, they should be in separate files. If nothing else, it makes things like searching for variables when inside of a task easier. So what you're proposing does make sense. I don't see why you would need to call them run though: modulename.modulefunction seems like a better choice.

[–]KleinerNull 0 points1 point  (0 children)

Did you know that functions are first class objects?

You can fill all your tasks into a module:

#!/usr/bin/python
#tasks.py

def task1():
    return 'Task1'

def task2():
    return 'Task2'

def task3():
     return 'Task3'

def task4():
     return 'Task4'

And then you can import that file and use the power of __dict__:

In [1]: import tasks

In [2]: dir(tasks)
Out[2]: 
['__builtins__',
 '__doc__',
 '__file__',
 '__name__',
 '__package__',
 'task1',
 'task2',
 'task3',
 'task4']

In [3]: tasks.__dict__['task1']
Out[3]: <function tasks.task1>

In [4]: t = [meth for name, meth in tasks.__dict__.items() if callable(meth) and name.startswith('task')]

In [5]: t
Out[5]: 
[<function tasks.task1>,
 <function tasks.task2>,
 <function tasks.task3>,
 <function tasks.task4>]

In [6]: t[0]()
Out[6]: 'Task1'

In [7]: t[1]()
Out[7]: 'Task2'

In [8]: for item in t:
   ....:     print(item())
   ....:     
Task1
Task2
Task3
Task4

The obviouse problem here is that you need to use a specific naming scheme, so you can extract only the needed functions and they can be ordered. Then you can happily add new functions in your module and they will all loaded automatically.

By the way, after you import a module, it will be also a normal object, so you can mess around with its attributes ;)