-
Notifications
You must be signed in to change notification settings - Fork 0
/
function_tools.py
219 lines (163 loc) · 5.07 KB
/
function_tools.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# cached_property()
# transform a method of a class into a property whose value is computed once and then cached as a normal attribute
# similar to the built-in @property decorator, with the addition of caching
from functools import cached_property
import statistics
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@cached_property
def stdev(self):
return statistics.stdev(self._data)
@cached_property
def variance(self):
return statistics.variance(self._data)
dataset = DataSet([1, 2, 3, 4, 5, 6])
dataset.variance
dataset.stdev
# lru_cache()
# Least-Recently-Used Cache
# I/O bound functions that are periodically called with the same arguments
from functools import lru_cache
import requests
@lru_cache(maxsize=32)
def get_pep(number: int) -> str:
resource = f'http://www.python.org/dev/peps/pep-{number:04d}/'
print(resource)
try:
with requests.get(resource) as s:
return s.text
except requests.HTTPError:
return 'Not Found'
list_of_peps = [8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991]
# run twice
for n in list_of_peps:
pep = get_pep(n)
print(n, len(pep))
print(get_pep.cache_info())
# total_ordering()
# implement all comparison methods without declaration in class
from functools import total_ordering
@total_ordering
class Pythonista:
firstname: str
lastname: str
def __init__(self, firstname: str, lastname: str) -> None:
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other: object) -> bool:
if not isinstance(other, Pythonista):
return NotImplemented
return ((self.lastname.lower(), self.firstname.lower())
== (other.lastname.lower(), other.firstname.lower()))
def __lt__(self, other: object):
if not isinstance(other, Pythonista):
return NotImplemented
return ((self.lastname.lower(), self.firstname.lower())
< (other.lastname.lower(), other.firstname.lower()))
guido = Pythonista('Guido', 'van Rossum')
brett = Pythonista('Brett', 'Cannon')
print(guido > brett)
# partial()
# avoid full reimplementation for minimum changes
import math
from functools import partial
def euclidean_distance(point1: [int, int], point2: [int, int]) -> float:
x1, y1 = point1
x2, y2 = point2
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
euclidean_distance((2, 2), (4, 4))
zero_euclid = partial(euclidean_distance, (0, 0)) # origin distance
point = (1, 1)
print(zero_euclid(point))
# partialmethod()
# define methods without reimplementation
from functools import partialmethod
class Cell:
def __init__(self):
self._alive = False
@property
def alive(self):
return self._alive
def set_state(self, state):
self._alive = bool(state)
set_alive = partialmethod(set_state, True) # avoid def set_alive:
set_dead = partialmethod(set_state, False) # avoid def set_dead:
c = Cell()
c.set_alive()
print(c.alive)
# reduce()
from functools import reduce
def sum_many(*args):
s = 0
for i in args:
s += i
return s
iterable = [1, 2, 3, 4, 5]
sum_many(iterable) # error
result = reduce(sum_many, iterable)
print(result)
# singledispatch()
# transforms a function into a single-dispatch generic function
# a generic function is a function composed of multiple functions
# implementing the same operation for different types
from functools import singledispatch
@singledispatch
def mul(a, b):
if type(a) is not type(b):
return NotImplemented
return a * b
@mul.register
def strtype(a: str, b: str):
return a + b
print(mul(1, 2))
print(mul(1.5, 2.5))
print(mul('1', '2'))
print(mul(1, 1.0))
# singledispatchmethod()
# transforms methods
from functools import singledispatchmethod
class Negator:
@singledispatchmethod
def neg(self, arg):
raise NotImplementedError('Cannot negate!!')
@neg.register
def neg_int(self, arg: int):
return -arg
@neg.register
def neg_bool(self, arg: bool):
return not arg
neg = Negator()
print(neg.neg(5))
print(neg.neg(True))
print(neg.neg('Hello'))
# @wraps()
# using decorators hides input function metadata (replaced with decorator metadata)
# use wraps to copy over the metadata from function to decorator
def show_args(f):
'''docs decorator'''
def wrapper(*args, **kwargs):
print(f'Calling function {f.__name__} with {args} and {kwargs}')
return f(*args, **kwargs)
return wrapper
@show_args
def add(a: int, b: int) -> int:
'''Add two numbers a and b and return the result'''
return a + b
print(add(5, 1))
print(add.__doc__) # missing metadata
print(add.__name__)
from functools import wraps
def show_args(f):
@wraps(f)
def wrapper(*args, **kwargs):
print(f'Calling function {f.__name__} with {args} and {kwargs}')
return f(*args, **kwargs)
return wrapper
@show_args
def add(a: int, b: int) -> int:
'''Add two numbers a and b and return the result'''
return a + b
print(add(5, 1))
print(add.__doc__)
print(add.__name__)