I am learning Python and am trying to figure out the best way to structure my code.
Lets say I have a long function, and want to break it up into smaller functions. In C, I would make it a 'static' function at the top level (since that is the only level of functions). I would also probably forward declare it and place it after the now-shortened function that uses it.
Now for Python. In Python, I have the option to create a nested function. Since this new "inner" function is really only a piece of the larger function broken off for readability purposes, and only used by it, it sounds like it should be a nested function, but having this function inside the parent function causes the whole function to still be very long, since no code was actually moved out of it! And especially since the functions have to be fully coded before they are called, it means the actual short function is all the way down at the end of this pseudo-long function, making readability terrible!
What is considered good practice for situations like this?
-
1do you want to stay functional or would you consider programming in oop?tim– tim2013年08月28日 07:23:43 +00:00Commented Aug 28, 2013 at 7:23
-
1See Defining private module functions in python.falsetru– falsetru2013年08月28日 07:25:12 +00:00Commented Aug 28, 2013 at 7:25
-
I think it's best to think of your Python programming as a set of namespaces and scopes. Use classes, modules and packages, as ways to bind (encapsulate) certain types of functionality and behavior (methods) to the instances of some given type (class). Nesting should be used sparingly and only for scoping or to avoid namespace collisions.Jim Dennis– Jim Dennis2013年08月28日 07:49:48 +00:00Commented Aug 28, 2013 at 7:49
4 Answers 4
How about placing the smaller functions in an own file and import that in your main function? You'd have something like:
def main_func():
from impl import a, b, c
a()
b()
c()
I think this approach leads to high readability: You see where the smaller functions come from in case you want to look into them, importing them is a one-liner, and the implementation of the main function is directly visible. By choosing an appropriate file name / location, you can also tell the user that these functions are not intended for use outside of main_func (you don't have real information hiding in Python anyway).
By the way: This question doesn't have one correct answer.
Comments
As far as I know, the main advantage of inner functions in Python is that they inherit the scope of the enclosing function. So if you need access to variables in the main function's scope (eg. argument or local variable), an inner function is the way to go. Otherwise, do whatever you like and/or find most readable.
EDIT: See this answer too.
Comments
So what I could understand is that you have a long function like:
def long_func(blah, foo, *args):
...
...
my_val = long_func(foo, blah, a, b, c)
What you have done is:
def long_func(blah, foo, *args):
def short_func1():
...
def short_func2():
...
...
short_func1()
short_func2()
...
...
my_val = long_func(foo, blah, a, b, c)
You have lots more options, I'll list two:
Make it into a class
class SomeName(object): def __init__(self, blah, foo, *args): self.blah = blah self.foo = foo self.args = args self.result = None # Might keep this for returning values or see (2) def short_func1(self): ... def short_func2(self): ... def run(self): # name it as you like! self.short_func1() self.short_func2() return self.result # (2) or return the last call, on you ... my_val = SomeName(foo, blah, a, b, c).run()Make another module and put the
short_funcsinto it. Just like flyx has suggested.def long_func(foo, blah, *args): from my_module import short_func1, short_func2 short_func1(foo) short_func2(blah)
Comments
The good practice is to keep cycomatic complexity low. This practically means breaking your long function into many smaller functions.
The complexity is measured by the number of if, while, do, for, ?:, catch, switch, case statements, and operators && and || (plus one) in the body of a constructor, method, static initializer, or instance initializer. It is a measure of the minimum number of possible paths through the source and therefore the number of required tests. Generally 1-4 is considered good, 5-7 ok, 8-10 consider re-factoring, and 11+ re-factor now !
I suggest to take this advice, coming from Sonar, a code quality analysis tool. A good way to refactor such code is using TDD. First write unit tests to cover all the execution paths of your current function. After that you can refactor with the peace of mind that the unit tests will guarantee you didn't break anything.
If on the other hand your long function is just long, but otherwise already has a low cyclomatic complexity, then I think it doesn't matter much whether the function is nested or not.