Skip to content

Commit 4a82f86

Browse files
committed
fixes #715
1 parent 963b4c7 commit 4a82f86

9 files changed

Lines changed: 187 additions & 141 deletions

File tree

fastcore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.8.19"
1+
__version__ = "1.9.0"

fastcore/_modidx.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
'fastcore.basics._oper': ('basics.html#_oper', 'fastcore/basics.py'),
101101
'fastcore.basics._risinstance': ('basics.html#_risinstance', 'fastcore/basics.py'),
102102
'fastcore.basics._store_attr': ('basics.html#_store_attr', 'fastcore/basics.py'),
103+
'fastcore.basics._strip_patch_name': ('basics.html#_strip_patch_name', 'fastcore/basics.py'),
103104
'fastcore.basics._typeerr': ('basics.html#_typeerr', 'fastcore/basics.py'),
104105
'fastcore.basics._using_attr': ('basics.html#_using_attr', 'fastcore/basics.py'),
105106
'fastcore.basics.add_props': ('basics.html#add_props', 'fastcore/basics.py'),
@@ -182,7 +183,6 @@
182183
'fastcore.basics.partition': ('basics.html#partition', 'fastcore/basics.py'),
183184
'fastcore.basics.partition_dict': ('basics.html#partition_dict', 'fastcore/basics.py'),
184185
'fastcore.basics.patch': ('basics.html#patch', 'fastcore/basics.py'),
185-
'fastcore.basics.patch_property': ('basics.html#patch_property', 'fastcore/basics.py'),
186186
'fastcore.basics.patch_to': ('basics.html#patch_to', 'fastcore/basics.py'),
187187
'fastcore.basics.properties': ('basics.html#properties', 'fastcore/basics.py'),
188188
'fastcore.basics.range_of': ('basics.html#range_of', 'fastcore/basics.py'),
@@ -366,7 +366,7 @@
366366
'fastcore.foundation.L.pairwise': ('foundation.html#l.pairwise', 'fastcore/foundation.py'),
367367
'fastcore.foundation.L.partition': ('foundation.html#l.partition', 'fastcore/foundation.py'),
368368
'fastcore.foundation.L.permutations': ('foundation.html#l.permutations', 'fastcore/foundation.py'),
369-
'fastcore.foundation.L.product': ('foundation.html#l.product', 'fastcore/foundation.py'),
369+
'fastcore.foundation.L.product__': ('foundation.html#l.product__', 'fastcore/foundation.py'),
370370
'fastcore.foundation.L.range': ('foundation.html#l.range', 'fastcore/foundation.py'),
371371
'fastcore.foundation.L.reduce': ('foundation.html#l.reduce', 'fastcore/foundation.py'),
372372
'fastcore.foundation.L.renumerate': ('foundation.html#l.renumerate', 'fastcore/foundation.py'),

fastcore/basics.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
'nested_attr', 'nested_setdefault', 'nested_callable', 'nested_idx', 'set_nested_idx', 'val2idx',
1818
'uniqueify', 'loop_first_last', 'loop_first', 'loop_last', 'first_match', 'last_match', 'fastuple', 'bind',
1919
'mapt', 'map_ex', 'compose', 'maps', 'partialler', 'instantiate', 'using_attr', 'copy_func', 'patch_to',
20-
'patch', 'patch_property', 'compile_re', 'ImportEnum', 'StrEnum', 'str_enum', 'ValEnum', 'Stateful',
21-
'NotStr', 'PrettyString', 'even_mults', 'num_cpus', 'add_props', 'str2bool', 'str2int', 'str2float',
22-
'str2list', 'str2date', 'to_bool', 'to_int', 'to_float', 'to_list', 'to_date', 'typed', 'exec_new',
23-
'exec_import', 'lt', 'gt', 'le', 'ge', 'eq', 'ne', 'add', 'sub', 'mul', 'truediv', 'is_', 'is_not', 'mod']
20+
'patch', 'compile_re', 'ImportEnum', 'StrEnum', 'str_enum', 'ValEnum', 'Stateful', 'NotStr', 'PrettyString',
21+
'even_mults', 'num_cpus', 'add_props', 'str2bool', 'str2int', 'str2float', 'str2list', 'str2date', 'to_bool',
22+
'to_int', 'to_float', 'to_list', 'to_date', 'typed', 'exec_new', 'exec_import', 'lt', 'gt', 'le', 'ge', 'eq',
23+
'ne', 'add', 'sub', 'mul', 'truediv', 'is_', 'is_not', 'mod']
2424

