Skip to content

Commit 6f2cb64

Browse files
committed
fixes #117
1 parent 943323b commit 6f2cb64

File tree

2 files changed

+84
-70
lines changed

2 files changed

+84
-70
lines changed

fastcore/foundation.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def __call__(cls, x=None, *args, **kwargs):
218218
return super().__call__(x, *args, **kwargs)
219219

220220
# Cell
221-
class L(CollBase, metaclass=_L_Meta):
221+
class L(GetAttr, CollBase, metaclass=_L_Meta):
222222
"Behaves like a list of `items` but can also index with list of indices or masks"
223223
_default='items'
224224
def __init__(self, items=None, *rest, use_list=False, match=None):
@@ -320,41 +320,33 @@ def map_filter(self, f=noop, g=noop, *args, gen=False, **kwargs):
320320
res = filter(g, self.map(f, *args, gen=gen, **kwargs))
321321
if gen: return res
322322
return self._new(res)
323-
def map_first(self, f=noop, g=noop, *args, **kwargs): return first(self.map_filter(f, g, *args, gen=False, **kwargs))
323+
def map_first(self, f=noop, g=noop, *args, **kwargs):
324+
return first(self.map_filter(f, g, *args, gen=False, **kwargs))
324325

325326
def starmap(self, f, *args, **kwargs): return self._new(itertools.starmap(partial(f,*args,**kwargs), self))
326327
def zip(self, cycled=False): return self._new((zip_cycle if cycled else zip)(*self))
327328
def zipwith(self, *rest, cycled=False): return self._new([self, *rest]).zip(cycled=cycled)
328329
def map_zip(self, f, *args, cycled=False, **kwargs): return self.zip(cycled=cycled).starmap(f, *args, **kwargs)
329-
def map_zipwith(self, f, *rest, cycled=False, **kwargs): return self.zipwith(*rest, cycled=cycled).starmap(f, **kwargs)
330-
def concat(self): return self._new(itertools.chain.from_iterable(self.map(L)))
330+
def map_zipwith(self, f, *rest, cycled=False, **kwargs):
331+
return self.zipwith(*rest, cycled=cycled).starmap(f, **kwargs)
331332
def shuffle(self):
332333
it = copy(self.items)
333334
random.shuffle(it)
334335
return self._new(it)
335336

336-
def append(self,o): return self.items.append(o)
337-
def remove(self,o): return self.items.remove(o)
338-
def count (self,o): return self.items.count(o)
339-
def reverse(self ): return self.items.reverse()
340-
def pop(self,o=-1): return self.items.pop(o)
341-
def clear(self ): return self.items.clear()
342-
def insert(self,idx,o): return self.items.insert(idx,o)
343-
def index(self, value, start=0, stop=sys.maxsize): return self.items.index(value, start, stop)
344-
def sort(self, key=None, reverse=False): return self.items.sort(key=key, reverse=reverse)
337+
def concat(self): return self._new(itertools.chain.from_iterable(self.map(L)))
345338
def reduce(self, f, initial=None): return reduce(f, self) if initial is None else reduce(f, self, initial)
346339
def sum(self): return self.reduce(operator.add)
347340
def product(self): return self.reduce(operator.mul)
341+
def setattrs(self, attr, val): [setattr(o,attr,val) for o in self]
348342

349343
# Cell
350-
_docs = {o:"Passthru to `list` method" for o in
351-
'append count remove reverse sort pop clear insert index'.split()}
352344
add_docs(L,
353345
__getitem__="Retrieve `idx` (can be list of indices, or mask, or int) items",
354-
range="Class Method: Same as `range`, but returns an `L`. Can pass a collection for `a`, to use `len(a)`",
346+
range="Class Method: Same as `range`, but returns `L`. Can pass collection for `a`, to use `len(a)`",
355347
split="Class Method: Same as `str.split`, but returns an `L`",
356348
copy="Same as `list.copy`, but returns an `L`",
357-
sorted="New `L` sorted by `key`. If key is str then use `attrgetter`. If key is int then use `itemgetter`",
349+
sorted="New `L` sorted by `key`. If key is str use `attrgetter`; if int use `itemgetter`",
358350
unique="Unique items, in stable order",
359351
val2idx="Dict from value to index",
360352
filter="Create new `L` filtered by predicate `f`, passing `args` and `kwargs` to `f`",
@@ -377,7 +369,8 @@ def product(self): return self.reduce(operator.mul)
377369
reduce="Wrapper for `functools.reduce`",
378370
sum="Sum of the items",
379371
product="Product of the items",
380-
**_docs)
372+
setattrs="Call `setattr` on all items"
373+
)
381374

382375
# Cell
383376
#hide
@@ -419,11 +412,11 @@ def __init__(self, cfg_name='settings.ini'):
419412
_add_new_defaults(self.d, self.config_file,
420413
host="github", doc_host="https://%(user)s.github.io", doc_baseurl="/%(lib_name)s/")
421414

422-
def __getattr__(self,k):
423-
if k=='d' or k not in self.d: raise AttributeError(k)
424-
return self.config_file.parent/self.d[k] if k.endswith('_path') else self.get(k)
425-
426-
def get(self,k,default=None): return self.d.get(k, default)
427415
def __setitem__(self,k,v): self.d[k] = str(v)
428416
def __contains__(self,k): return k in self.d
429-
def save(self): save_config_file(self.config_file,self.d)
417+
def save(self): save_config_file(self.config_file,self.d)
418+
def __getattr__(self,k): return stop(AttributeError(k)) if k=='d' or k not in self.d else self.get(k)
419+
420+
def get(self,k,default=None):
421+
v = self.d.get(k)
422+
return self.config_file.parent/v if k.endswith('_path') else v

nbs/01_foundation.ipynb

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,7 +1514,7 @@
15141514
"outputs": [],
15151515
"source": [
15161516
"#export\n",
1517-
"class L(CollBase, metaclass=_L_Meta):\n",
1517+
"class L(GetAttr, CollBase, metaclass=_L_Meta):\n",
15181518
" \"Behaves like a list of `items` but can also index with list of indices or masks\"\n",
15191519
" _default='items'\n",
15201520
" def __init__(self, items=None, *rest, use_list=False, match=None):\n",
@@ -1616,31 +1616,25 @@
16161616
" res = filter(g, self.map(f, *args, gen=gen, **kwargs))\n",
16171617
" if gen: return res\n",
16181618
" return self._new(res)\n",
1619-
" def map_first(self, f=noop, g=noop, *args, **kwargs): return first(self.map_filter(f, g, *args, gen=False, **kwargs))\n",
1619+
" def map_first(self, f=noop, g=noop, *args, **kwargs):\n",
1620+
" return first(self.map_filter(f, g, *args, gen=False, **kwargs))\n",
16201621
"\n",
16211622
" def starmap(self, f, *args, **kwargs): return self._new(itertools.starmap(partial(f,*args,**kwargs), self))\n",
16221623
" def zip(self, cycled=False): return self._new((zip_cycle if cycled else zip)(*self))\n",
16231624
" def zipwith(self, *rest, cycled=False): return self._new([self, *rest]).zip(cycled=cycled)\n",
16241625
" def map_zip(self, f, *args, cycled=False, **kwargs): return self.zip(cycled=cycled).starmap(f, *args, **kwargs)\n",
1625-
" def map_zipwith(self, f, *rest, cycled=False, **kwargs): return self.zipwith(*rest, cycled=cycled).starmap(f, **kwargs)\n",
1626-
" def concat(self): return self._new(itertools.chain.from_iterable(self.map(L)))\n",
1626+
" def map_zipwith(self, f, *rest, cycled=False, **kwargs):\n",
1627+
" return self.zipwith(*rest, cycled=cycled).starmap(f, **kwargs)\n",
16271628
" def shuffle(self):\n",
16281629
" it = copy(self.items)\n",
16291630
" random.shuffle(it)\n",
16301631
" return self._new(it)\n",
16311632
"\n",
1632-
" def append(self,o): return self.items.append(o)\n",
1633-
" def remove(self,o): return self.items.remove(o)\n",
1634-
" def count (self,o): return self.items.count(o)\n",
1635-
" def reverse(self ): return self.items.reverse()\n",
1636-
" def pop(self,o=-1): return self.items.pop(o)\n",
1637-
" def clear(self ): return self.items.clear()\n",
1638-
" def insert(self,idx,o): return self.items.insert(idx,o)\n",
1639-
" def index(self, value, start=0, stop=sys.maxsize): return self.items.index(value, start, stop)\n",
1640-
" def sort(self, key=None, reverse=False): return self.items.sort(key=key, reverse=reverse)\n",
1633+
" def concat(self): return self._new(itertools.chain.from_iterable(self.map(L)))\n",
16411634
" def reduce(self, f, initial=None): return reduce(f, self) if initial is None else reduce(f, self, initial)\n",
16421635
" def sum(self): return self.reduce(operator.add)\n",
1643-
" def product(self): return self.reduce(operator.mul)"
1636+
" def product(self): return self.reduce(operator.mul)\n",
1637+
" def setattrs(self, attr, val): [setattr(o,attr,val) for o in self]"
16441638
]
16451639
},
16461640
{
@@ -1650,14 +1644,12 @@
16501644
"outputs": [],
16511645
"source": [
16521646
"#export\n",
1653-
"_docs = {o:\"Passthru to `list` method\" for o in\n",
1654-
" 'append count remove reverse sort pop clear insert index'.split()}\n",
16551647
"add_docs(L,\n",
16561648
" __getitem__=\"Retrieve `idx` (can be list of indices, or mask, or int) items\",\n",
1657-
" range=\"Class Method: Same as `range`, but returns an `L`. Can pass a collection for `a`, to use `len(a)`\",\n",
1649+
" range=\"Class Method: Same as `range`, but returns `L`. Can pass collection for `a`, to use `len(a)`\",\n",
16581650
" split=\"Class Method: Same as `str.split`, but returns an `L`\",\n",
16591651
" copy=\"Same as `list.copy`, but returns an `L`\",\n",
1660-
" sorted=\"New `L` sorted by `key`. If key is str then use `attrgetter`. If key is int then use `itemgetter`\",\n",
1652+
" sorted=\"New `L` sorted by `key`. If key is str use `attrgetter`; if int use `itemgetter`\",\n",
16611653
" unique=\"Unique items, in stable order\",\n",
16621654
" val2idx=\"Dict from value to index\",\n",
16631655
" filter=\"Create new `L` filtered by predicate `f`, passing `args` and `kwargs` to `f`\",\n",
@@ -1680,7 +1672,8 @@
16801672
" reduce=\"Wrapper for `functools.reduce`\",\n",
16811673
" sum=\"Sum of the items\",\n",
16821674
" product=\"Product of the items\",\n",
1683-
" **_docs)"
1675+
" setattrs=\"Call `setattr` on all items\"\n",
1676+
" )"
16841677
]
16851678
},
16861679
{
@@ -1826,7 +1819,7 @@
18261819
{
18271820
"data": {
18281821
"text/plain": [
1829-
"[0, 7, 11]"
1822+
"[4, 1, 2]"
18301823
]
18311824
},
18321825
"execution_count": null,
@@ -2318,17 +2311,7 @@
23182311
"cell_type": "markdown",
23192312
"metadata": {},
23202313
"source": [
2321-
"If the special argument `_arg` is passed, then that is the kwarg used in the map."
2322-
]
2323-
},
2324-
{
2325-
"cell_type": "code",
2326-
"execution_count": null,
2327-
"metadata": {},
2328-
"outputs": [],
2329-
"source": [
2330-
"#What is this? TODO Jeremy: fix\n",
2331-
"#L.range(4).map(f, b=arg0)"
2314+
"You can also pass the same `arg` params that `bind` accepts:"
23322315
]
23332316
},
23342317
{
@@ -2385,7 +2368,7 @@
23852368
{
23862369
"data": {
23872370
"text/markdown": [
2388-
"<h4 id=\"L.zip\" class=\"doc_header\"><code>L.zip</code><a href=\"__main__.py#L107\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2371+
"<h4 id=\"L.zip\" class=\"doc_header\"><code>L.zip</code><a href=\"__main__.py#L108\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
23892372
"\n",
23902373
"> <code>L.zip</code>(**`cycled`**=*`False`*)\n",
23912374
"\n",
@@ -2432,7 +2415,7 @@
24322415
{
24332416
"data": {
24342417
"text/markdown": [
2435-
"<h4 id=\"L.map_zip\" class=\"doc_header\"><code>L.map_zip</code><a href=\"__main__.py#L109\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2418+
"<h4 id=\"L.map_zip\" class=\"doc_header\"><code>L.map_zip</code><a href=\"__main__.py#L110\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
24362419
"\n",
24372420
"> <code>L.map_zip</code>(**`f`**, **\\*`args`**, **`cycled`**=*`False`*, **\\*\\*`kwargs`**)\n",
24382421
"\n",
@@ -2468,7 +2451,7 @@
24682451
{
24692452
"data": {
24702453
"text/markdown": [
2471-
"<h4 id=\"L.zipwith\" class=\"doc_header\"><code>L.zipwith</code><a href=\"__main__.py#L108\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2454+
"<h4 id=\"L.zipwith\" class=\"doc_header\"><code>L.zipwith</code><a href=\"__main__.py#L109\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
24722455
"\n",
24732456
"> <code>L.zipwith</code>(**\\*`rest`**, **`cycled`**=*`False`*)\n",
24742457
"\n",
@@ -2505,7 +2488,7 @@
25052488
{
25062489
"data": {
25072490
"text/markdown": [
2508-
"<h4 id=\"L.map_zipwith\" class=\"doc_header\"><code>L.map_zipwith</code><a href=\"__main__.py#L110\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2491+
"<h4 id=\"L.map_zipwith\" class=\"doc_header\"><code>L.map_zipwith</code><a href=\"__main__.py#L111\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
25092492
"\n",
25102493
"> <code>L.map_zipwith</code>(**`f`**, **\\*`rest`**, **`cycled`**=*`False`*, **\\*\\*`kwargs`**)\n",
25112494
"\n",
@@ -2620,7 +2603,7 @@
26202603
"\n",
26212604
"> <code>L.sorted</code>(**`key`**=*`None`*, **`reverse`**=*`False`*)\n",
26222605
"\n",
2623-
"New [`L`](/foundation.html#L) sorted by `key`. If key is str then use `attrgetter`. If key is int then use `itemgetter`"
2606+
"New [`L`](/foundation.html#L) sorted by `key`. If key is str use `attrgetter`; if int use `itemgetter`"
26242607
],
26252608
"text/plain": [
26262609
"<IPython.core.display.Markdown object>"
@@ -2690,7 +2673,7 @@
26902673
"\n",
26912674
"> <code>L.range</code>(**`a`**, **`b`**=*`None`*, **`step`**=*`None`*)\n",
26922675
"\n",
2693-
"Class Method: Same as `range`, but returns an [`L`](/foundation.html#L). Can pass a collection for `a`, to use `len(a)`"
2676+
"Class Method: Same as `range`, but returns [`L`](/foundation.html#L). Can pass collection for `a`, to use `len(a)`"
26942677
],
26952678
"text/plain": [
26962679
"<IPython.core.display.Markdown object>"
@@ -2722,7 +2705,7 @@
27222705
{
27232706
"data": {
27242707
"text/markdown": [
2725-
"<h4 id=\"L.concat\" class=\"doc_header\"><code>L.concat</code><a href=\"__main__.py#L111\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2708+
"<h4 id=\"L.concat\" class=\"doc_header\"><code>L.concat</code><a href=\"__main__.py#L118\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
27262709
"\n",
27272710
"> <code>L.concat</code>()\n",
27282711
"\n",
@@ -2857,6 +2840,43 @@
28572840
"test_eq(t.map_first(lambda o:o*2 if o>2 else None), 6)"
28582841
]
28592842
},
2843+
{
2844+
"cell_type": "code",
2845+
"execution_count": null,
2846+
"metadata": {},
2847+
"outputs": [
2848+
{
2849+
"data": {
2850+
"text/markdown": [
2851+
"<h4 id=\"L.setattrs\" class=\"doc_header\"><code>L.setattrs</code><a href=\"__main__.py#L122\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
2852+
"\n",
2853+
"> <code>L.setattrs</code>(**`attr`**, **`val`**)\n",
2854+
"\n",
2855+
"Call `setattr` on all items"
2856+
],
2857+
"text/plain": [
2858+
"<IPython.core.display.Markdown object>"
2859+
]
2860+
},
2861+
"metadata": {},
2862+
"output_type": "display_data"
2863+
}
2864+
],
2865+
"source": [
2866+
"show_doc(L.setattrs)"
2867+
]
2868+
},
2869+
{
2870+
"cell_type": "code",
2871+
"execution_count": null,
2872+
"metadata": {},
2873+
"outputs": [],
2874+
"source": [
2875+
"t = L(SimpleNamespace(),SimpleNamespace())\n",
2876+
"t.setattrs('foo', 'bar')\n",
2877+
"test_eq(t.attrgot('foo'), ['bar','bar'])"
2878+
]
2879+
},
28602880
{
28612881
"cell_type": "markdown",
28622882
"metadata": {},
@@ -2904,7 +2924,7 @@
29042924
"metadata": {},
29052925
"outputs": [],
29062926
"source": [
2907-
"_d = dict(user='fastai', lib_name='fastcore')\n",
2927+
"_d = dict(user='fastai', lib_name='fastcore', some_path='test')\n",
29082928
"try:\n",
29092929
" save_config_file('tmp.ini', _d)\n",
29102930
" res = read_config_file('tmp.ini')\n",
@@ -2945,21 +2965,21 @@
29452965
" _add_new_defaults(self.d, self.config_file,\n",
29462966
" host=\"github\", doc_host=\"https://%(user)s.github.io\", doc_baseurl=\"/%(lib_name)s/\")\n",
29472967
"\n",
2948-
" def __getattr__(self,k):\n",
2949-
" if k=='d' or k not in self.d: raise AttributeError(k)\n",
2950-
" return self.config_file.parent/self.d[k] if k.endswith('_path') else self.get(k)\n",
2951-
"\n",
2952-
" def get(self,k,default=None): return self.d.get(k, default)\n",
29532968
" def __setitem__(self,k,v): self.d[k] = str(v)\n",
29542969
" def __contains__(self,k): return k in self.d\n",
2955-
" def save(self): save_config_file(self.config_file,self.d)"
2970+
" def save(self): save_config_file(self.config_file,self.d)\n",
2971+
" def __getattr__(self,k): return stop(AttributeError(k)) if k=='d' or k not in self.d else self.get(k)\n",
2972+
"\n",
2973+
" def get(self,k,default=None):\n",
2974+
" v = self.d.get(k)\n",
2975+
" return self.config_file.parent/v if k.endswith('_path') else v"
29562976
]
29572977
},
29582978
{
29592979
"cell_type": "markdown",
29602980
"metadata": {},
29612981
"source": [
2962-
"`Config` searches parent directories for a config file, and provides direct access to the 'DEFAULT' section. "
2982+
"`Config` searches parent directories for a config file, and provides direct access to the 'DEFAULT' section. Keys ending in `_path` are converted to paths in the config file's directory."
29632983
]
29642984
},
29652985
{
@@ -2973,6 +2993,7 @@
29732993
" cfg = Config('tmp.ini')\n",
29742994
" test_eq(cfg.user,'fastai')\n",
29752995
" test_eq(cfg.doc_baseurl,'/fastcore/')\n",
2996+
" test_eq(cfg.get('some_path'), Path('../test').resolve())\n",
29762997
"finally: os.unlink('../tmp.ini')"
29772998
]
29782999
},

0 commit comments

Comments
 (0)