Skip to content

Hello, I'm Thomas

GitLab's CI rocks

In fact, I love it so much that I use it to automagically update this blog by simply pushing to its Git repo!

What is CI?

CI stands for "continuous integration" and describes the process of developing software inside an infrastructure ecosystem instead of for that infrastructure ecosystem.

Why is GitLab's CI so cool?

I'm maybe a bit biased here, but GitLab's CI is integrated directly inside GitLab.

This means

  • No extra software needed (compared to Jenkins)
  • Easy configuration inside a project/repository via a simple yml file with intuitive commands
  • Extensive documentation
  • Broad variants of GitLab's build tool, the GitLab runner (e.g. simple bash execution, execution via SSH, in a Docker image or even in a K8s cluster)
  • Automagically activated
  • Direct and useful feedback inside the GUI

Python3: exec in global vs local scope

Today, I encountered a seemingly strange problem. When using exec to execute a command that you do not want to implement hard into your code, everything works fine and as expected in a global scope. That means, if you execute the exec command in the global part of a script or hack it into your favourite CLI, the following code behaves as expected.

>>> import pandas as pd
>>> df = pd.DataFrame({"a": [1,2,3,4], "b":[4,3,2,1]})
>>> print(df)
   a  b
0  1  4
1  2  3
2  3  2
3  4  1
>>> exec('df=df[df["b"] > 2]')
>>> print(df)
   a  b
0  1  4
1  2  3

The exec command evaluates the filter expression and assigns its value to df again. So far, so good.

However, if you now move the exec command to a local scope, i.e. inside a function, it suddenly ceases to yield the expected result.

>>> import pandas as pd
>>> def lol(df):
...   exec('df=df[df["b"] > 2]')
...   return df
>>> df = pd.DataFrame({"a": [1,2,3,4], "b":[4,3,2,1]})
>>> print(df)
   a  b
0  1  4
1  2  3
2  3  2
3  4  1
>>> print(lol(df))
   a  b
0  1  4
1  2  3
2  3  2
3  4  1

Obviously, I cannot overwrite the df variable here. On the other hand, it is however perfectly possible to create new variables in the local scope:

>>> def some_method():
...     print(locals())
...     exec("never_before_seen_variable = 5")
...     print(locals())
...
>>> some_method()
{}
{'never_before_seen_variable': 5}

As a side note: It generally is not a good idea to use exec anywhere in your code as it can and will introduce unforeseen and unwanted side effects. For example, the code above will not fail since the variable assignment is a perfectly valid expression. However, it does not have the desired effect (to be fair, it does not have any effect). Additionally, exec can execute arbitrary code and whole code blocks. If you don't know exactly who will be using your code (and as a consequence, will have access to your exec statement), better leave it out.