30 Things I Never Knew About Python Until Recently (Compilation)
# Despite Learning Python Since 2017
1) Mapping Proxies (Immutable Dictionaries)
Mapping Proxies are dictionaries that CANNOT be changed after creation. We use this if we don’t want users to be able to change our values.
from types import MappingProxyType
mp = MappingProxyType({'apple':4, 'orange':5})
print(mp)
# {'apple': 4, 'orange': 5}
We get an error if we attempt to change stuff in a mapping proxy.
from types import MappingProxyType
mp = MappingProxyType({'apple':4, 'orange':5})
print(mp)
'''
Traceback (most recent call last):
File "some/path/a.py", line 4, in <module>
mp['apple'] = 10
~~^^^^^^^^^
TypeError: 'mappingproxy' object does not support item assignment
'''
2) __dict__ Is Different For Classes & Objects
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
rocky = Dog('rocky', 5)
print(type(rocky.__dict__)) # <class 'dict'>
print(rocky.__dict__) # {'name': 'rocky', 'age': 5}
print(type(Dog.__dict__)) # <class 'mappingproxy'>
print(Dog.__dict__)
# {'__module__': '__main__',
# '__init__': <function Dog.__init__ at 0x108f587c0>,
# '__dict__': <attribute '__dict__' of 'Dog' objects>,
# '__weakref__': <attribute '__weakref__' of 'Dog' objects>,
# '__doc__': None}
The __dict__
attribute of objects are normal dictionaries, while the __dict__
attribute of classes are Mapping Proxies, which are essentially immutable dictionaries (cannot be changed).
3) any() and all()
any([True, False, False]) # True
any([False, False, False]) # False
all([True, False, False]) # False
all([True, True, True]) # True
The any()
and all()
functions both take in iterables (eg. lists).
any()
returns True if AT LEAST one element is True.all()
returns True only if ALL elements are True.
4) divmod()
The built-in divmod()
function does both //
and %
operators at the same time.
quotient, remainder = divmod(27, 10)
print(quotient) # 2
print(remainder) # 7
Here, 27 // 10
evaluates to 2, while 27 % 10
evaluates to 7. Hence, the tuple 2, 7
is returned.
5) Inspect variables easily using formatted strings
name = 'rocky'
age = 5
string = f'{name=} {age=}'
print(string)
# name='rocky' age=5
In formatted strings, we can add =
after a variable to print it in the syntax var_name=var_value
.
6) We can convert floats into ratios (sometimes funky)
print(float.as_integer_ratio(0.5)) # (1, 2)
print(float.as_integer_ratio(0.25)) # (1, 4)
print(float.as_integer_ratio(1.5)) # (3, 2)
The built-in float.as_integer_ratio()
function allows us to convert a float into a tuple representing a fraction. But sometimes it behaves funky.
print(float.as_integer_ratio(0.1)) # (3602879701896397, 36028797018963968)
print(float.as_integer_ratio(0.2)) # (3602879701896397, 18014398509481984)
7) Showing existing global/local variables with globals() and locals()
x = 1
print(globals())
# {'__name__': '__main__', '__doc__': None, ..., 'x': 1}
The built-in globals()
function returns a dictionary containing all global variables and their values.
def test():
x = 1
y = 2
print(locals())
test()
# {'x': 1, 'y': 2}
The built-in locals()
function returns a dictionary containing all local variables and their values.
8) The __import__() Function
import numpy as np
import pandas as pd
^ the normal way of importing modules.
np = __import__('numpy')
pd = __import__('pandas')
^ this does the exact same thing as the code block above.
9) Infinity values in Python
a = float('inf')
b = float('-inf')
^ we can define positive infinity and negative infinity above. Positive infinity is larger than all other numbers, while negative infinity is smaller than all other numbers.
10) We can use ‘pprint’ to print stuff nicely
from pprint import pprint
d = {"A":{"apple":1, "orange":2, "pear":3},
"B":{"apple":4, "orange":5, "pear":6},
"C":{"apple":7, "orange":8, "pear":9}}
pprint(d)
11) We can print coloured output in Python
We need to pip install colorama
first.
from colorama import Fore
print(Fore.RED + "hello world")
print(Fore.BLUE + "hello world")
print(Fore.GREEN + "hello world")
12) A faster way to create dictionaries
d1 = {'apple':'pie', 'orange':'juice', 'pear':'cake'}
^ the normal way
d2 = dict(apple='pie', orange='juice', pear='cake')
^ a faster way. This does the exact same thing as the code block above, but we type fewer quotation marks.
13) We can unprint stuff in Python
CURSOR_UP = '\033[1A'
CLEAR = '\x1b[2K'
print('apple')
print('orange')
print('pear')
print((CURSOR_UP + CLEAR)*2, end='') # this unprints 2 lines
print('pineapple')
The printed output:
- printing the
'\033[1A'
character moves our cursor up by 1 line. - printing the
'\x1b[2K'
character clears the entire current line. - When printed together, we can unprint entire lines
14) Private variables in objects aren’t private
class Dog:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
Here, the self.__name
variable is supposed to be private. We shouldn’t be able to access it from outside of the class. Except that we can.
rocky = Dog('rocky')
print(rocky.__dict__) # {'_Dog__name': 'rocky'}
We can use the __dict__
attribute to access or edit these attributes.
15) We can create classes using ‘type()’
classname = type(name, bases, dict)
name
is a string representing the class’ namebases
is a tuple containing the class’ parent classesdict
is a dictionary containing attributes & methods
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f'Dog({self.name}, {self.age})')
^ creating a Dog class the normal way
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f'Dog({self.name}, {self.age})')
Dog = type('Dog', (), {'__init__':__init__, 'bark':bark})
^ creating the exact same Dog class as above using type()
16) We can use Chinese characters as variables
Or emojis. Or nearly any unicode character.
我 = 4
你 = 5
print(我 + 你) # 9
17) Backspace character in Python
The backspace character \b
deletes one printed character.
print('abc' + '\b' + 'd') # abd
18) The bell character allows us to make bell sounds
print('\a')
Try this in cmd/terminal. Printing the bell character '\a'
should make a bell sound.
19) We can use classes as decorators
class add():
def __init__(self, char):
self.char = char
def __call__(self, function):
def inner(*args):
return function(*args) + self.char
return inner
@add("!")
def greet(name):
return "hello " + name
print(greet("tom")) # hello tom!
^ this is possible due to the __call__
magic method, which defines what happens when we call the add
object.
20) Functions can have variables
def test():
print('test')
test.apple = 4
print(test.apple) # 4
21) An easy way to align strings
The string methods ljust
, rjust
and center
can be used to pad a string with spaces while aligning the string to the left/right/center respectively.
print('>' + 'hello'.ljust(10) + '<') # >hello <
print('>' + 'hello'.rjust(10) + '<') # > hello<
print('>' + 'hello'.center(10) + '<') # > hello <
Or we could use formatted strings to do this:
print(f'>{"hello":<10}<') # >hello <
print(f'>{"hello":>10}<') # > hello<
print(f'>{"hello":^10}<') # > hello <
22) This happens when we add a list to itself
lis = ['apple', 'orange']
lis.append(lis)
print(lis)
# ['apple', 'orange', [...]]
23) We can run Python code in strings using eval()
print(eval('4+5')) # 9
print(eval('7-3')) # 4
The eval()
is built-in and we don’t need to import it.
24) round() accepts negative decimal places
print(round(3.14159265, 2)) # 3.14
^ normal way of using round()
. Here, we round 3.14159265
to 2 decimal places, so we get 3.14
.
print(round(12345, -1)) # 12340
print(round(12345, -2)) # 12300
print(round(12345, -3)) # 12000
^ we can also use round()
to round off to the nearest 10, 100, 1000 etc.
25) The walrus operator :=
n = 5
if n > 3:
print('n is more than 3')
^ a simple for loop
if (n := 5) > 3:
print('n is more than 3')
^ doing the exact same thing using the walrus operator :=
Here, the walrus operator does the same thing as our assignment operator =
. However, it does one additional thing — it also returns the value itself. We can thus use this operator to save one line of code.
Note — this works for Python 3.8+
26) We can pickle multiple objects into the same file
The built-in pickle
module allows us to save Python data structures or objects into binary files. We can save multiple objects per file.
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
import pickle
with open('test.pckl', 'wb') as f:
pickle.dump(a, f) # saving a into test.pckl (a is the 1st object)
pickle.dump(b, f) # saving b into test.pckl (b is the 2nd object)
pickle.dump(c, f) # saving c into test.pckl (c is the 3rd object)
# Unpickling (reading) them from test.pckl
import pickle
with open('test.pckl', 'rb') as f:
a = pickle.load(f) # a = [1,2,3]
b = pickle.load(f) # b = [4,5,6]
c = pickle.load(f) # c = [7,8,9]
27) The -O flag allows us to ignore assert statements
# this is run.py
assert 1==2
print('hello')
^ if we run this normally, we will get an AssertionError. This is because we try to assert that 1==2
, which evaluates to False.
We can forcefully ignore all assert statements by adding an -O
flag.
python -O run.py
28) dict.fromkeys() to create dictionaries
fruits = ['apple', 'orange', 'pear']
d = dict.fromkeys(fruits)
# d = {'apple':None, 'orange':None, 'pear':None}
^ we can quickly create dictionaries from lists using dict.fromkeys()
. Note that all values will be None
. If we want to define the default value, we can pass in another argument into dict.fromkeys()
.
fruits = ['apple', 'orange', 'pear']
d = dict.fromkeys(fruits, 100)
# d = {'apple':100, 'orange':100, 'pear':100}
29) frozensets
Frozensets are essentially immutable sets — sets that cannot be changed at all after they are created.
fs = frozenset({1, 2, 3})
Why we use frozensets:
- We can add frozensets into other sets (we can’t do that with sets)
- We can use frozensets as dictionary keys (we can’t do that with sets)
- Checking if something exists inside a frozenset still takes O(1) time
30) We can forces classes to accept only certain attributes using __slots__
class Dog:
__slots__ = ['name', 'age']
^ essentially defining that the Dog class can only accept the name
and age
attributes and nothing else.
dog = Dog()
dog.name = 'rocky' # no problem
dog.age = 5 # no problem
dog.breed = "german shepherd" # ERROR
We can set name
and age
for Dog, but not anything else.
Conclusion
Hope you learnt at least one thing about Python after reading this. See you next time, and this list will only get longer.
Some Final words
If this story provided value to you, and you wish to show support, you could:
- Clap multiple times for this story (this really helps me out!)
- Consider signing up for a Medium membership using my link — it’s $5 per month and you get to read unlimited stories on Medium.
Sign up using my link here to read unlimited Medium articles.
Get my free Ebooks: https://zlliu.co/books
I write Python articles (sometimes other stuff) that the younger me would have wanted to read. Do join my email list to get notified whenever I publish.
More content at PlainEnglish.io.
Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.
Interested in scaling your software startup? Check out Circuit.