Why PEP8 is not enough to write Pythonic code (1/2)

A lot of python programmers claim that if you can program in any language then you can program in python. And to be quite honest it’s… true. Writing Python code that does its job is not hard. But writing elegant and readable python code is… also not that hard. Unfortunately, those two facts often lead to the situation when new team member that doesn’t know python is asked to write code and when they ask an unavoidable question (“Do we have any coding standards”) the answer is “Just read PEP8 and you will be fine – we are using it in our project with few modifications: [here is the list of those modifications – usually something about line length and camel case]”.

Unfortunately, PEP8 is not enough to claim that your code is Pythonic. Here is the list of examples of things that most people coming from other languages (mostly Java and C/C++) do wrong when they try to write Python.

Function calls

Python is elegant. It was designed with readability and flexibility in mind. But people coming from other languages tend to use styles that made sense in those languages and try hard to write similar code in python. Here we have a function call that retrieves telemetry data from HyperDrives:

hd = get_telemetry(ip, 20, 10, False)

Can you easily say by just looking at this code what are those parameters mean? You can probably easily guess what the first one is. What about others? If you are reading code the only way to find out is to read a declaration of the function. If you are reviewing code like this and ask responsible developer you will probably get an answer like this: “You can check in IDE and it’s OK because it’s pep8 compliant!!!!111111oneonejeden”. Of course, I can use IDE to check that. But it’s always nice not to have to think about things like this and concentrate your mental powers on code that is really important.Solution:

hyp_drv_data = get_telemetry(ip, jumps=20, errlvl=10, details=False)

Which one is more readable and elegant? Which one can be easily understood in a few months? Technically speaking both of those examples are PEP8 compliant.

Setters, getters

Your newest team member (formerly Java guy) was asked to write a simple class that describes RGBA color. He comes up with something like this:

class Color:
    def __init__(self, red=0.0, blue=0.0, green=0.0, alpha=0.0):
        self.red = red
        self.blue = blue
        self.green = green
        self.alpha = alpha

    def set_red(self, value):
        self.red = value

    def set_blue(self, value):
        self.blue = value

    def set_green(self, value):
        self.green = value

    def set_alpha(self, value):
        self.alpha = value

    def get_red(self):
        return self.red

    def get_blue(self):
        return self.blue

    def get_green(self):
        return self.green

    def get_alpha(self):
        return self.alpha


In many languages, it’s very common to never expose fields from the class and only allow to access/modify those with setters/getters. It makes sense. If someone uses it directly and later on you want to add some sanitization when setting it, your only path to do that is by hiding the field and creating setter/getter. The unwanted side effect is that you break code using your class. It’s not that bad if it’s your code – you can fix it easily. Problems start when you have some clients using your code or other departments at your company. They might be not that understanding with your changes. Try to do something like this with guys writing code that prepares data to be sent with ultra-violet beam to Empire’s Flotillas in another galaxy system. Those guys have no chill.

Because of those issues, people coming from other languages tend to create setters/getters every time they want to expose some fields. Even if it’s not needed now, it can save a lot of troubles in the future. It’s a little bit different in Python:

There is no such thing as “private” field – we are adults here and we should know what we are doing. __ and _ are just conventions and don’t prevent anyone from using them if they really want

It’s a dynamic language so if you add __ or _ + setter/getter, it might break some code. But without compilation, it’s not that easy to detect those type of issues. Especially if your code is used in different project and you cannot easily change things over there.

Writing those necessary setters and getters for “later” (that might never come) is useless and not very elegant. Is there a python solution to our problem? Yes, there it is! You should start with just this:

class Color:
    def __init__(self, red=0.0, blue=0.0, green=0.0, alpha=0.0):
        self.red = red
        self.blue = blue
        self.green = green
        self.alpha = alpha

And use it like this:

model_color = Color()
model_color.red = 1.0


Now some of you might start asking: “But you just talked about how hard it will be to find every place that those fields were used like this after you realize that there are some edge cases and you need to do something before setting/reading that field?” The answer is very simple – if the time comes that you need to add some logic, you will add it and it will be still exposed as a field and not function.

As an example, we will use our color class. After a few months, you might find that some 3rd party libraries are keeping color not as floats(0.0-1.0) but as ints(0-255)Here is partial (I’m lazy) solution to that problem that will still allow using member fields but will also make sure that we keep data in the same format:

class Color:
    def __init__(self, red=0.0, blue=0.0, green=0.0, alpha=0.0):
        self.red = red
        self.blue = blue
        self.green = green
        self.alpha = alpha

    def red(self):
        return self.red

    def red(self, value):
        self.red = self._color_converter(value)

    def _color_converter(self, value):
        return value if type(value) is float else value / 255

It’s also interesting that even assignment in our constructor (self.red = red) will trigger our custom setter.

As you can see both Color classes are PEP8 compliant. But one is noisy and contains a lot of boilerplate that might be never used and another starts as a simple&elegant one but still can be extended WHEN it’s really needed and not “just in case”.

Stay tuned! Next part of the article in a week!
Let know us what do you think about this article? Share your knowledge and experience with us: