Skip to content

Instantly share code, notes, and snippets.

@ssukhawani
Forked from csparpa/pythonicity-guide
Created July 22, 2021 18:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ssukhawani/bbeab6599c843ba3d4c81f9e16d1d593 to your computer and use it in GitHub Desktop.
Save ssukhawani/bbeab6599c843ba3d4c81f9e16d1d593 to your computer and use it in GitHub Desktop.
An extensive snippet-driven guide to writing idiomatic Python code
# ******************************
# * Quick guide to pythonicity *
# * by @csparpa *
# ******************************
# === ZEN ===
import this
# === HELP DOCSTRINGS ===
help(range)
# === PYTHONIC KEYWORDS ===
# The 'in' keyword. Used in for loops and to check if a value is contained into
# an iterable
3 in [4, 5, 6] # False
for c in "welcome":
print c
# The 'is' keyword is used to test for object identity
from datetime import datetime
a = datetime.today()
b = datetime.today()
b is a # Gives False, as a and b point to two distinct objects
c = a
c is a # Gives True, as both a and c point to the same object
# === TRUTHINESS ===
# Different objects have different truthiness values. I.e: an empty containers,
# the integer 0 and None are considered False.
def truthiness(x):
return 'True' if x else 'False'
truthiness(None) # False
truthiness("") # False
truthiness([]) # False
truthiness({}) # False
truthiness(()) # False
truthiness("abc") # True
truthiness([1, 5]) # True
truthiness(0) # False
truthiness(1) # True
# Check for nonethiness explicitly, using the 'is' keyword
r = 3
r is None # Gives False
r = None
r is None # Gives True
# === STRING FORMATTING ===
# Use the '%' syntax...
'%s, %d, %f, %e' % ('a', 2, 34.6, 34.6) # Gives: 'a, 2, 34.600000, 3.460000e+01'
# ...or the newer '{} syntax'
'{0}, {1}, {2}'.format('a', 'b', 'c') # Gives: 'a, b, c'
'{2}, {1}, {0}'.format('a', 'b', 'c') # Gives: 'c, b, a'
'{2}, {1}, {0}'.format(*'abc') # Gives: 'c, b, a'
'{0}{1}{0}'.format('abra', 'cad') # Gives: 'abracadabra'
'{one}{two}'.format(one=1, two=2) # Gives: '12'
'{0}{1}'.format(*('x','y')) # Gives: 'xy'
'r={n.real} j={n.imag}'.format(n=2+3j) # Gives: 'r=2.0 j=3.0'
'{0[0]}{0[1]}{0[2]}'.format(['a','b','c']) # Gives: 'abc'
# Strings that span on multiple lines can be assigned with triple quotes
s="""my
very
long
phrase"""
print s # Gives: 'my\nvery\nlong\nphrase'
# Break very long strings into multiple lines while protecting newline insertion
s = 'this \
really needs to \
stay on a single line'
print s # Gives: 'this really needs to stay on a single line'
# Raw strings don't acknowledge escape sequences
print 'C:\\folder' # Prints: C:\folder
print r'C:\\folder' # Prints: C:\\folder
# === UNPACKING ===
for index, item in enumerate(["a", "b", "c"]):
print "%d, %s" % (index, item)
# Use unpacking to swap contents of variables
a = 1
b = 2
a, b = b, a
# Function args unpacking: argument lists...
args = [3, 8]
range(*args)
# ... and dictionaries
def func(first, second=9, third=5):
print "first:%s, second:%s, third:%s" % (first, second, third)
func(1, **{'second': 'y', 'third': 'z'}) # Gives: first:1, second:y, third:z
func(**{'first': 'x', 'second': 'y', 'third': 'z'}) # Gives: first:x, second:y, third:z
# === SLICING ===
# Extract sublists. Remember the '-1' offset when giving indexes!
l = [1, 2, 3, 4, 5]
l[:] # Gives [1, 2, 3, 4, 5]
l[0:0] # Gives []
l[1:3] # Extracts items from index 1 to index 3-1=2, gives [2, 3]
l[:4] # Extracts items from index 0 up to index 4-1=3, gives [1, 2, 3, 4]
l[2:] # Extracts items from index 2 on, gives [3, 4, 5]
l[::2] # Extracts items with a step of 2 from index 0 on, gives [1, 3, 5]
l[2:4:2] # Extracts items with a step of 2 from index 2 to 4-1=3, gives [3]
# Also negative values can be provided to slicing: this means counting the item
# indexes from the end of the list on but backwards
l[::-1] # Reverses the list, gives [5, 4, 3, 2, 1]
l[-1] # Extracts the last element of the list, gives [5]
l[-4:-1] # Extracts items from index -4 to index -1-1=-2, gives [2, 3, 4]
# === COMPREHENSIONS ===
# List comprehension
even_numbers = [n for n in range(1, 11) if n % 2 == 0]
evenness = [str(n) + ' is even' if n % 2 == 0 else str(n) + ' is odd' for n in range(1, 11)]
# Dictionary comprehension
odd_numbers = {n:'odd' for n in range(1, 11) if n % 2 != 0}
oddness = {n:('odd' if n%2 != 0 else 'even') for n in range(1, 11)}
list_to_dict = {index: value for index, value in enumerate(['a', 'b', 'c'])}
# Set comprehension
even_numbers = { x for x in range(1, 11) if x % 2 == 0}
# === BUILTINS ===
# Range: generates a sequential list, which takes O(n) memory.
first_ten = range(1, 11)
even_numbers_under_ten = range(0, 10, 2)
first_ten_negatives = range(-1, -11, -1)
# XRange: returns a generator, which takes O(1) memory.
first_ten = xrange(1, 11)
word = 'welcometopython'
for i in first_ten:
print i
# All/any:
contains_empty_string = all(['hello', '', 'friends']) #False
contains_at_least_one_non_empty_string = any(['hello', '', 'friends']) #True
# Characters: chr, ord, unichr
chr(56) # Gives the 8-bit char with the specified ASCII code (gives '8')
ord('8') # Gives the Unicode code point for the specified 8-bit char (gives 56 again)
unichr(56) # Gives the Unicode char for the specified code point (gives u'8')
# String/Unicode representations
uc = unicode('abc^Y', 'utf-8') # Gives the Unicode representation for the specified
# object (gives u'abc\x19')
st = str(uc) # Gives the 8-bit string representation for the
# specified object
# Containers builders: dict, list, set, tuple
l=list()
l.append('a')
l.append('b')
l.append('a')
t=tuple(l)
s=set(t)
d=dict([(1, 'a'), (2, 'b')]
# Enumerate
for t in enumerate(['a', 'b', 'c', 'd'], 1):
print t
# Dir: lists attributes/names in the scope local to the specified object
dir() #Local scope names
dir(module_x) #Attributes in module_x's scope
# Filter: gives a list from those elements of iterable for which the filtering
# function returns True
f = lambda x: True if x % 2 != 0 else False #Extract odd numbers
filter(f, [1, 2, 3, 4, 5]) #Gives [1, 3, 5]
# Map: apply a function to every item of iterable and return the list of the results
f = lambda x: True if x % 2 != 0 else False #Extract odd numbers
map(f, [1, 2, 3, 4, 5]) #Gives [True, False, True, False, True]
# Reduce: apply a function of two args cumulatively to the items of iterable, from
# left to right, so as to reduce the iterable to a single value
reduce(lambda a, b: a*b, [1, 2, 3, 4, 5]) #Gives 120
# Isinstance: gives True if object's class is of type/subtype of the one declared
t=(1, 2, 3)
t_class = t.__class__
isinstance(t, t_class) #True
t=[1, 2, 3]
isinstance(t, t_class) #False
# Iter/Next: returns iterator of iterables and then retrieve the next element from it
i=iter([1, 2, 3, 4, 5])
i.next()
i.next()
# Locals: returns a dict with the local scope variables
def f(a, b):
print locals()
f(4,5) # Gives {'a': 4, 'b': 5}
# Len
len('helloworld') # Gives: 10
len(range(1, 100)) # Gives: 99
# Sorted: returns a list of sorted items from the input iterable
sorted(('mad', 'it\'s', 'world', 'a')) #Gives ['a', "it's", 'mad', 'world']
sorted([7, 2, -9, 0, 0, 2]) #Gives [-9, 0, 0, 2, 2, 7]
# Getattr/Setattr/Delattr/Hasattr
# Getattr performs a name-based lookup of objects attributes. Hasattr tells you
# if an object has the specified attribute. Setattr adds a named and valued
# attribute to the object or modifies one already existing with that name, while
# Delattr deletes an attribute from it
# Attribute addition/modification/deletion must be allowed by the objects you
# want to operate on!
import datetime
d = datetime.datetime(2010,3,4)
getattr(d, "month") # Gives: 3
getattr(d, "ctime") # Gives 'ctime', which is a function
hasattr(d, "month") # Gives: True
setattr(d, "foobar", 123) # Gives an error
class foobar():
pass
f = foobar()
setattr(f, "foobar", 123) # Gives: True
delattr(f, "foobar")
hasattr(f, "foobar") # Gives: False
# Type: gives the type of its argument
# Module 'types' has constants that you can use to compare types
type(5) # Gives: <type 'int'>
type('x') # Gives: <type 'str'>
type(len) # Gives: <type 'builtin_function_or_method'>
type(int) # Gives: <type 'type'> -> what??? See the "metaclasses" section
type('a') == types.StringType # Gives: True
type(len) == types.BuiltinMethodType # Gives: True
# Zip: gives a list of tuples, each one having as items the items at the same
# indexes of the sequences you passed in as arguments
zip([1, 4, 7],[2, 5, 8], [3, 6, 9]) # Gives [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
zip({'x': 1, 'y': 2},['a','b'], (9,8)) # Gives [('y', 'a', 9), ('x', 'b', 8)]
# === FILE I/O ===
# Specify a mode when you open a file: the deafault mode is 'r', use 'w' for
# writing text files, specify: 'rb', 'wb' for r/w binary files, use 'a' for
# appending.
# Use the 'with' keyword to ensure that file handle is closed also if errors
# are raised. If you don't, you'll need to explicitly call close() on the handle.
# Read a file: file-like objects are iterable
with open('file.txt') as f:
for line in f: # Alternatives are f.readline() and f.readlines()
print line
# Write a file
with open('file.txt', 'a') as f:
f.write('helloworld')
# === STRINGS ===
#Strings join
line = ' '.join(['99', 'bottles', 'of', 'beer', 'on', 'the', 'wall'])
# === GENERATORS ===
#Generator functions (aka "coroutines"): functions that can "save their work" and
#return execution control to their callers, expecting to regain it in the future.
#The keyword "yield" is used to freeze coroutines' execution state (inner vars)
#and execution resumes when next() is called
#Generator functions create "generator objects", which are special iterators and
#can therefore be used in for loops
def simple_generator(): #generator function
yield 1
yield 2
yield 3
sg = simple_generator()
next(sg)
next(sg)
next(sg)
for i in simple_generator(): #loops autoatically call 'next()' on the generator
print i
#Infinite generators: eg, we want to get all the even numbers >= x
def even_numbers_from(x):
while 1:
if x % 2 == 0:
yield x
x += 1
#Generator expressions
gen_exp = (n for n in range(1, 3))
for i in gen_exp:
print i
# === FUNCTION ARGUMENTS ===
#Positional args vs keyword args
def func1(a, b='x', c=None):
print "a:%s, b:%s, c:%s" % (a, b, c)
func1(1)
func1(1, 2)
func1(1, 2, 3)
#Arbitrary args list
def func2(a, b='x', *args):
print "a:%s, b:%s, c:%s" % (a, b, args)
func2(1)
func2(1, 2)
func2(1, 2, 'a')
func2(1, 2, 'a', 'b', 'c')
func2(1, 2, [1, 2, 3, 4], {'a': 1, 'b': 2})
#Arbitrary args list + arbitrary args dict
def func3(a, b='x', *args, **kwargs):
print "a:%s, b:%s, c:%s, d:%s" % (a, b, args, kwargs)
print 'ciccio'
func3(1)
func3(1, 2)
func3(1, 2, 'a')
func3(1, 2, [1, 2, 3, 4], {'a': 1, 'b': 2})
func3(1, 2, [1, 2, 3, 4], p={'a': 1, 'b': 2})
func3(1, 2, [1, 2, 3, 4], p=1, q=2)
# === LAMBDAS ===
#'lambda' is used to build anonymous function objects, good to be used only once
is_even = lambda x: True if x %2 == 0 else False
is_even(4)
is_even(5)
(lambda x: True if x%2!=0 else False)(5) #this tells you on the fly wether x is odd
# === CLASS PROPERTIES ===
#dinamically attach attributes to classes, specifying getter/setter/deleter handles
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
#Alternate way to implement properties: use decorators
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
# === FUNCTION CLOSURES ===
# In Python, everything is an object - functions are not alike. You have a
# function closure when inner functions defined in non-global scope remember
# what their enclosing namespaces looked like at definition time.
def outer():
x = 1
# 'inner' is defined each time that 'outer' is invoked
def inner():
print x # The value of 'x' is remembered and printed when inner is executed
return inner
f = outer() # 'f' contains the 'inner' function
f()
# === FUNCTION DECORATORS ===
# Without arguments
def decorator(func):
def decorated_function(n):
print 'I am the decorator'
return func(n)
return decorated_function
@decorator
def inner(n):
print 'I am the inner function'
print 'n is %d' % (n,)
inner(4) # Gives: I am the decorator
# I am the inner function
# n is 4
# With arguments: we need a function (decorator factory) that takes
# the arguments and builds the actual generator
def greet(formula='hello'): # decorator factory
def decorator(func): # decorator
def decorated(*args, **kwargs): # decorated function
print '%s' % (formula,)
return func(*args, **kwargs) # original function
return decorated
return decorator
@greet(formula='God save you, o')
def spell(name):
print '%s' % (name,)
spell('Queen') # Prints:
# God save you, o
# Queen
# === CLASS DECORATORS ===
def addAge(cls):
original_init = cls.__init__
def __init__(self, age, *args, **kws):
self.__age = age
original_init(self, *args, **kws)
def get_age(self):
return self.__age
cls.__init__ = __init__ # we replace the original __init__ with the "decorating" one
cls.get_age = get_age # we bind the additional function to the class
return cls
@addAge
class Person():
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
p = Person(32, 'Claudio') # pay attention to params order!
print p.get_name(), p.get_age()
# === METACLASSES ===
# In Python, every class (either built-in or user-defined) is of type 'type'.
# Metaclasses are "class-builders": they can be any callable that return a
# 'type'. If you define your own metaclass, you can build 'type' instances
# programmatically using the 'type(name, bases, dict)' builtin:
# E.g: this is a parent class
class MyParentType(object):
_x = 'parent'
# ... and this is the child class
class MyType(MyParentType):
_y = 'child'
def sum(a, b):
return a+b
# I want to be able to create the 'MyType' class also programmatically. I can do
# it like this:
mytype = type('MyType', # Name for the generated-class
(MyParentType,), # Base classes tuple
{'_y': 'child', 'sum': lambda self,a,b: a+b} #dict containing attributes and functions
)
# Now 'mytype' contains a fully-featured class definition and can be instantiated
print mytype # Gives: <class '__main__.MyType'>
obj = mytype()
obj._x # Gives: 'parent'
obj._y # Gives: 'child'
obj.sum(7,6) # Gives: 13
# The __metaclass__ hook allows to specify the callable that is used to build the
# class object when the 'class' statement is executed.
# If you don't specify any value for __metaclass__, then the package's __metaclass__
# is used - if even that one is undefined, Python looks into the __metaclass__ of
# parent classes up in the chain. If no matches are found, then 'type' is used.
def bind_increment_function(class_name, class_parents, class_attrs):
attrs = class_attrs.copy()
attrs.update({'_param': 'x'})
return type(class_name, class_parents, attrs)
class Number():
__metaclass__ = bind_increment_function # the metaclass callable
def __init__(self, n):
self._n = n
def add(self, number):
return self._n + number
o = Number(7)
o._n # Gives: 10
o._param # Gives: 'x'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment