Real Python PyTricks Archive

An archive of the PyTricks featured in Dan Badder's Real Python email newsletter.

Peeking Behind The Bytecode Curtain#

    
      # You can use Python's built-in "dis"
      # module to disassemble functions and
      # inspect their CPython VM bytecode:

      >>> def greet(name):
      ...     return 'Hello, ' + name + '!'

      >>> greet('Dan')
      'Hello, Dan!'

      >>> import dis
      >>> dis.dis(greet)
      2   0 LOAD_CONST     1 ('Hello, ')
          2 LOAD_FAST      0 (name)
          4 BINARY_ADD
          6 LOAD_CONST     2 ('!')
          8 BINARY_ADD
          10 RETURN_VALUE
    
  

Check if all elements in a list are equal#

    
      # Pythonic ways of checking if all
      # items in a list are equal:

      >>> lst = ['a', 'a', 'a']

      >>> len(set(lst)) == 1
      True

      >>> all(x == lst[0] for x in lst)
      True

      >>> lst.count(lst[0]) == len(lst)
      True

      # I ordered those from "most Pythonic" to "least Pythonic" 
      # and  "least efficient" to "most efficient". 
      # The len(set()) solution is idiomatic,  but constructing 
      # a set is less efficient memory and speed-wise.
    
  

Class inheritance and the issubclass() built-in#

    
      # You can check for class
      # inheritance relationships 
      # with the "issubclass()" built-in:

      >>> class BaseClass: pass
      >>> class SubClass(BaseClass): pass

      >>> issubclass(SubClass, BaseClass)
      True
      >>> issubclass(SubClass, object)
      True
      >>> issubclass(BaseClass, SubClass)
      False
    
  

contextlib.suppress#

    
      # In Python 3.4+ you can use
      # contextlib.suppress() to selectively
      # ignore specific exceptions:
      
      import contextlib
      
      with contextlib.suppress(FileNotFoundError):
          os.remove('somefile.tmp')
      
      # This is equivalent to:
      
      try:
          os.remove('somefile.tmp')
      except FileNotFoundError:
          pass
      
      # contextlib.suppress docstring: 
      #
      # "Return a context manager that suppresses any 
      #  of the specified exceptions if they occur in the body
      #  of a with statement and then resumes execution with 
      #  the first statement following the end of 
      #  the with statement."
    
  

CPython easter egg#

    
      # Here's a fun little CPython easter egg.
      # Just run the following in a Python 2.7+ 
      # interpreter session:
      
      >>> import antigravity
    
  

Emulate switch/case statements#

    
      # Because Python has first-class functions they can
      # be used to emulate switch/case statements
      
      def dispatch_if(operator, x, y):
          if operator == 'add':
              return x + y
          elif operator == 'sub':
              return x - y
          elif operator == 'mul':
              return x * y
          elif operator == 'div':
              return x / y
          else:
              return None
      
      
      def dispatch_dict(operator, x, y):
          return {
              'add': lambda: x + y,
              'sub': lambda: x - y,
              'mul': lambda: x * y,
              'div': lambda: x / y,
          }.get(operator, lambda: None)()
      
      
      >>> dispatch_if('mul', 2, 8)
      16
      
      >>> dispatch_dict('mul', 2, 8)
      16
      
      >>> dispatch_if('unknown', 2, 8)
      None
      
      >>> dispatch_dict('unknown', 2, 8)
      None
    
  

Python 3.3+ has a new "faulthandler" std lib module#

    
      # Python 3.3+ has a std
      # lib module for displaying
      # tracebacks even when Python
      # "dies", e.g with a segfault:
      
      import faulthandler
      faulthandler.enable()
      
      # Can also be enabled with
      # "python -X faulthandler"
      # from the command line.
      
      # Learn more here:
      # https://docs.python.org/3/library/faulthandler.html
    
  

