-
Notifications
You must be signed in to change notification settings - Fork 222
/
item_19_provide_optimal_behavior.py
174 lines (113 loc) · 5.61 KB
/
item_19_provide_optimal_behavior.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# Item 19: Provide optimal behavior with keyword arguments
# Like most other programming languages, calling a function in Python allows
# for passing arguments by position.
def remainder(number, divisor):
return number % divisor
assert remainder(20, 7) == 6
# All positional arguments to Python functions can also be passed by keyword,
# where the name of the argument is used in an assignment within the
# parentheses of a function call. The keyword arguments can be passed in any
# order as long as all of the required positional arguments are specified.
# You can mix and match keyword and positional arguments. There calls are
# equivalent.
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
# Positional arguments must be specified before keyword arguments.
# remainder(number=20, 7)
# line 36
# remainder(number=20, 7)
# SyntaxError: non-keyword arg after keyword arg
# Each argument can only be specified noce.
# remainder(20, number=7)
# line 45, in <module>
# remainder(20, number=7)
# TypeError: remainder() got multiple values for keyword argument 'number'
# The flexibility of keyword arguments provides three significant benefits.
# The first advantage is that keyword arguments make the function call clearer
# to new reader of the code. With the call remainder(20, 7), it's not evident
# looking at the implementation of the remainder method. In the call with
# keyword arguments, number=20 and divisor=7 make it immediately obvious which
# parameter is being used for each purpose.
# The second impact of arguments is that they can have default values
# specified in the function definition. This allows a function to provide
# additional capabilities when you need them but lets you accept the default
# behavior most of the time. This can eliminate repetitive code and reduce
# noise.
# For example, say you want to compute the rate of fluid flowing into a vat.
# If the vat is also on a scale, then you could use the difference between two
# weight measurements at two different times to determine the flow rate.
def flow_rate(weight_diff, time_diff):
return weight_diff / time_diff
weight_diff = 0.5
time_diff = 3
flow = flow_rate(weight_diff, time_diff)
print('%.3f kg per second' % flow)
# 0.167 kg per second
# In the typical case, it's useful to know the flow rate in kilograms per
# second. Other times, it'd be helpful to use the last sensor measurements
# to larger time scales, like hours or days. You can provide this behavior
# in the same function by adding an argument for the time period scaling
# factor.
def flow_rate(weight_diff, time_diff, period):
return (weight_diff / time_diff) * period
# The problem is that now you need to specify the period argument every time
# you call the function, even in the common case of flow rate per second (
# where the period is 1).
flow_per_second = flow_rate(weight_diff, time_diff, 1)
# To make this less noisy, I can give the period arguments a default value.
def flow_rate(weight_diff, time_diff, period=1):
return (weight_diff / time_diff) * period
# The period argument is now optional.
flow_per_second = flow_rate(weight_diff, time_diff)
flow_per_hour = flow_rate(weight_diff, time_diff, period=3600)
print(flow_per_second)
print(flow_per_hour)
# 0.166666666667
# 600.0
# This works well for simple default values (it gets tricky for complex
# default values--see Item 20: "Use None and Docstrings to specify dynamic
# default arguments").
# The third reason to use keyword arguments is that they provide a powerful
# way to extend a function's parameters while remaining backwards compatible
# with existing callers. This lets you provide additional functionality
# without having to migrate a lot of code, reducing the chance of introducing
# bugs.
def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1):
return ((weight_diff / units_per_kg) / time_diff) * period
# The default argument value for units_per_kg is 1, which makes the return
# weight units remain as kilograms. This means that all existing callers will
# see no change in behavior. New callers to flow_rate can specify the new
# keyword argument to see the new behavior.
pounds_per_hour = flow_rate(
weight_diff, time_diff, period=3600, units_per_kg=2.2)
print(pounds_per_hour)
# 272.727272727
# The only problem with this approach is that optional keyword arguments like
# period and units_per_kg may still be specified as positional arguments.
pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2)
print(pounds_per_hour)
# 272.727272727
# Supplying optional arguments positionally can be confusing because it isn't
# clear that the values 3600 and 2.2 correspond to. The best practice is to
# always specify optional arguments using the keyword names and never pass
# them as positional arguments.
# Note:
# Backwards compatibility using optional keyword arguments like this is
# crucial for functions that accept *args (see Item 18: "Reduce visual noise
# with variable positional arguments"). But a even better practice is to use
# keyword-only arguments (see Item 21: "Enforce clarity with keyword-only
# arguments").
# Things to remember
# 1. Function arguments can be specified by position or by keyword.
# 2. Keywords make it clear what the purpose of each arguments is when it
# would be confusing with only positional arguments.
# 3. Keywords arguments with default values make it easy to add new behaviors
# to a function, especially when the function has existing callers.
# 4. Optional keyword arguments should always be passed by keyword instead of
# by position.