avatarLiu Zuo Lin

Summary

The website content provides a compilation of 30 lesser-known Python programming language features and techniques, offering insights and practical examples for developers, even those who have been using Python since 2017.

Abstract

The article titled "30 Things I Never Knew About Python Until Recently (Compilation)" presents a curated list of Python features that may not be widely known, despite the author's experience with Python since 2017. These features range from the use of mapping proxies for immutable dictionaries to the implementation of private variables in classes, which are not truly private. The article covers a variety of topics, including built-in functions like any(), all(), and divmod(), as well as string formatting techniques for easy inspection of variables. It also delves into advanced concepts such as using classes as decorators, the walrus operator :=, and the use of frozensets for immutable sets. The compilation serves as a resource for Python developers looking to expand their knowledge and includes tips on dictionary creation, printing colored output, and ignoring assert statements with the -O flag. The author encourages readers to engage with the content by joining an email list for updates and offers free e-books related to Python programming.

Opinions

  • The author believes that even experienced Python developers can learn new aspects of the language, emphasizing the depth and versatility of Python.
  • There is an underlying appreciation for Python's built-in functions and features that can simplify coding tasks and improve code readability.
  • The article suggests that the Python community can benefit from sharing such insights, as it helps in uncovering the full potential of the language.
  • The author promotes the idea of continuous learning in the field of programming and encourages readers to explore Python beyond the basics.
  • By providing practical examples and encouraging the use of tools like pprint and colorama, the author shows a preference for code clarity and aesthetics.
  • The inclusion of a call-to-action for subscribing to a newsletter and accessing free e-books indicates the author's commitment to community building and education.

30 Things I Never Knew About Python Until Recently (Compilation)

# Despite Learning Python Since 2017

cool art

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’ name
  • bases is a tuple containing the class’ parent classes
  • dict 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:

  1. Clap multiple times for this story (this really helps me out!)
  2. 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.

Python
Python Programming
Recommended from ReadMedium