2525
# %% ../nbs/01_basics.ipynb
2626
from .imports import *
@@ -1051,42 +1051,46 @@ def __init__(self, f): self.f = f
10511051
def __get__(self, _, f_cls): return MethodType(self.f, f_cls)
10521052

10531053
# %% ../nbs/01_basics.ipynb
1054-
def patch_to(cls, as_prop=False, cls_method=False, set_prop=False):
1054+
def _strip_patch_name(nm):
1055+
"Strip trailing `__` from `nm` if it doesn't start with `_`"
1056+
return nm[:-2] if nm.endswith('__') and not nm.startswith('_') else nm
1057+
1058+
def patch_to(cls, as_prop=False, cls_method=False, set_prop=False, nm=None):
10551059
"Decorator: add `f` to `cls`"
10561060
if not isinstance(cls, (tuple,list)): cls=(cls,)
10571061
def _inner(f):
10581062
for c_ in cls:
10591063
nf = copy_func(f)
1060-
nm = f.__name__
1064+
fnm = nm or _strip_patch_name(f.__name__)
10611065
# `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually
10621066
for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o))
1063-
nf.__qualname__ = f"{c_.__name__}.{nm}"
1064-
if cls_method: setattr(c_, nm, _clsmethod(nf))
1067+
nf.__name__ = fnm
1068+
nf.__qualname__ = f"{c_.__name__}.{fnm}"
1069+
if cls_method: setattr(c_, fnm, _clsmethod(nf))
10651070
else:
1066-
if set_prop: setattr(c_, nm, getattr(c_, nm).setter(nf))
1067-
elif as_prop: setattr(c_, nm, property(nf))
1071+
if set_prop: setattr(c_, fnm, getattr(c_, fnm).setter(nf))
1072+
elif as_prop: setattr(c_, fnm, property(nf))
10681073
else:
1069-
onm = '_orig_'+nm
1070-
if hasattr(c_, nm) and not hasattr(c_, onm): setattr(c_, onm, getattr(c_, nm))
1071-
setattr(c_, nm, nf)
1074+
onm = '_orig_'+fnm
1075+
if hasattr(c_, fnm) and not hasattr(c_, onm): setattr(c_, onm, getattr(c_, fnm))
1076+
setattr(c_, fnm, nf)
10721077
# Avoid clobbering existing functions
1073-
return globals().get(nm, builtins.__dict__.get(nm, None))
1078+
return globals().get(fnm, builtins.__dict__.get(fnm, None))
10741079
return _inner
10751080

10761081
# %% ../nbs/01_basics.ipynb
1077-
def patch(f=None, *, as_prop=False, cls_method=False, set_prop=False):
1082+
def patch(f=None, *, as_prop=False, cls_method=False, set_prop=False, nm=None):
10781083
"Decorator: add `f` to the first parameter's class (based on f's type annotations)"
1079-
if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop)
1084+
if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop, nm=nm)
10801085
ann,glb,loc = get_annotations_ex(f)
1081-
cls = union2tuple(eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc))
1082-
return patch_to(cls, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop)(f)
1083-
1084-
# %% ../nbs/01_basics.ipynb
1085-
def patch_property(f):
1086-
"Deprecated; use `patch(as_prop=True)` instead"
1087-
warnings.warn("`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead")
1088-
cls = next(iter(f.__annotations__.values()))
1089-
return patch_to(cls, as_prop=True)(f)
1086+
if cls_method:
1087+
if 'cls' not in ann: raise TypeError(f"@patch with cls_method=True requires 'cls' to have a type annotation")
1088+
cls = ann.pop('cls')
1089+
else:
1090+
if not ann: raise TypeError(f"@patch requires the first parameter of `{f.__name__}` to have a type annotation")
1091+
cls = next(iter(ann.values()))
1092+
cls = union2tuple(eval_type(cls, glb, loc))
1093+
return patch_to(cls, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop, nm=nm)(f)
10901094

10911095
# %% ../nbs/01_basics.ipynb
10921096
def compile_re(pat):

fastcore/foundation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ def sum(self:L):
409409

410410
# %% ../nbs/02_foundation.ipynb
411411
@patch
412-
def product(self:L):
412+
def product__(self:L):
413413
"Product of the items"
414414
return self.reduce(operator.mul, 1)
415415

nbs/01_basics.ipynb

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6035,31 +6035,36 @@
60356035
{
60366036
"cell_type": "code",
60376037
"execution_count": null,
6038-
"id": "2256bd09",
6038+
"id": "97f2fee5",
60396039
"metadata": {},
60406040
"outputs": [],
60416041
"source": [
60426042
"#| export\n",
6043-
"def patch_to(cls, as_prop=False, cls_method=False, set_prop=False):\n",
6043+
"def _strip_patch_name(nm):\n",
6044+
" \"Strip trailing `__` from `nm` if it doesn't start with `_`\"\n",
6045+
" return nm[:-2] if nm.endswith('__') and not nm.startswith('_') else nm\n",
6046+
"\n",
6047+
"def patch_to(cls, as_prop=False, cls_method=False, set_prop=False, nm=None):\n",
60446048
" \"Decorator: add `f` to `cls`\"\n",
60456049
" if not isinstance(cls, (tuple,list)): cls=(cls,)\n",
60466050
" def _inner(f):\n",
60476051
" for c_ in cls:\n",
60486052
" nf = copy_func(f)\n",
6049-
" nm = f.__name__\n",
6053+
" fnm = nm or _strip_patch_name(f.__name__)\n",
60506054
" # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually\n",
60516055
" for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o))\n",
6052-
" nf.__qualname__ = f\"{c_.__name__}.{nm}\"\n",
6053-
" if cls_method: setattr(c_, nm, _clsmethod(nf))\n",
6056+
" nf.__name__ = fnm\n",
6057+
" nf.__qualname__ = f\"{c_.__name__}.{fnm}\"\n",
6058+
" if cls_method: setattr(c_, fnm, _clsmethod(nf))\n",
60546059
" else:\n",
6055-
" if set_prop: setattr(c_, nm, getattr(c_, nm).setter(nf))\n",
6056-
" elif as_prop: setattr(c_, nm, property(nf))\n",
6060+
" if set_prop: setattr(c_, fnm, getattr(c_, fnm).setter(nf))\n",
6061+
" elif as_prop: setattr(c_, fnm, property(nf))\n",
60576062
" else:\n",
6058-
" onm = '_orig_'+nm\n",
6059-
" if hasattr(c_, nm) and not hasattr(c_, onm): setattr(c_, onm, getattr(c_, nm))\n",
6060-
" setattr(c_, nm, nf)\n",
6063+
" onm = '_orig_'+fnm\n",
6064+
" if hasattr(c_, fnm) and not hasattr(c_, onm): setattr(c_, onm, getattr(c_, fnm))\n",
6065+
" setattr(c_, fnm, nf)\n",
60616066
" # Avoid clobbering existing functions\n",
6062-
" return globals().get(nm, builtins.__dict__.get(nm, None))\n",
6067+
" return globals().get(fnm, builtins.__dict__.get(fnm, None))\n",
60636068
" return _inner"
60646069
]
60656070
},
@@ -6215,20 +6220,78 @@
62156220
"test_eq(t.func_mult(4), 8)"
62166221
]
62176222
},
6223+
{
6224+
"cell_type": "markdown",
6225+
"id": "521f1b1e",
6226+
"metadata": {},
6227+
"source": [
6228+
"You can also rename the function in the patched class:"
6229+
]
6230+
},
62186231
{
62196232
"cell_type": "code",
62206233
"execution_count": null,
6221-
"id": "e4c74c53",
6234+
"id": "d50c188d",
6235+
"metadata": {},
6236+
"outputs": [],
6237+
"source": [
6238+
"class _T8(int): pass \n",
6239+
"\n",
6240+
"@patch_to(_T8, nm='add_value')\n",
6241+
"def func2(self, a): return self+a\n",
6242+
"\n",
6243+
"t = _T8(1)\n",
6244+
"test_eq(t.add_value(2), 3)\n",
6245+
"test_eq(_T8.add_value.__name__, 'add_value')\n",
6246+
"assert not hasattr(t, 'func2')"
6247+
]
6248+
},
6249+
{
6250+
"cell_type": "markdown",
6251+
"id": "81877f71",
6252+
"metadata": {},
6253+
"source": [
6254+
"A `__` suffix is stripped (unless there's also a `_` prefix):"
6255+
]
6256+
},
6257+
{
6258+
"cell_type": "code",
6259+
"execution_count": null,
6260+
"id": "cdd9dedb",
6261+
"metadata": {},
6262+
"outputs": [],
6263+
"source": [
6264+
"class _T9(int): pass \n",
6265+
"\n",
6266+
"@patch_to(_T9)\n",
6267+
"def func__(self, a): return self+a\n",
6268+
"\n",
6269+
"t = _T9(1)\n",
6270+
"test_eq(t.func(2), 3)\n",
6271+
"test_eq(_T9.func.__name__, 'func')\n",
6272+
"assert not hasattr(t, 'func__')"
6273+
]
6274+
},
6275+
{
6276+
"cell_type": "code",
6277+
"execution_count": null,
6278+
"id": "8faf7b86",
62226279
"metadata": {},
62236280
"outputs": [],
62246281
"source": [
62256282
"#| export\n",
6226-
"def patch(f=None, *, as_prop=False, cls_method=False, set_prop=False):\n",
6283+
"def patch(f=None, *, as_prop=False, cls_method=False, set_prop=False, nm=None):\n",
62276284
" \"Decorator: add `f` to the first parameter's class (based on f's type annotations)\"\n",
6228-
" if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop)\n",
6285+
" if f is None: return partial(patch, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop, nm=nm)\n",
62296286
" ann,glb,loc = get_annotations_ex(f)\n",
6230-
" cls = union2tuple(eval_type(ann.pop('cls') if cls_method else next(iter(ann.values())), glb, loc))\n",
6231-
" return patch_to(cls, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop)(f)"
6287+
" if cls_method:\n",
6288+
" if 'cls' not in ann: raise TypeError(f\"@patch with cls_method=True requires 'cls' to have a type annotation\")\n",
6289+
" cls = ann.pop('cls')\n",
6290+
" else:\n",
6291+
" if not ann: raise TypeError(f\"@patch requires the first parameter of `{f.__name__}` to have a type annotation\")\n",
6292+
" cls = next(iter(ann.values()))\n",
6293+
" cls = union2tuple(eval_type(cls, glb, loc))\n",
6294+
" return patch_to(cls, as_prop=as_prop, cls_method=cls_method, set_prop=set_prop, nm=nm)(f)"
62326295
]
62336296
},
62346297
{
@@ -6348,16 +6411,37 @@
63486411
{
63496412
"cell_type": "code",
63506413
"execution_count": null,
6351-
"id": "f05267a8",
6414+
"id": "591af803",
63526415
"metadata": {},
63536416
"outputs": [],
63546417
"source": [
6355-
"#| export\n",
6356-
"def patch_property(f):\n",
6357-
" \"Deprecated; use `patch(as_prop=True)` instead\"\n",
6358-
" warnings.warn(\"`patch_property` is deprecated and will be removed; use `patch(as_prop=True)` instead\")\n",
6359-
" cls = next(iter(f.__annotations__.values()))\n",
6360-
" return patch_to(cls, as_prop=True)(f)"
6418+
"class _T8(int): pass \n",
6419+
"\n",
6420+
"@patch(nm='add_value')\n",
6421+
"def func2(self:_T8, a): return self+a\n",
6422+
"\n",
6423+
"t = _T8(1)\n",
6424+
"test_eq(t.add_value(2), 3)\n",
6425+
"test_eq(_T8.add_value.__name__, 'add_value')\n",
6426+
"assert not hasattr(t, 'func2')"
6427+
]
6428+
},
6429+
{
6430+
"cell_type": "code",
6431+
"execution_count": null,
6432+
"id": "fdbfff66",
6433+
"metadata": {},
6434+
"outputs": [],
6435+
"source": [
6436+
"class _T9(int): pass \n",
6437+
"\n",
6438+
"@patch\n",
6439+
"def func__(self:_T9, a): return self+a\n",
6440+
"\n",
6441+
"t = _T9(1)\n",
6442+
"test_eq(t.func(2), 3)\n",
6443+
"test_eq(_T9.func.__name__, 'func')\n",
6444+
"assert not hasattr(t, 'func__')"
63616445
]
63626446
},
63636447
{

nbs/02_foundation.ipynb

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -729,18 +729,7 @@
729729
"execution_count": null,
730730
"id": "68e52115",
731731
"metadata": {},
732-
"outputs": [
733-
{
734-
"data": {
735-
"text/plain": [
736-
"__main__.L"
737-
]
738-
},
739-
"execution_count": null,
740-
"metadata": {},
741-
"output_type": "execute_result"
742-
}
743-
],
732+
"outputs": [],
744733
"source": [
745734
"#| export\n",
746735
"Sequence.register(L);"
@@ -2308,7 +2297,7 @@
23082297
"source": [
23092298
"#| export\n",
23102299
"@patch\n",
2311-
"def product(self:L):\n",
2300+
"def product__(self:L):\n",
23122301
" \"Product of the items\"\n",
23132302
" return self.reduce(operator.mul, 1)"
23142303
]
@@ -3236,19 +3225,13 @@
32363225
"text/markdown": [
32373226
"---\n",
32383227
"\n",
3239-
"[source](https://github.com/AnswerDotAI/fastcore/blob/master/fastcore/foundation.py#L283){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
3228+
"[source](https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/foundation.py#L577){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
32403229
"\n",
32413230
"### Config.get\n",
32423231
"\n",
32433232
"> Config.get (k, default=None)"
32443233
],
32453234
"text/plain": [
3246-
"---\n",
3247-
"\n",
3248-
"[source](https://github.com/AnswerDotAI/fastcore/blob/master/fastcore/foundation.py#L283){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
3249-
"\n",
3250-
"### Config.get\n",
3251-
"\n",
32523235
"> Config.get (k, default=None)"
32533236
]
32543237
},
@@ -3337,7 +3320,7 @@
33373320
"text/markdown": [
33383321
"---\n",
33393322
"\n",
3340-
"[source](https://github.com/AnswerDotAI/fastcore/blob/master/fastcore/foundation.py#L297){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
3323+
"[source](https://github.com/AnswerDotAI/fastcore/blob/main/fastcore/foundation.py#L591){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
33413324
"\n",
33423325
"### Config.find\n",
33433326
"\n",
@@ -3346,12 +3329,6 @@
33463329
"*Search `cfg_path` and its parents to find `cfg_name`*"
33473330
],
33483331
"text/plain": [
3349-
"---\n",
3350-
"\n",
3351-
"[source](https://github.com/AnswerDotAI/fastcore/blob/master/fastcore/foundation.py#L297){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
3352-
"\n",
3353-
"### Config.find\n",
3354-
"\n",
33553332
"> Config.find (cfg_name, cfg_path=None, **kwargs)\n",
33563333
"\n",
33573334
"*Search `cfg_path` and its parents to find `cfg_name`*"
@@ -3423,7 +3400,13 @@
34233400
"source": []
34243401
}
34253402
],
3426-
"metadata": {},
3403+
"metadata": {
3404+
"kernelspec": {
3405+
"display_name": "python3",
3406+
"language": "python",
3407+
"name": "python3"
3408+
}
3409+
},
34273410
"nbformat": 4,
34283411
"nbformat_minor": 5
34293412
}

0 commit comments

Comments
 (0)