None
¶None
as a return type, raise
exceptions from helper functions. except
them from the calling functionNone
as a special meaning are error prone because None
and other values like 0
or the empty string evaluate to False as well#Bad
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
x, y = 0, 5
result = divide(x, y)
if result is None:
print('Invalid inputs')
#Better
def divide(a, b):
try:
return a / b
except ZeroDivisionError as e:
raise ValueError('Invalid inputs') from e
x, y = 5, 2
try:
result = divide(x, y)
except ValueError:
print('Invalid inputs')
else:
print('The result is %.1f' % result)
The result is 2.5
helper
function.def sort_priority(values, group):
def helper(x):
if x in group:
return (0, x)
return (1, x)
values.sort(key=helper)
When referencing a variable, the interpreter will traverse the scope in the following order
len
and str
)If no places have the defined variable NameError
exception is raised
def sort_priority2(numbers, group):
found = False # Scope: 'sort_priority'’
def helper(x):
if x in group:
found = True # Scope: 'helper' this is bad!
return (0, x)
return (1, x)
numbers.sort(key=helper)
return found
To fix this, use nonlocal
nonlocal
does not resolve up to the module leveldef sort_priority3(numbers, group):
found = False
def helper(x):
nonlocal found
if x in group:
found = True
return (0, x)
return (1, x)
numbers.sort(key=helper)
return found
nonlocal
in most cases outside simple functions like thisConsidering the following code that returns a list of indexes for the start of each word
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
words = 'This is some text to test'
result = index_words(words)
print(result[:])
[0, 5, 8, 13, 18, 21]
Problems:
Resolution:
yeild
expressionsnext
funtion the iterator will advance the generator to its next yield statementEx:
def index_words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
result = list(index_words_iter(words))
print(result)
[0, 5, 8, 13, 18, 21]
def normalize(numbers):
total = sum(numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
numbers
is iterated over multiple times. Note: iterators raise the StopIteration
when exhausted.numbers = list(numbers)
) and use that but this would again, only work for small inputsdef read_visits(data_path):
with open(data_path) as f:
for line in f:
yield int(line)
def normalize_func(get_iter):
total = sum(get_iter()) # New iterator
result = []
for value in get_iter(): # New iterator
percent = 100 * value / total
result.append(percent)
return result
path = "data/none.txt"
#percentages = normalize_func(lambda: read_visits(path))
__iter__
functionclass ReadVisits(object):
def __init__(self, data_path):
self.data_path = data_path
def __iter__(self):
with open(self.data_path) as f:
for line in f:
yield int(line)
def normalize_defensive(numbers):
if iter(numbers) is iter(numbers): # An iterator - bad!
raise TypeError('You have to supply a container!')
total = sum(numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
*args
for optional parameters to clear up noisedef log(message, values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print('%s: %s' % (message, values_str))
log('nums are', [1, 2])
log('yo', [])
nums are: 1, 2 yo
def log(message, *values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print('%s: %s' % (message, values_str))
log('nums are', [1, 2])
log('yo') #Nicer
nums are: [1, 2] yo
*
favorites = [7, 33, 99]
log('faves', *favorites)
faves: 7, 33, 99
Problems with accepting variable number of position arguments:
*args
to small inputs.def remainder(number, divisor):
return number % divisor
print(remainder(20, 7))
print(remainder(20, divisor=7))
print(remainder(number=20, divisor=7))
print(remainder(divisor=7, number=20))
6 6 6 6
remainder(number=20, 7)
File "<ipython-input-79-9265fd4030d2>", line 1 remainder(number=20, 7) ^ SyntaxError: positional argument follows keyword argument
remainder(20, number=7)
Keyword args provide several benefits:
remainder(20,7)
is not as clear as remainder(number=20,divisor=7)
)def remainder(number, divisor=1):
from datetime import datetime
from time import sleep
def log(message, when=datetime.now()):
print('%s: %s' % (when, message))
log('hi')
sleep(0.1)
log('hi')
def log(message, when=None):
"""Log a message with a timestamp.
Args:
message: Message to print.
when: datetime of when the message occurred.
Defaults to the present time.
"""
when = datetime.now() if when is None else when
print('%s: %s' % (when, message))
log('hi')
sleep(0.1)
log('hi')
import json
#Bad
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad')
foo['a'] = 5
bar = decode('another bad')
bar['b'] = 1
print('Bad results')
print('a: ', foo)
print('b: ', bar)
#Fixed
def decode(data, default=None):
"""Load JSON data from a string.
Args:
data: JSON data to decode.
default: Value to return if decoding fails.
Defaults to an empty dictionary.
"""
if default is None:
default = {}
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad')
foo['a'] = 5
bar = decode('another bad')
bar['b'] = 1
print('Good results')
print('a: ', foo)
print('b: ', bar)
*
symbol to indicate the end of positional argumentsdef safe_division_b(number, divisor,
ignore_overflow=False,
ignore_zero_division=False):
return 0
def safe_division_c(number, divisor, *, #Note the star
ignore_overflow=False,
ignore_zero_division=False):
return 0
print(safe_division_b(1,2,True,False))
0
print(safe_division_c(1,2,True,False))
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-81-d83d3f360b5b> in <module>() ----> 1 print(safe_division_c(1,2,True,False)) NameError: name 'safe_division_c' is not defined