After working on and building things using python programming language for a while, I feel that there are some of the tricks or unknowns that this beast of a language contains and has to offer !
In this article I aim to highlight some of the unknowns that I work with from time to time and that I feel every python developer should know to get the most out of it.
Without any further ado, let's begin !
Unknown 1 - NaN
Well, NaN
itself is an unknown (Not-A-Number) when it comes to programming or mathematics, but what a python developer should focus on is the arithmetic operations on a NaN
object (that is basically math.nan
), they are bizarre !
Let's see this through an example below:
someNaN = float("NaN")
someNaN == someNaN # False
Well, you see python itself couldn't figure out the infamous NaN
object. It considers as what it literally means - Not-A-Number, hence if something is not a numeric value (or can not be converted to a numeric), how come we can compare it (makes sense, right ?).
So, if there is a equality check where we need to filter out all NaN
from a list or dictionary, how can we do that ? For this python provides us with math.isnan
, so to check the NaN
equality, always use math.isnan
.
Although, you may not encounter NaN
objects out of the blue as python is intelligent enough to not throw NaN
, instead it raises expections like ValueError
!
But, if you are doing data science and using numpy or pandas, be careful and check your code atleast twice to make sure you handle NaN
objects.
Unknown 2 - global, nonlocal
Ever encountered a situation where you needed to access out-of-scope variable and didn't know the right way to do it ?
Well, I have plenty of my share experiencing the exact same situation, where I had to refactor a lot of code just to access some out-of-the scope variables !
Let's see how can we access global variables first:
someGlobalVar = True
def someFunc():
return someGlobalVar
def someFunc2():
someGlobalVar = False
return someGlobalVar
def someFunc3():
global someGlobalVar
someGlobalVar = False
return someGlobalVar
print(someFunc(), someGlobalVar) # True, True
print(someFunc2(), someGlobalVar) # False, True
print(someFunc3(), someGlobalVar) # False, False
someFunc
above deals with the global variable while returning someGlobalVar
, without changing the actual global variable outside the scope it directly returns it with no change at all. Python treats it as global and not local to function's scope.
someFunc2
changes the variable someGlobalVar
resulting in python to think that some new local variable is created within the function and hence does not change global variable.
someFunc3
uses global
keyword to explicity say which varibale is going to be global within the function scope. Hence, after using global keyword, python knows that someGlobalVar
is coming from outside scope and it needs to change that.
So, which approach to used while developing software ?
Well, to be honest, it is better to not use too many global variables, but only to define some constants that are never going to be changed during execution.
Still, to use global variables within local scopes of functions or classes, always use global
keyword, even when you are not going to change the value, as explicit is better than implicit !
Same goes for nonlocal
keyword, always specify nonlocal variables when creating closures that requires the same.
def outer():
val1 = True
def inner():
nonlocal val1
if val1:
# do some stuff
nonlocal
keyword will look for the nearest/immediate outer variable, if not found it will move to next outer scope and so on...
Unknown 3 - Mutable Objects
Ever tried to pass mutable objects like list
or dict
as default arguments to a function in python ?
If not, then you have been lucky, if yes, then you know the disaster it brings (sometimes this disaster can be a blessing in disguise, but not often !).
Never ever pass mutable objects as default function arguments (except for logging purpose) !
def someFunc(a = []):
a.append('Never Do This')
return a
print(someFunc()) # ['Never Do This']
print(someFunc()) # ['Never Do This', 'Never Do This']
print(someFunc()) # ['Never Do This', 'Never Do This', 'Never Do This']
During execution, interpretor creates some space for variable a
which is a mutable object list
and when someFunc
is called and appends some value to a
, no new address is created but at the same address where a
resides the value is added (since its mutable, value at same address can be changed), same thing happens during 2nd and 3rd call to someFunc
resulting in getting a completely different list from what we wanted.
A better way to deal with this could be as follows (as most of the tutorials and stackoverflow answers pointed out):
def someFunct(a = None):
if not a:
a = []
Doing this, creates a new address for list a
everytime it is called, as each call when ends destory its contents and recreate it again during next call. But for default args, the are created once !
Unknown 4 - Indexing List
Well, to access some part of the list/string in python, we immediately gets to indexing it ! But, how often do we index it using negative indices or using a method like partition
(which returns a tuple of length 3, for string object) ?
Let's look at in which case negative index could be useful:
a = [1, 2, 3]
print(a[::-1]) # reverse the list in 1 line
A case where negative index makes no sense:
a = [1, 2, 3]
print(a[-0:]) # equivalent to a[:], creates copy of list, [1, 2, 3]
Well, not much here, but for me, I have seldom used negative indices to access elements, sometimes it feels like a trick to do a job and sometime it is hectic to manage negative indexed slice. More than often, just to access the last element I use negative index or to reverse a list !
That's it from me, I hope you learnt something new or refreshed some of the unknowns (that's what I like to call them 😁) !
If you were here as one of the followers, then I appreciate you reading this and if you are new here, then follow me for more upcoming on-the-go articles to freshen up your developer brain 🧙♂️
Share it with your friends, python programming beginners or seasonal python professionals !
Just starting your Open Source Journey ? Don't forget to check Hello Open Source
Want to make a simple and awesome game from scratch ? Check out PongPong
Want to ++
your GitHub Profile README ? Check out Quote - README
Till next time !
Namaste 🙏
Top comments (2)
All these are new to me - but most quizzical is the default mutable object. I had to parse that in my head several times before I could quite grasp its implications.
It's as if
is processed in-interpreter as
a = <an array>
a
to address<$A>
thing
using<$A>
So some sort of pre-processing of assignments ...
Which is essentially what you explained but I just really needed to express it in my own broken break-down....
I have never tried to use such a construct, but I am pretty sure I have seen it in some peoples' code at work so I will endeavour to weed that out.....!
I am englihtened!
Glad you found it interesting 😊
And the disaster that's blessing in disguise is to use this for
logging
within a function. Capture some kind of information everytime a function is called.There are sure some more interesting ways to log our findings or errors, like using decorator, singleton object or mutable objects in functions and simply using conditionals.