All the cool Python 3 features that'll make you switch today!

Try it out
# super() no longer requires passing the class name and self

class A:
    def __init__(self, arg):
        self.arg = arg

class B(A):
    def __init__(self):
        super().__init__("hello")
        # in python 2 this would be:
        #super(B, self).__init__("hello)

b = B()
print(b.arg)  # hello
# underscores can be used to make numbers easier to read in code
big_number = 100_000_000_000

print(big_number)  # 100000000000
# easy string formatting
# no more '%{name}s' % {'name': name}
name = 'Alice'
print(f'My name is {name}.')  # prints "My name is Alice"
# add type annotations to variables, arguments and functions
# you can later access those programmatically

# you can also use mypy to check for type errors
# http://mypy-lang.org/

def my_add(a: int, b: int) -> int:
    return a + b

print(my_add(40, 2))  # 42
print(my_add.__annotations__)  # {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
# easy way to use iterators in your generator

def weird_range():
  yield from range(5)
  yield from range(5)
  
for i in weird_range():
  print(i)

# in python 2 you had to manually iterate over the iterator

def python2_weird_range():
  for i in range(5):
    yield i
  for i in range(5):
    yield i
# easily split lists or iterators into variables

a, b, *rest = range(10)
print(a)     # prints 0
print(b)     # prints 1
print(rest)  # prints [2, 3, 4, 5, 6, 7, 8, 9]

# or even

a, *rest, b = range(10)
print(rest)  # prints [1, 2, 3, 4, 5, 6, 7, 8]

# in python 2 it's a lot more verbose

l = list(range(10))
a = l[0]
rest = l[1:-1]
b = l[-1]
from contextlib import suppress

d = {1: 'one', 2: 'two'}
with suppress(KeyError):
  print(d[3])

### is way easier to read than:

try:
  print(d[3])
except KeyError:
  pass
# useful helpers for generating random and *secure* data of all types

import secrets

print(secrets.token_bytes(16))
print(secrets.token_hex(16))
print(secrets.token_urlsafe(16))
print(secrets.randbelow(10))
# easy path manipulation library
# no more os.path.join() and os.path.is* everywhere

from pathlib import Path

directory = Path("/etc")
filepath = directory / "test_file.txt"

if not filepath.exists():
    Path("/tmp/some/folder/somewhere").mkdir(parents=True)
# built-in Last Recently Used cache decorator
# useful for caching expensive function most-used results

from functools import lru_cache

counter = 0

@lru_cache(maxsize=2)
def get_something(i):
    global counter
    counter += 1
    return counter

print(get_something(0))  # 1
print(get_something(0))  # 1
print(get_something(1))  # 2
print(get_something(0))  # 1
print(get_something(2))  # 3
print(get_something(3))  # 4
print(get_something(4))  # 5
print(get_something(0))  # 6
# avoid accidental passing of keyword arguments

a, b, *rest = range(10)
def f(a, b, *args, option=True):
  print(option)

# in python 2 this would have passed option=False, but not in python 3
f(a, b, False)
# in both python 2 and 3 this explictly passes option=False
f(a, b, option=False)
# built-in ipaddress module that helps you easily deal with IP addresses
# check type, compare, check against subnets and more with both IPv4 and IPv6

from ipaddress import ip_address, ip_network

ip4 = ip_address('192.168.0.1')
print('packed', ip4.packed)
print('is private', ip4.is_private)

ip6 = ip_address('2001:db8::')
print('exploded', ip6.exploded)
print('is multicast', ip6.is_multicast)

net = ip_network('192.168.0.0/24')
print(f'{ip4} in {net} =', ip4 in net)
print(f'{ip6} in {net} =', ip6 in net)
# enumerations are fully supported
# you longer need to create your own conversion and comparison methods

from enum import Enum

class Color(Enum):
    red = 1
    green = 2
    blue = 3

print(Color.red.value)      # 1
print(Color(2))             # Color.green

print(Color.red == 1)       # False
print(Color.red.value == 1) # True

Got more? Submit a pull request.

Created by Amir Szekely. Inspired by Aaron Meurer's list.