Skip to content

Commit c3ec8d8

Browse files
committed
fixes #75
1 parent efff532 commit c3ec8d8

16 files changed

+278
-293
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Release notes
22

33
<!-- do not remove -->
4+
45
## 1.0.2
56

67
### Bugs Squashed

README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The documentation also contains links to any related functions or classes, which
5757

5858
### Testing
5959

60-
fastcore's testing module is designed to work well with [nbdev](https://nbdev.fast.ai), which is a full literate programming environment built on Jupyter Notebooks. That means that your tests, docs, and code all live together in the same notebook. fastcore and nbdev's approach to testing starts with the premise that your all your tests should pass. If one fails, no more tests in a notebook are run.
60+
fastcore's testing module is designed to work well with [nbdev](https://nbdev.fast.ai), which is a full literate programming environment built on Jupyter Notebooks. That means that your tests, docs, and code all live together in the same notebook. fastcore and nbdev's approach to testing starts with the premise that all your tests should pass. If one fails, no more tests in a notebook are run.
6161

6262
Tests look like this:
6363

@@ -181,9 +181,8 @@ Looking at that `ProductPage` example, it's rather verbose and duplicates a lot
181181

182182
```python
183183
class ProductPage:
184-
store_attrs = 'author,price,cost'
185184
def __init__(self,author,price,cost): store_attr()
186-
__repr__ = basic_repr(store_attrs)
185+
__repr__ = basic_repr('author,price,cost')
187186

188187
ProductPage("Jeremy", 1.50, 0.50)
189188
```
@@ -244,7 +243,7 @@ p
244243

245244

246245

247-
(#20) [10,7,9,17,18,4,16,15,19,14...]
246+
(#20) [17,10,7,18,12,13,5,16,15,19...]
248247

249248

250249

@@ -257,7 +256,7 @@ p[2,4,6]
257256

258257

259258

260-
(#3) [9,18,16]
259+
(#3) [7,12,5]
261260

262261

263262

@@ -270,7 +269,7 @@ p.argwhere(ge(15))
270269

271270

272271

273-
(#5) [3,4,6,7,8]
272+
(#5) [0,3,7,8,9]
274273

275274

276275

@@ -295,11 +294,11 @@ Most Python programmers use object oriented methods and inheritance to allow dif
295294

296295
```python
297296
@typedispatch
298-
def f_td_test(x:numbers.Integral, y): return x+1
297+
def _f(x:numbers.Integral, y): return x+1
299298
@typedispatch
300-
def f_td_test(x:int, y:float): return x+y
299+
def _f(x:int, y:float): return x+y
301300

302-
f_td_test(3,2.0), f_td_test(3,2)
301+
_f(3,2.0), _f(3,2)
303302
```
304303

305304

fastcore/_nbdev.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"arg3": "01_foundation.ipynb",
4242
"arg4": "01_foundation.ipynb",
4343
"coll_repr": "01_foundation.ipynb",
44+
"is_bool": "01_foundation.ipynb",
4445
"mask2idxs": "01_foundation.ipynb",
4546
"cycle": "01_foundation.ipynb",
4647
"zip_cycle": "01_foundation.ipynb",
@@ -130,11 +131,6 @@
130131
"parallel_chunks": "02_utils.ipynb",
131132
"run_procs": "02_utils.ipynb",
132133
"parallel_gen": "02_utils.ipynb",
133-
"ipython_shell": "02_utils.ipynb",
134-
"in_ipython": "02_utils.ipynb",
135-
"in_colab": "02_utils.ipynb",
136-
"in_jupyter": "02_utils.ipynb",
137-
"in_notebook": "02_utils.ipynb",
138134
"type_hints": "03_dispatch.ipynb",
139135
"anno_ret": "03_dispatch.ipynb",
140136
"cmp_instance": "03_dispatch.ipynb",

fastcore/dispatch.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .imports import *
88
from .foundation import *
99
from .utils import *
10+
from collections import defaultdict
1011

1112
# Cell
1213
def type_hints(f):
@@ -163,7 +164,7 @@ def default_set_meta(self, x, copy_meta=False):
163164
def cast(x, typ):
164165
"cast `x` to type `typ` (may also change `x` inplace)"
165166
res = typ._before_cast(x) if hasattr(typ, '_before_cast') else x
166-
if isinstance(res, ndarray): res = res.view(typ)
167+
if isinstance_str(res, 'ndarray'): res = res.view(typ)
167168
elif hasattr(res, 'as_subclass'): res = res.as_subclass(typ)
168169
else:
169170
try: res.__class__ = typ

fastcore/foundation.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
__all__ = ['defaults', 'FixSigMeta', 'PrePostInitMeta', 'NewChkMeta', 'BypassNewMeta', 'copy_func', 'patch_to', 'patch',
44
'patch_property', 'use_kwargs_dict', 'use_kwargs', 'delegates', 'method', 'funcs_kwargs', 'add_docs', 'docs',
5-
'custom_dir', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'coll_repr', 'mask2idxs', 'cycle', 'zip_cycle',
6-
'is_indexer', 'negate_func', 'GetAttr', 'delegate_attr', 'bind', 'listable_types', 'CollBase', 'L']
5+
'custom_dir', 'arg0', 'arg1', 'arg2', 'arg3', 'arg4', 'coll_repr', 'is_bool', 'mask2idxs', 'cycle',
6+
'zip_cycle', 'is_indexer', 'negate_func', 'GetAttr', 'delegate_attr', 'bind', 'listable_types', 'CollBase',
7+
'L']
78

89
# Cell
910
from .imports import *
11+
from contextlib import contextmanager
12+
from copy import copy
13+
import random
1014

1115
# Cell
1216
defaults = SimpleNamespace()
@@ -216,6 +220,11 @@ def coll_repr(c, max_n=10):
216220
return f'(#{len(c)}) [' + ','.join(itertools.islice(map(repr,c), max_n)) + (
217221
'...' if len(c)>10 else '') + ']'
218222

223+
# Cell
224+
def is_bool(x):
225+
"Check whether `x` is a bool or None"
226+
return isinstance(x,(bool,NoneType)) or isinstance_str(x, 'bool_')
227+
219228
# Cell
220229
def mask2idxs(mask):
221230
"Convert bool mask or index list to index `L`"
@@ -224,7 +233,7 @@ def mask2idxs(mask):
224233
if len(mask)==0: return []
225234
it = mask[0]
226235
if hasattr(it,'item'): it = it.item()
227-
if isinstance(it,(bool,NoneType,np.bool_)): return [i for i,m in enumerate(mask) if m]
236+
if is_bool(it): return [i for i,m in enumerate(mask) if m]
228237
return [int(i) for i in mask]
229238

230239
# Cell
@@ -349,11 +358,15 @@ def __setitem__(self, idx, o):
349358
if not is_iter(o): o = [o]*len(idx)
350359
for i,o_ in zip(idx,o): self.items[i] = o_
351360

361+
def __eq__(self,b):
362+
if isinstance_str(b, 'ndarray'): return array_equal(b, self)
363+
if isinstance(b, (str,dict)): return False
364+
return all_equal(b,self)
365+
352366
def __iter__(self): return iter(self.items.itertuples() if hasattr(self.items,'iloc') else self.items)
353367
def __contains__(self,b): return b in self.items
354368
def __reversed__(self): return self._new(reversed(self.items))
355369
def __invert__(self): return self._new(not i for i in self)
356-
def __eq__(self,b): return False if isinstance(b, (str,dict,set)) else all_equal(b,self)
357370
def __repr__(self): return repr(self.items) if _is_array(self.items) else coll_repr(self)
358371
def __mul__ (a,b): return a._new(a.items*b)
359372
def __add__ (a,b): return a._new(a.items+_listify(b))
@@ -443,7 +456,7 @@ def product(self): return self.reduce(operator.mul)
443456
map_dict="Like `map`, but creates a dict from `items` to function results",
444457
starmap="Like `map`, but use `itertools.starmap`",
445458
itemgot="Create new `L` with item `idx` of all `items`",
446-
attrgot="Create new `L` with attr `k` of all `items`, if `items` contains dicts, then `L` will contain corresponding values for key `k` for each dict.",
459+
attrgot="Create new `L` with attr `k` (or value `k` for dicts) of all `items`.",
447460
cycle="Same as `itertools.cycle`",
448461
enumerate="Same as `enumerate`",
449462
zip="Create new `L` with `zip(*items)`",

fastcore/imports.py

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,18 @@
1-
import numpy as np
2-
import io,operator,sys,os,re,mimetypes,itertools,shutil,pickle,tempfile,subprocess
3-
import itertools,random,inspect,functools,math,bz2,typing,numbers,warnings,threading
4-
import json,urllib.request
1+
import sys,os,re,shutil,typing,itertools,operator,functools,math,warnings,functools,inspect,io
52

3+
from operator import itemgetter,attrgetter
64
from warnings import warn
7-
from dataclasses import dataclass
5+
from typing import Iterable,Generator,Sequence,Iterator
86
from functools import partial,reduce
9-
from threading import Thread
10-
from time import sleep
11-
from copy import copy
12-
from contextlib import redirect_stdout,contextmanager
13-
from collections.abc import Iterable,Iterator,Generator,Collection,Sequence
14-
from types import SimpleNamespace
157
from pathlib import Path
16-
from collections import defaultdict,Counter
17-
from operator import itemgetter,attrgetter
18-
from uuid import uuid4
19-
from urllib.request import HTTPError
20-
21-
# External modules
22-
from numpy import array,ndarray
23-
from pdb import set_trace
24-
25-
#Optional modules
26-
try: import matplotlib.pyplot as plt
27-
except: pass
288

299
try:
3010
from types import WrapperDescriptorType,MethodWrapperType,MethodDescriptorType
3111
except ImportError:
3212
WrapperDescriptorType = type(object.__init__)
3313
MethodWrapperType = type(object().__str__)
3414
MethodDescriptorType = type(str.join)
35-
from types import BuiltinFunctionType,BuiltinMethodType,MethodType,FunctionType
15+
from types import BuiltinFunctionType,BuiltinMethodType,MethodType,FunctionType,SimpleNamespace
3616

3717
NoneType = type(None)
3818
string_classes = (str,bytes)
@@ -60,17 +40,49 @@ def noops(self, x=None, *args, **kwargs):
6040
"Do nothing (method)"
6141
return x
6242

63-
def one_is_instance(a, b, t): return isinstance(a,t) or isinstance(b,t)
43+
def any_is_instance(t, *args): return any(isinstance(a,t) for a in args)
44+
45+
def isinstance_str(x, cls_name):
46+
"Like `isinstance`, except takes a type name instead of a type"
47+
return cls_name in [t.__name__ for t in type(x).__mro__]
48+
49+
def array_equal(a,b):
50+
if hasattr(a, '__array__'): a = a.__array__()
51+
if hasattr(b, '__array__'): b = b.__array__()
52+
return (a==b).all()
6453

6554
def equals(a,b):
6655
"Compares `a` and `b` for equality; supports sublists, tensors and arrays too"
6756
if (a is None) ^ (b is None): return False
68-
if one_is_instance(a,b,type): return a==b
57+
if any_is_instance(type,a,b): return a==b
6958
if hasattr(a, '__array_eq__'): return a.__array_eq__(b)
7059
if hasattr(b, '__array_eq__'): return b.__array_eq__(a)
71-
cmp = (np.array_equal if one_is_instance(a, b, ndarray ) else
72-
operator.eq if one_is_instance(a, b, (str,dict,set)) else
73-
all_equal if is_iter(a) or is_iter(b) else
60+
cmp = (array_equal if isinstance_str(a, 'ndarray') or isinstance_str(b, 'ndarray') else
61+
operator.eq if any_is_instance((str,dict,set), a, b) else
62+
all_equal if is_iter(a) or is_iter(b) else
7463
operator.eq)
7564
return cmp(a,b)
7665

66+
def ipython_shell():
67+
"Same as `get_ipython` but returns `False` if not in IPython"
68+
try: return get_ipython()
69+
except NameError: return False
70+
71+
def in_ipython():
72+
"Check if code is running in some kind of IPython environment"
73+
return bool(ipython_shell())
74+
75+
def in_colab():
76+
"Check if the code is running in Google Colaboratory"
77+
return 'google.colab' in sys.modules
78+
79+
def in_jupyter():
80+
"Check if the code is running in a jupyter notebook"
81+
if not in_ipython(): return False
82+
return ipython_shell().__class__.__name__ == 'ZMQInteractiveShell'
83+
84+
def in_notebook():
85+
"Check if the code is running in a jupyter notebook"
86+
return in_colab() or in_jupyter()
87+
88+
IN_IPYTHON,IN_JUPYTER,IN_COLAB,IN_NOTEBOOK = in_ipython(),in_jupyter(),in_colab(),in_notebook()

fastcore/nb_imports.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
import numbers,tempfile,pickle,random
4+
5+
from PIL import Image
6+
from numpy import array,ndarray
7+
from copy import copy
8+
from time import sleep

fastcore/test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
# Cell
77
from .imports import *
8+
from collections import Counter
9+
from contextlib import redirect_stdout
810

911
# Cell
1012
def test_fail(f, msg='', contains=''):
@@ -49,7 +51,7 @@ def is_close(a,b,eps=1e-5):
4951
if hasattr(a, '__array__') or hasattr(b,'__array__'):
5052
return (abs(a-b)<eps).all()
5153
if isinstance(a, (Iterable,Generator)) or isinstance(b, (Iterable,Generator)):
52-
return is_close(np.array(a), np.array(b), eps=eps)
54+
return all(abs(a_-b_)<eps for a_,b_ in zip(a,b))
5355
return abs(a-b)<eps
5456

5557
# Cell
@@ -93,7 +95,7 @@ def test_warns(f, show=False):
9395
# Cell
9496
def test_fig_exists(ax):
9597
"Test there is a figure displayed in `ax`"
96-
assert ax and len(np.frombuffer(ax.figure.canvas.tostring_argb(), dtype=np.uint8))
98+
assert ax and len(ax.figure.canvas.tostring_argb())
9799

98100
# Cell
99101
def test_sig(f, b):

fastcore/utils.py

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
'partialler', 'mapped', 'instantiate', 'using_attr', 'log_args', 'Self', 'Self', 'remove_patches_path',
1010
'bunzip', 'join_path_file', 'urlread', 'urljson', 'sort_by_run', 'PrettyString', 'round_multiple',
1111
'even_mults', 'num_cpus', 'add_props', 'ContextManagers', 'set_num_threads', 'ProcessPoolExecutor',
12-
'parallel', 'parallel_chunks', 'run_procs', 'parallel_gen', 'ipython_shell', 'in_ipython', 'in_colab',
13-
'in_jupyter', 'in_notebook', 'IN_NOTEBOOK', 'IN_JUPYTER', 'IN_COLAB', 'IN_IPYTHON']
12+
'parallel', 'parallel_chunks', 'run_procs', 'parallel_gen']
1413

1514
# Cell
1615
from .imports import *
1716
from .foundation import *
1817
from functools import wraps
18+
import mimetypes,bz2,pickle,random
19+
from contextlib import contextmanager
1920

2021
# Cell
2122
def ifnone(a, b):
@@ -321,13 +322,13 @@ class Inf(metaclass=_InfMeta):
321322
pass
322323

323324
# Cell
324-
def _oper(op,a,b=np.nan): return (lambda o:op(o,a)) if b!=b else op(a,b)
325+
def _oper(op,a,b=float('nan')): return (lambda o:op(o,a)) if b!=b else op(a,b)
325326

326327
def _mk_op(nm, mod=None):
327328
"Create an operator using `oper` and add to the caller's module"
328329
if mod is None: mod = inspect.currentframe().f_back.f_locals
329330
op = getattr(operator,nm)
330-
def _inner(a,b=np.nan): return _oper(op, a,b)
331+
def _inner(a,b=float('nan')): return _oper(op, a,b)
331332
_inner.__name__ = _inner.__qualname__ = nm
332333
_inner.__doc__ = f'Same as `operator.{nm}`, or returns partial if 1 arg'
333334
mod[nm] = _inner
@@ -647,7 +648,7 @@ def even_mults(start, stop, n):
647648
if n==1: return stop
648649
mult = stop/start
649650
step = mult**(1/(n-1))
650-
return np.array([start*(step**i) for i in range(n)])
651+
return [start*(step**i) for i in range(n)]
651652

652653
# Cell
653654
def num_cpus():
@@ -763,43 +764,10 @@ def parallel_gen(cls, items, n_workers=defaults.cpus, **kwargs):
763764
if n_workers==0:
764765
yield from enumerate(list(cls(**kwargs)(items)))
765766
return
766-
batches = np.array_split(items, n_workers)
767-
idx = np.cumsum(0 + L(batches).map(len))
767+
batches = L(chunked(items, n_chunks=n_workers))
768+
idx = L(itertools.accumulate(0 + batches.map(len)))
768769
queue = Queue()
769770
if progress_bar: items = progress_bar(items, leave=False)
770771
f=partial(_f_pg, cls(**kwargs), queue)
771772
done=partial(_done_pg, queue, items)
772-
yield from run_procs(f, done, L(batches,idx).zip())
773-
774-
# Cell
775-
def ipython_shell():
776-
"Same as `get_ipython` but returns `False` if not in IPython"
777-
try: return get_ipython()
778-
except NameError: return False
779-
780-
# Cell
781-
def in_ipython():
782-
"Check if code is running in some kind of IPython environment"
783-
return bool(ipython_shell())
784-
785-
# Cell
786-
def in_colab():
787-
"Check if the code is running in Google Colaboratory"
788-
return 'google.colab' in sys.modules
789-
790-
# Cell
791-
def in_jupyter():
792-
"Check if the code is running in a jupyter notebook"
793-
if not in_ipython(): return False
794-
return ipython_shell().__class__.__name__ == 'ZMQInteractiveShell'
795-
796-
# Cell
797-
def in_notebook():
798-
"Check if the code is running in a jupyter notebook"
799-
return in_colab() or in_jupyter()
800-
801-
# Cell
802-
IN_IPYTHON,IN_JUPYTER,IN_COLAB,IN_NOTEBOOK = in_ipython(),in_jupyter(),in_colab(),in_notebook()
803-
804-
# Cell
805-
#nbdev_comment _all_ = ['IN_NOTEBOOK', 'IN_JUPYTER', 'IN_COLAB', 'IN_IPYTHON']
773+
yield from run_procs(f, done, L(batches,idx).zip())

0 commit comments

Comments
 (0)