Finding the most common elements in an iterable#

    
      # collections.Counter lets you find the most common
      # elements in an iterable:
      
      >>> import collections
      >>> c = collections.Counter('helloworld')
      
      >>> c
      Counter({'l': 3, 'o': 2, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
      
      >>> c.most_common(3)
      [('l', 3), ('o', 2), ('e', 1)]
    
  

"for" (and "while") loops can have an "else" branch?!#

    
      # Python's `for` and `while` loops
      # support an `else` clause that executes
      # only if the loops terminates without
      # hitting a `break` statement.
      
      def contains(haystack, needle):
          """
          Throw a ValueError if `needle` not
          in `haystack`.
          """
          for item in haystack:
              if item == needle:
                  break
          else:
              # The `else` here is a
              # "completion clause" that runs
              # only if the loop ran to completion
              # without hitting a `break` statement.
              raise ValueError('Needle not found')
      
      
      >>> contains([23, 'needle', 0xbadc0ffee], 'needle')
      None
      
      >>> contains([23, 42, 0xbadc0ffee], 'needle')
      ValueError: "Needle not found"
      
      
      # Personally, I'm not a fan of the `else`
      # "completion clause" in loops because
      # I find it confusing. I'd rather do
      # something like this:
      def better_contains(haystack, needle):
          for item in haystack:
              if item == needle:
                  return
          raise ValueError('Needle not found')
      
      # Note: Typically you'd write something
      # like this to do a membership test,
      # which is much more Pythonic:
      if needle not in haystack:
          raise ValueError('Needle not found')
    
  

Forced keyword-only parameters in Python 3.x#

    
      # In Python 3 you can use a bare "*" asterisk
      # in function parameter lists to force the
      # caller to use keyword arguments for certain
      # parameters:
      
      >>> def f(a, b, *, c='x', d='y', e='z'):
      ...     return 'Hello'
      
      # To pass the value for c, d, and e you 
      # will need to explicitly pass it as 
      # "key=value" named arguments:
      >>> f(1, 2, 'p', 'q', 'v')
      TypeError: 
      "f() takes 2 positional arguments but 5 were given"
      
      >>> f(1, 2, c='p', d='q',e='v')
      'Hello'
    
  

Function argument unpacking in Python#

    
      # Why Python Is Great:
      # Function argument unpacking
      
      def myfunc(x, y, z):
          print(x, y, z)
      
      tuple_vec = (1, 0, 1)
      dict_vec = {'x': 1, 'y': 0, 'z': 1}
      
      >>> myfunc(*tuple_vec)
      1, 0, 1
      
      >>> myfunc(**dict_vec)
      1, 0, 1
    
  

Python's built-in HTTP server#

    
      # Python has a HTTP server built into the
      # standard library. This is super handy for
      # previewing websites.
      
      # Python 3.x
      $ python3 -m http.server
      
      # Python 2.x
      $ python -m SimpleHTTPServer 8000
      
      # (This will serve the current directory at
      #  http://localhost:8000)
    
  

Python's shorthand for in-place value swapping#

    
      # Why Python Is Great:
      # In-place value swapping
      
      # Let's say we want to swap
      # the values of a and b...
      a = 23
      b = 42
      
      # The "classic" way to do it
      # with a temporary variable:
      tmp = a
      a = b
      b = tmp
      
      # Python also lets us
      # use this short-hand:
      a, b = b, a
    
  

Working with IP addresses in Python 3#

    
      # Python 3 has a std lib
      # module for working with
      # IP addresses:
      
      >>> import ipaddress
      
      >>> ipaddress.ip_address('192.168.1.2')
      IPv4Address('192.168.1.2')
      
      >>> ipaddress.ip_address('2001:af3::')
      IPv6Address('2001:af3::')
      
      # Learn more here:
      # https://docs.python.org/3/library/ipaddress.html
    
  

"is" vs "=="#

    
      # "is" vs "=="

      >>> a = [1, 2, 3]
      >>> b = a
      
      >>> a is b
      True
      >>> a == b
      True
      
      >>> c = list(a)
      
      >>> a == c
      True
      >>> a is c
      False
      
      # • "is" expressions evaluate to True if two 
      #   variables point to the same object
      
      # • "==" evaluates to True if the objects 
      #   referred to by the variables are equal
    
  

itertools.permutations()#

    

      # itertools.permutations() generates permutations
      # for an iterable. Time to brute-force those passwords ;-)
      
      >>> import itertools
      >>> for p in itertools.permutations('ABCD'):
      ...     print(p)
      
      ('A', 'B', 'C', 'D')
      ('A', 'B', 'D', 'C')
      ('A', 'C', 'B', 'D')
      ('A', 'C', 'D', 'B')
      ('A', 'D', 'B', 'C')
      ('A', 'D', 'C', 'B')
      ('B', 'A', 'C', 'D')
      ('B', 'A', 'D', 'C')
      ('B', 'C', 'A', 'D')
      ('B', 'C', 'D', 'A')
      ('B', 'D', 'A', 'C')
      ('B', 'D', 'C', 'A')
      ('C', 'A', 'B', 'D')
      ('C', 'A', 'D', 'B')
      ('C', 'B', 'A', 'D')
      ('C', 'B', 'D', 'A')
      ('C', 'D', 'A', 'B')
      ('C', 'D', 'B', 'A')
      ('D', 'A', 'B', 'C')
      ('D', 'A', 'C', 'B')
      ('D', 'B', 'A', 'C')
      ('D', 'B', 'C', 'A')
      ('D', 'C', 'A', 'B')
      ('D', 'C', 'B', 'A')
    
  

Lambda Functions#

    
      # The lambda keyword in Python provides a
      # shortcut for declaring small and 
      # anonymous functions:
      
      >>> add = lambda x, y: x + y
      >>> add(5, 3)
      8
      
      # You could declare the same add() 
      # function with the def keyword:
      
      >>> def add(x, y):
      ...     return x + y
      >>> add(5, 3)
      8
      
      # So what's the big fuss about?
      # Lambdas are *function expressions*:
      >>> (lambda x, y: x + y)(5, 3)
      8
      
      # • Lambda functions are single-expression 
      # functions that are not necessarily bound
      # to a name (they can be anonymous).
      
      # • Lambda functions can't use regular 
      # Python statements and always include an
      # implicit `return` statement.
    
  

Python's list comprehensions are awesome#

    
      # Python's list comprehensions are awesome.

      vals = [expression 
              for value in collection 
              if condition]
      
      # This is equivalent to:
      
      vals = []
      for value in collection:
          if condition:
              vals.append(expression)
      
      # Example:
      
      >>> even_squares = [x * x for x in range(10) if not x % 2]
      >>> even_squares
      [0, 4, 16, 36, 64]
    
  

Python list slice syntax fun#

    
      # Python's list slice syntax can be used without indices
      # for a few fun and useful things:
      
      # You can clear all elements from a list:
      >>> lst = [1, 2, 3, 4, 5]
      >>> del lst[:]
      >>> lst
      []
      
      # You can replace all elements of a list
      # without creating a new list object:
      >>> a = lst
      >>> lst[:] = [7, 8, 9]
      >>> lst
      [7, 8, 9]
      >>> a
      [7, 8, 9]
      >>> a is lst
      True
      
      # You can also create a (shallow) copy of a list:
      >>> b = lst[:]
      >>> b
      [7, 8, 9]
      >>> b is lst
      False
    
  

@classmethod vs @staticmethod vs "plain" methods#

    
      # @classmethod vs @staticmethod vs "plain" methods
      # What's the difference?
      
      class MyClass:
          def method(self):
              """
              Instance methods need a class instance and
              can access the instance through `self`.
              """
              return 'instance method called', self
      
          @classmethod
          def classmethod(cls):
              """
              Class methods don't need a class instance.
              They can't access the instance (self) but
              they have access to the class itself via `cls`.
              """
              return 'class method called', cls
      
          @staticmethod
          def staticmethod():
              """
              Static methods don't have access to `cls` or `self`.
              They work like regular functions but belong to
              the class's namespace.
              """
              return 'static method called'
      
      # All methods types can be
      # called on a class instance:
      >>> obj = MyClass()
      >>> obj.method()
      ('instance method called', )
      >>> obj.classmethod()
      ('class method called', )
      >>> obj.staticmethod()
      'static method called'
      
      # Calling instance methods fails
      # if we only have the class object:
      >>> MyClass.classmethod()
      ('class method called', )
      >>> MyClass.staticmethod()
      'static method called'
      >>> MyClass.method()
      TypeError: 
          "unbound method method() must be called with MyClass "
          "instance as first argument (got nothing instead)"
    
  

Multiple sets of kwargs in Python 3.5+#

    
      # Python 3.5+ allows passing multiple sets
      # of keyword arguments ("kwargs") to a
      # function within a single call, using
      # the "**" syntax:
      
      >>> def process_data(a, b, c, d):
      >>>    print(a, b, c, d)
      
      >>> x = {'a': 1, 'b': 2}
      >>> y = {'c': 3, 'd': 4}
      
      >>> process_data(**x, **y)
      1 2 3 4
      
      >>> process_data(**x, c=23, d=42)
      1 2 23 42
    
  

Python's namedtuples can be a great alternative to defining a class manually#

    
      # Why Python is Great: Namedtuples
      # Using namedtuple is way shorter than
      # defining a class manually:
      >>> from collections import namedtuple
      >>> Car = namedtuple('Car', 'color mileage')
      
      # Our new "Car" class works as expected:
      >>> my_car = Car('red', 3812.4)
      >>> my_car.color
      'red'
      >>> my_car.mileage
      3812.4
      
      # We get a nice string repr for free:
      >>> my_car
      Car(color='red' , mileage=3812.4)
      
      # Like tuples, namedtuples are immutable:
      >>> my_car.color = 'blue'
      AttributeError: "can't set attribute"
    
  

You can use "json.dumps()" to pretty-print Python dicts#

    
      # The standard string repr for dicts is hard to read:
      >>> my_mapping = {'a': 23, 'b': 42, 'c': 0xc0ffee}
      >>> my_mapping
      {'b': 42, 'c': 12648430. 'a': 23}  # 😞
      
      # The "json" module can do a much better job:
      >>> import json
      >>> print(json.dumps(my_mapping, indent=4, sort_keys=True))
      {
          "a": 23,
          "b": 42,
          "c": 12648430
      }
      
      # Note this only works with dicts containing
      # primitive types (check out the "pprint" module):
      >>> json.dumps({all: 'yup'})
      TypeError: keys must be a string
    
  

The get() method on Python dicts and its "default" arg#

    
      # The get() method on dicts
      # and its "default" argument
      
      name_for_userid = {
          382: "Alice",
          590: "Bob",
          951: "Dilbert",
      }
      
      def greeting(userid):
          return "Hi %s!" % name_for_userid.get(userid, "there")
      
      >>> greeting(382)
      "Hi Alice!"
      
      >>> greeting(333333)
      "Hi there!"
    
  

When To Use __repr__ vs __str__?#

    
      # When To Use __repr__ vs __str__?
      # Emulate what the std lib does:
      >>> import datetime
      >>> today = datetime.date.today()
      
      # Result of __str__ should be readable:
      >>> str(today)
      '2017-02-02'
      
      # Result of __repr__ should be unambiguous:
      >>> repr(today)
      'datetime.date(2017, 2, 2)'
      
      # Python interpreter sessions use 
      # __repr__ to inspect objects:
      >>> today
      datetime.date(2017, 2, 2)
    
  

How to sort a Python dict by value#

    
      # How to sort a Python dict by value
      # (== get a representation sorted by value)
      
      >>> xs = {'a': 4, 'b': 3, 'c': 2, 'd': 1}
      
      >>> sorted(xs.items(), key=lambda x: x[1])
      [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
      
      # Or:
      
      >>> import operator
      >>> sorted(xs.items(), key=operator.itemgetter(1))
      [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
    
  

Different ways to test multiple flags at once in Python#

    
      # Different ways to test multiple
      # flags at once in Python
      x, y, z = 0, 1, 0
      
      if x == 1 or y == 1 or z == 1:
          print('passed')
      
      if 1 in (x, y, z):
          print('passed')
      
      # These only test for truthiness:
      if x or y or z:
          print('passed')
      
      if any((x, y, z)):
          print('passed')
    
  

Measure the execution time of small bits of Python code with the "timeit" module#

    
      # The "timeit" module lets you measure the execution
      # time of small bits of Python code
      
      >>> import timeit
      >>> timeit.timeit('"-".join(str(n) for n in range(100))',
                        number=10000)
      
      0.3412662749997253
      
      >>> timeit.timeit('"-".join([str(n) for n in range(100)])',
                        number=10000)
      
      0.2996307989997149
      
      >>> timeit.timeit('"-".join(map(str, range(100)))',
                        number=10000)
      
      0.24581470699922647
    
  

Python 3.5+ type annotations#

    
      # Python 3.5+ supports 'type annotations' that can be
      # used with tools like Mypy to write statically typed Python:
      
      def my_add(a: int, b: int) -> int:
          return a + b
    
  

Python 3 allows unicode variable names#

    
      # Python 3 allows unicode
      # variable names:
      
      >>> π = math.pi
      >>> class Spin̈alTap: pass
      >>> Spin̈alTap()
      
      
      # Only letter-like characters
      # work, however:
      
      >>> 🍺 = "beer"
      SyntaxError:
      "invalid character in identifier"
    
  

Avoid version conflicts with Virtual Environments#

    
      # Virtual Environments ("virtualenvs") keep
      # your project dependencies separated.
      # They help you avoid version conflicts
      # between packages and different versions
      # of the Python runtime.
      
      # Before creating & activating a virtualenv:
      # `python` and `pip` map to the system
      # version of the Python interpreter
      # (e.g. Python 2.7)
      $ which python
      /usr/local/bin/python
      
      # Let's create a fresh virtualenv using
      # another version of Python (Python 3):
      $ python3 -m venv ./venv
      
      # A virtualenv is just a "Python
      # environment in a folder":
      $ ls ./venv
      bin      include    lib      pyvenv.cfg
      
      # Activating a virtualenv configures the
      # current shell session to use the python
      # (and pip) commands from the virtualenv
      # folder instead of the global environment:
      $ source ./venv/bin/activate
      
      # Note how activating a virtualenv modifies
      # your shell prompt with a little note
      # showing the name of the virtualenv folder:
      (venv) $ echo "wee!"
      
      # With an active virtualenv, the `python`
      # command maps to the interpreter binary
      # *inside the active virtualenv*:
      (venv) $ which python
      /Users/dan/my-project/venv/bin/python3
      
      # Installing new libraries and frameworks
      # with `pip` now installs them *into the
      # virtualenv sandbox*, leaving your global
      # environment (and any other virtualenvs)
      # completely unmodified:
      (venv) $ pip install requests
      
      # To get back to the global Python
      # environment, run the following command:
      (venv) $ deactivate
      
      # (See how the prompt changed back
      # to "normal" again?)
      $ echo "yay!"
      
      # Deactivating the virtualenv flipped the
      # `python` and `pip` commands back to
      # the global environment:
      $ which python
      /usr/local/bin/python
    
  

Contribute! • Privacy Policy • Terms of Service

My work on this project is released under the Creative Commons Attribution 4.0 International Public License. The PyTricks are copyright Dan Badder of Real Python.

Built With:

Bunny Fonts Logo GitHub Logo