1. Slice sequences
somelist [start:end]
- start: inclusive index
- end: exclusive index
- works with built-in types: list, tuple, str, bytes
- works with any class that defines __getitem__
For example:
- Basic example:
>> mylist = ['a','b','c','d','e','f','g','h']
>> print(mylist[:])
#Out: the whole list ['a','b','c','d','e','f','g','h']
>> print(mylist[-4:])
#Out: ['e','f','g','h']
>> print(mylist[3:-3])
#Out: ['d','e']
>> print(mylist[:-1])
#Out: ['a','b','c','d','e','f','g']
- Noticed: the slice is the deep copy, changing the slice won't affect the original one
>> mylist_slice = mylist[4:]
>> print(mylist_slice)
#Out: ['d','e','f','g','h']
>> #changing mylist_slice won't affect mylist
>> mylist_slice[2] = '99'
>>print(mylist_slice)
#Out: ['d','e','99','g','h']
>>print(mylist)
#Out: ['a','b','c','d','e','f','g','h']
>>#demonstrate slice is a deep copy: their addresses are different
mylist_copy = mylist[:]
print(id(mylist))
#Out: 4501936776
print(id(mylist_copy))
#Out: 4502919176
2. Avoid using start, end, and stride in a single slice
somelist[start:end:stride]
- start: inclusive index
- end: exclusive index
- stride: index interval
For example:
>> a = ["red","blue","yellow","pink","white","black"]
>> print(a[::4])
#Out: ['red', 'white']
>> print(a[1::4])
#Out: ['blue', 'black']
>> y = a[::-1]
>> y
#Out: ['black', 'white', 'pink', 'yellow', 'blue', 'red']
>> print(a[-2::-2])
#Out: ['white', 'yellow', 'red']
3. Prefer Enumerate over Range
range(start,stop[,step]):
- start: inclusive point, the begining point; default is 0
- stop: excusive point
- step: default is 1
enumerate(sequence, [start=0]): create tuples containing the index and list item using a list
- sequence
- start: the start of the index
Example 1:
from random import randint
random_bits = 0
for i in range(64):
if randint(0,1): #generate 0 or 1
#If 1 is generated, setting the corresponding bit with 1
random_bits |= 1 << i
print(bin(random_bits))
#Output: 0b1111111101010010010110000100111001000110100101100010000110101100
Example 2:
mylist = ['a','b','c','d']
for item in mylist:
print(item)
#Output:
#a
#b
#c
#d
for i in range(len(mylist)):
item = mylist[i]
print("%d: %s" %(i+1,item))
#Output:
#1: a
#2: b
#3: c
#4: d
print(list(enumerate(mylist)))
#Output:
#[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
for i,item in enumerate(mylist,start=1):
print("%d: %s" %(i,item))
#Output:
#1: a
#2: b
#3: c
#4: d
4. Use zip to process iterators in parallel
zip (*iterables):
- iterables: can be built-in iterables, like list,string,dict, or user-defined iterables (object that has __iter__ method)
zip_longest(*iterables):
- Similar with zip(), but will keep the longest items
For example:
- Example 1: basic example
names = ["paul",'william','bob']
letters = [len(n) for n in names]
print(letters)
#Output: [4, 7, 3]
#Find the longest name
longest_name = None
max_letters = 0
for (name,letter) in zip(names,letters):
if letter > max_letters:
longest_name,max_letters = name,letter
print(longest_name,max_letters)
#Output: william 7
print(list(zip(names,letters)))
#Output: [('paul', 4), ('william', 7), ('bob', 3)]
- Example 2: when different number of elements in iterables passed to zip(), zip_longest()
names = ["paul",'william','bob']
letters = [len(n) for n in names]
names.append("Rosalind")
for name,count in zip(names,letters):
print("%s has %d letters" %(name,count))
#Output
#paul has 4 letters
#william has 7 letters
#bob has 3 letters
from itertools import zip_longest
for name,count in zip_longest(names,letters):
if count is None:
print("%s has is of unknown length" % name)
else:
print("%s has %d letters" %(name,count))
#Output
#paul has 4 letters
#william has 7 letters
#bob has 3 letters
#Rosalind has is of unknown length
print(list(zip_longest(names,letters)))
#Output: [('paul', 4), ('william', 7), ('bob', 3), ('Rosalind', None)]
5. Avoid else blocks after for and while loops
- The "ELSE" block after the for/while loop will always run except that the loop is ended by a "break" out.
- If the loop is ended by a "break" out, then "else" block won't run
- If the loop is not ended by a "break" out, then "else" block will always run
It is confusing to use else block following the loop so that the author recommends us not use like that. The followings are some examples of "Else" block after the loop.
for i in range(3):
print("Loop %d" %i)
else:
print("Else block")
#Output:
#Loop 0
#Loop 1
#Loop 2
#Else block
for i in range(3):
print("Loop %d" %i)
if i==1:
break
else:
print("Else block")
#Output:
#Loop 0
#Loop 1
for i in []:
print("Never run")
else:
print("Else block")
#Output:
#Else block
while False:
print("never run")
else:
print("Else block")
#output:
#Else block
a = 5
b = 9
for i in range(2, min(a,b)+1):
print("testing",i)
if a % i == 0 and b % i == 0:
print("Not coprime")
break
else:
print("Coprime")
#Output:
#testing 2
#testing 3
#testing 4
#testing 5
#Coprime
- "ElSE" block after in try/execept
try:
x=5
except:
print("Caught exception")
else:
print("everything is fine")
#Output: everything is fine
ry:
raise Exception("Doh")
except:
print("Caught exception")
finally:
print("This always runs")
#output:
#Caught exception
#This always runs
6. Take advantage of each block in TRY/EXCEPT/ELSE/FINALLY
The template of try/except/else/finally: always remember the annotations below.
try:
#Do something
except MyException as e:
#Handle exception
else:
#Runs when there are no exceptions
finally:
#Always runs after try
Try to use "try, except,else and finally" together to make it clear what you are doing and what you are not doing at each step. To illustrate it, the author gives us a list of examples:
Example v1: open a file without any exception handles
handle = open("/tmp/hello.txt",encoding='utf-8')
handle.write("success\nand\nnew\nlines")
handle.close()
#Raise IOError if file doesn't exist
Example v2: compare two types of exception handles. Noticed: "else" block will run when there's no exception; "finally" block will always run successfully even there is an exception.
#v2 -1
handle = open("/tmp/hello.txt",encoding='utf-8') #May Raise IOError
try:
data = handle.read() #May Raise UnicodeDecodeError
finally:
handle.close() #will always run
#v3 -2
try:
handle = open("/tmp/hello.txt", encoding='utf-8') # Raise IOError
data = handle.read() #May Raise UnicodeDecodeError
finally:
handle.close()
#May also raise another exception if file doesn't exist,
#that will break the rule of "finally always runs"
Example v3: A complete example of try/except/else/finally
import json
UNDEFINED = object()
def divide_json(path):
handle = open(path,'r+') #IOError
try:
data = handle.read() #UnicodeDecodeError
op = json.loads(data) #ValueError
value = op['numerator'] / op['denominator'] #ZeroDivisionError
except ZeroDivisionError:
return UNDEFINED
else:
op['result'] = value
result = json.dumps(op)
handle.seek(0)
handle.write(result) #IOError
return value
finally:
handle.close() #Always runs
######## Test #################
temp_path = '/tmp/random_data.json'
with open(temp_path,'w') as handle:
handle.write('{"numerator":1,"denominator":10}')
print(divide_json(temp_path))
#Output: 0.1
with open(temp_path,'w') as handle:
handle.write('{"numerator":1,"denominator":0}')
print(divide_json(temp_path) is UNDEFINED)
#Output: True
with open(temp_path,'w') as handle:
handle.write('{"numerator":hello world}')
print(divide_json(temp_path))
#Output: Traceback ....
#Raise JSONDecodeError
7. Consider CONTEXLIB and with statements for reusable TRY/FINALLY behavior
7.1 with makes it possible to factor out simplified standard uses of try/finally statements,for example:
#Example: try/finally
f = open('file.txt', 'w')
try:
f.write("Hello")
finally:
f.close()
The previous example can be shortened by with statement like follows:
#Example: the previous example can be shortened by with statement
with open("file.text","w") as f:
f.write("Hello")
7.2 How does with statement works
In fact, with statement makes use of the context manager.
7.2.1 What is a context manager
A context manager is an object that defines the runtime context to be established when executing a with statement. The context manager handles the entry into, and the exist from, the desired runtime context for the execution of the block of code. Context managers are normally invoked using the with statement, but can also be used by directly invoking their methods.
A context manager is an object that implements __enter__() and __exit__() functions. These two functions defines the entry and exist of the context respectively.
7.2.2. How with statement works.
The following code is a general use format of with statement, that is the same as the second code block.
with EXPRESSION as VAR:
CODE BLOCK
contextManager = EXPRESSION
VAR = contextManager.__enter__()
try:
CODE BLOCK
finally:
contextManager.__exit__()
7.2.3. how to define a customized context manager.
Noticed that a context manger is a class that implements __enter__() and __exit__() methods. Let's take the File as an example:
class File:
def __init__(self,filename,mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print("Enter")
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, exc_type=None, exc_val=None, exc_tbs=None):
print("Exist")
self.f.close()
Now, the File is a context manger. And we can validate the theory given in the 7.2.2.
Firstly, let's run the following code:
with File('file.txt', 'w') as f:
print("Writing...")
f.write('Hello')
#Output:
#Enter
#Writing...
#Exist
Then, we can use try/finally to implement a similar function. This function will get the same output as the previous example, that demonstrate the theory of with statement.
contextManager = File('file.txt', 'w')
VAR = contextManager .__enter__()
try:
print("Writing...")
VAR.write('Hello')
finally:
contextManager.__exit__()
7.2.4. contextmanager decorator in Python
In python, a contextmanager decorator is given to help users define a factory function for with
statement context managers, without needing to create a class or separate __enter__()
and __exit__()
methods.
An abstract example is given in the following. In managed_resource(), the code ahead of finally corresponds to the __enter__(), the code in the finally corresponds to the __exit__() method.
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource)
>>> with managed_resource(timeout=3600) as resource:
# Resource is released at the end of this block,
# even if code in the block raises an exception