Python

Python is an interpreted programming language known for its expressiveness and ability to support high-velocity product development. Its ecosystem also encompasses powerful frameworks, such as Django, that make building web applications extremely easy and error-proof. Many studies, such as the one in the following reference, have consistently shown that Python is more than twice as productive as Java:

Reference: http://www.connellybarnes.com/documents/language_productivity.pdf

That said, Python is not without its quirks. The following list describes some of the challenges people face when developing with Python:

  • Performance: Since Python in an interpreted language, computation performance is generally much slower than code that is compiled to native code, due to the extra level of indirection. There is also a lot of thread-serialization in Python programs due to something called the global interpreter lock (GIL). This is a lock (mutex) inside the Python interpreter and it is needed as the interpreter is not thread-safe. Without this lock, multiple threads executing inside the interpreter can cause severe consistency problems. For example, two threads can simultaneously increment the reference count of the same object, and, due to the race condition, the count could end up being incremented only once. To avoid such issues, Python code inside the interpreter is serialized by talking the GIL. The result is a pretty significant performance hit on multithreaded programs. The following table shows the performance benchmark results of Python versus Go:

Benchmark

Go execution time
(seconds)

Python execution time
(seconds)

Mandelbrot

5.48

279.68

spectral-norm

3.94

193.86

binary-trees

28.80

93.40

n-body

21.37

882.00

k-nucleotide

12.77

79.79

Reference: https://benchmarksgame-team.pages.debian.net/benchmarksgame/faster/go-python3.html 

There are third-party packages, such as gevent, that tout concurrency, but the programming model is difficult and they rely on monkey-patching of other libraries, which may cause unexpected behavior.

  • Developer-induced complexity: Python is a very dynamic language and sometimes the freedom causes developers to go overboard and write cute code. This code, over time, becomes very difficult to read, understand, and maintain. You cannot understand the code by just reading it; you need to run through multiple scenarios to get a feel for what's happening. One strange example is the following snippet, which changes the value to internal keyword True:
>>> True = False   
>>> if True == False:   
...     print "what "   
...   
what 
  •  Dynamic Typing: The programming freedom offered by Python leads to another form of complexity: dynamic typing. Consider the following Python code:
ages = { "Abe" : 10, "Bob"   : 11,  "Chris" : 12}   
   
def ambiguos_age(name):   
    to_ret = "not found"   
    try :   
        to_ret = ages[name]   
    finally:   
        return to_ret   
   
   
print ambiguos_age("Abe")   
print ambiguos_age("Des")  

Here, the ambiguos_age() function sometimes returns a string and sometimes an integer. This example might look contrived, but situations such as this can easily arise in production code where functions are deeply nested. This behavior can then cascade into external contracts (such as the JSON response of APIs). Now, if you have a typical Android app (written in Java, which is strongly typed), you will observe the strange behavior of the API sometimes breaking in deserialization.

There will also be a sense of familiarity for Python programmers. Go can alleviate many of the concerns here:

  • The Go programming model epitomizes concurrency and parallelism. Python programmers immediately feel the freedom to model concurrency inherent in the problem they are solving.
  • Go is strongly typed. The ambiguos_age() type of code errors are caught by the compiler! When I first ported Python code into Go, I was surprised by the amount of erroneous code that we had out there in production! While being statically typed, type inference in Go allows brevity, which is something Python programmers treasure.
  • A lot of multithreaded code gets easier to read/maintain with channels. It might take some time to refactor all the mutex lock/unlock-based code into the pipes and filters model based on channels, but the long term maintenance benefit will more than pay off the initial cost of porting.
  • One of the benefits of Python that I (and many other developers) like is the strong formatting guidelines—in particular, indentation. This allows consistency in program layout that enables, among other things, faster code reviews. Go is not as strict in enforcing indentation, although there's the need to start the bracket on the same line as the start of the block. The go fmt tool comes in handy to perform automated formatting (including indentation) and can be included in the Makefile to enforce consistent formatting.
You might be wondering, why are there brackets but no semicolons? And why can't I put the opening bracket on the next line?

Go uses brackets for statement grouping, a syntax familiar to programmers who have worked with any language in the C family. Semicolons, however, are for parsers, not for people, and we wanted to eliminate them as much as possible. To achieve this goal, Go borrows a trick from BCPL; the semicolons that separate statements are in the formal grammar but are injected automatically, without look ahead, by the laxer at the end of any line that could be the end of a statement. This works very well in practice but has the effect that it forces a bracket style. For example, the opening bracket of a function cannot appear on a line by itself (https://golang.org/doc/faq#semicolons).

There will be some gotchas during migration:

  • Like most modern languages, Python has exception-handling as a key language construct. In fact, trying something out and catching an exception using the try-except construct is idiomatic Python. The Go error-handling is basically return of errors from the function and expecting the client to handle the error (which may mean passing it to its own caller). This leads to very verbose error-handling code. It also can be easy to write code that ignores errors in a function, which could lead to wrong behavior or even program crashes.
  • Package-management is also something of a grey area in Go. The Python virtual environment concept (using venv and a requirements.txt requirements file) allows easy management of dependencies.
There are upcoming standards, such as Dep and Govendor in the Go ecosystem, that aim to solve this. As we saw earlier, another easy way to manage dependencies is by using a vendor directory in your GitHub repository.
  • Sometimes Go code is slower than Python. Yes, surprisingly so! A few years back, the Python simple JSON package was about 5x faster than encoding/JSON in decoding and about 2-3x faster than encoding/jSON in encoding. This was because the Python code actually utilized a C extension.
JSON encoding/decoding has gotten faster, and there is a ticket to track this (https://github.com/golang/go/issues/5683). There are also many third-party JSON parsers that claim to be much faster (such as https://github.com/valyala/fastjson).
  • Python programmers can get irritated initially that it takes longer to get to working code (working through all the compiler errors including things such as unused imports—which many developers feel is overkill as an error). But this is just an initial feeling, and they quickly understand the long term benefits of having better-quality code. There are also tools such as goimports (fork of gofmt), which you can plug into your editor to run as you save a file.

Go marks unused imports as errors to enable build speed and program clarity. You can read more about this at https://golang.org/doc/faq#unused_variables_and_imports.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset