Some words about Python to prior Java programmers

tl;dr – if you class name ends with Collector, Executor or Task, did you consider writing collect(), execute() or task() instead?

Dude, not everything is a class.

In Java, you cannot have free procedures. In Java if you wanted just a behaviour,  you would define a class with a constructor and a single routine, say execute() or main(). In Python, this is discouraged. You should contain your behaviour in a single def. A tell-tale sign of this is creating a class, even without storing it in an intermediate variable, and then launching it’s main() or do() or go() or execute() or run(). Instead of a CleanupExecutor().run()  try writing cleanup(). You’ll get rid of that nasty boilerplace, and also gain significant speedup, because Python won’t need to allocate a dictionary and collapse two function calls into one. Python allows you to play with function arguments to your’s content, and there’s usually very little need to build a two-method class, with a well-defined execution order (ie. validate() runs always before run()).

Especially, things that end with Executor() or Collector() or just any verb-derived noun should be rephrased as execute(), collect() or verb().

I need to store some attributes

Did you know that you can use annotations to store information within the function itself? Moreover, thanks to the function being a Python object, you can create and inspect it’s attributes on the fly. Just take a look at this:

def a(i: int) -> float:
   pass
a.test = 5

assert a.__annotations__[i'] is int
assert a.__annotations__['return'] is float
assert a.test == 5

You can also build a decorator to assign these values to your function:

def assign(key: str, value):
   def inner(fun):
      setattr(fun, key, value)
      return fun
   return inner

@assign('test', 5)
def a(i: int) -> None:
    pass

assert a.test == 5

And also introspect them during runtime

But I have significant setup that I have to do!

In that case, store your data within local variables and add a set-up section to your def. You can use comments too delimit where the initialization section. One benefit you will see to it is a decrease in self.variable1 = variable1 in constructor sections.

Also please answer yourself – is your setup that extensive? 10 lines of self.x = x don’t count.

But I need to inherit from this class’s behaviour

In this case you will simply need to call the parent procedure from your child procedure. If you don’t care about some parameters, just define them as *args and **kwargs.

When should I use a class and when should I use a function?

You use object if there are multiple possible interactions you can have with your object. Use a class when it has some intermediate data you’ll need access to between subsequent calls. If in doubt, bias yourself toward functions. Special case is when a function will return some intemediate data, and only it matters – in this case you should make them two functions, especially if they don’t share any data. If you have data stored within a one object, and a subsequent call to a codepath will require some prior data – that’s a class.

If you note that you class has two or more codepaths, say initialize() or validate() that runs always before run(), please refactor that into more functions. A valid way to decompose a complicated function is not an object, but a series of functions instead, especially if they don’t share data.

But I need to split this function!

A correct way to split a large function is to split it into a series of smaller ones, not to use an object.

But I might need to extend this in the future!

Classic YAGNI – You Ain’t Gonna Need It. If you have a need to refactor it in the future, you can always do so. Remember that calls to __init__() and call to a function have the same semantics in Python. Even, if you phrased your constructors as finally calling run() or execute(), you can easily refactor them into function calls later. Python is, by Java standards, extremely programmable, and it can be programmed to do nearly all the behaviour you’ll ever need. Consult a local guru, if there’s a thing you feel that would be particularly clever to do, but you just don’t have an idea how to do. Programming is nowadays a rarely single profession, and most of the time you’ll have other team members, who might help you. The time you spent on making it a class would be best spent on thinking how do I write refactorable code.

Closing words

Switching to Python from Java isn’t just a matter of language. You should definitely change the way you think about code, especially when you have a few years of Java experience. Java tends to smack ideas until they become things, whereas in Python you can define pure actions, that don’t require long constructors to accept variables. While it’s true that you can write Java in Python, but you should avoid it as long as possible. This will result in cleaner code, that’s also easier to understand by other Python programmers, who are used to functional programming.

Embrace this difference. You will soon find out that, when you switch, your code becomes much shorter, and it’s always a valid practice to sacrifice boilerplace for readability.

Published

By Piotr Maślanka

Programmer, paramedic, entrepreneur, biotechnologist, expert witness. Your favourite renaissance man.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.