|
3 | 3 | { |
4 | 4 | "cell_type": "code", |
5 | 5 | "execution_count": null, |
| 6 | + "id": "4636a39a", |
6 | 7 | "metadata": {}, |
7 | 8 | "outputs": [], |
8 | 9 | "source": [ |
|
14 | 15 | }, |
15 | 16 | { |
16 | 17 | "cell_type": "markdown", |
| 18 | + "id": "d99e49d2", |
17 | 19 | "metadata": {}, |
18 | 20 | "source": [ |
19 | 21 | "# A tour of fastcore" |
20 | 22 | ] |
21 | 23 | }, |
22 | 24 | { |
23 | 25 | "cell_type": "markdown", |
| 26 | + "id": "8a5d405f", |
24 | 27 | "metadata": {}, |
25 | 28 | "source": [ |
26 | 29 | "Here's a (somewhat) quick tour of a few higlights from fastcore." |
27 | 30 | ] |
28 | 31 | }, |
29 | 32 | { |
30 | 33 | "cell_type": "markdown", |
| 34 | + "id": "50c03be8", |
31 | 35 | "metadata": {}, |
32 | 36 | "source": [ |
33 | 37 | "### Documentation" |
34 | 38 | ] |
35 | 39 | }, |
36 | 40 | { |
37 | 41 | "cell_type": "markdown", |
| 42 | + "id": "7a5091fd", |
38 | 43 | "metadata": {}, |
39 | 44 | "source": [ |
40 | 45 | "All fast.ai projects, including this one, are built with [nbdev](https://nbdev.fast.ai), which is a full literate programming environment built on Jupyter Notebooks. That means that every piece of documentation, including the page you're reading now, can be accessed as interactive Jupyter notebooks. In fact, you can even grab a link directly to a notebook running interactively on Google Colab - if you want to follow along with this tour, click the link below:" |
|
43 | 48 | { |
44 | 49 | "cell_type": "code", |
45 | 50 | "execution_count": null, |
| 51 | + "id": "00336b56", |
46 | 52 | "metadata": {}, |
47 | 53 | "outputs": [ |
48 | 54 | { |
|
64 | 70 | }, |
65 | 71 | { |
66 | 72 | "cell_type": "markdown", |
| 73 | + "id": "8388646e", |
67 | 74 | "metadata": {}, |
68 | 75 | "source": [ |
69 | 76 | "The full docs are available at [fastcore.fast.ai](https://fastcore.fast.ai). The code in the examples and in all fast.ai libraries follow the [fast.ai style guide](https://docs.fast.ai/dev/style.html). In order to support interactive programming, all fast.ai libraries are designed to allow for `import *` to be used safely, particular by ensuring that [`__all__`](https://riptutorial.com/python/example/2894/the---all---special-variable) is defined in all packages. In order to see where a function is from, just type it:" |
|
72 | 79 | { |
73 | 80 | "cell_type": "code", |
74 | 81 | "execution_count": null, |
| 82 | + "id": "05687ae5", |
75 | 83 | "metadata": {}, |
76 | 84 | "outputs": [ |
77 | 85 | { |
|
91 | 99 | }, |
92 | 100 | { |
93 | 101 | "cell_type": "markdown", |
| 102 | + "id": "5c324091", |
94 | 103 | "metadata": {}, |
95 | 104 | "source": [ |
96 | 105 | "For more details, including a link to the full documentation and source code, use `doc`, which pops up a window with this information:\n", |
|
106 | 115 | }, |
107 | 116 | { |
108 | 117 | "cell_type": "markdown", |
| 118 | + "id": "34ec6c68", |
109 | 119 | "metadata": {}, |
110 | 120 | "source": [ |
111 | 121 | "### Testing" |
112 | 122 | ] |
113 | 123 | }, |
114 | 124 | { |
115 | 125 | "cell_type": "markdown", |
| 126 | + "id": "96f09e79", |
116 | 127 | "metadata": {}, |
117 | 128 | "source": [ |
118 | 129 | "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.\n", |
|
123 | 134 | { |
124 | 135 | "cell_type": "code", |
125 | 136 | "execution_count": null, |
| 137 | + "id": "e194af33", |
126 | 138 | "metadata": {}, |
127 | 139 | "outputs": [], |
128 | 140 | "source": [ |
|
131 | 143 | }, |
132 | 144 | { |
133 | 145 | "cell_type": "markdown", |
| 146 | + "id": "54fb6d99", |
134 | 147 | "metadata": {}, |
135 | 148 | "source": [ |
136 | 149 | "That's an example from the docs for `coll_repr`. As you see, it's not showing you the output directly. Here's what that would look like:" |
|
139 | 152 | { |
140 | 153 | "cell_type": "code", |
141 | 154 | "execution_count": null, |
| 155 | + "id": "e90b1325", |
142 | 156 | "metadata": {}, |
143 | 157 | "outputs": [ |
144 | 158 | { |
|
158 | 172 | }, |
159 | 173 | { |
160 | 174 | "cell_type": "markdown", |
| 175 | + "id": "a7cc0ef3", |
161 | 176 | "metadata": {}, |
162 | 177 | "source": [ |
163 | 178 | "So, the test is actually showing you what the output looks like, because if the function call didn't return `'(#1000) [0,1,2,3,4...]'`, then the test would have failed.\n", |
|
170 | 185 | { |
171 | 186 | "cell_type": "code", |
172 | 187 | "execution_count": null, |
| 188 | + "id": "9630506c", |
173 | 189 | "metadata": {}, |
174 | 190 | "outputs": [], |
175 | 191 | "source": [ |
|
178 | 194 | }, |
179 | 195 | { |
180 | 196 | "cell_type": "markdown", |
| 197 | + "id": "a5da9f28", |
181 | 198 | "metadata": {}, |
182 | 199 | "source": [ |
183 | 200 | "When a test fails, it prints out information about what was expected:\n", |
|
201 | 218 | { |
202 | 219 | "cell_type": "code", |
203 | 220 | "execution_count": null, |
| 221 | + "id": "d3192bb1", |
204 | 222 | "metadata": {}, |
205 | 223 | "outputs": [], |
206 | 224 | "source": [ |
|
209 | 227 | }, |
210 | 228 | { |
211 | 229 | "cell_type": "markdown", |
| 230 | + "id": "80ef89a5", |
212 | 231 | "metadata": {}, |
213 | 232 | "source": [ |
214 | 233 | "You can even test that exceptions are raised:" |
|
217 | 236 | { |
218 | 237 | "cell_type": "code", |
219 | 238 | "execution_count": null, |
| 239 | + "id": "4d653100", |
220 | 240 | "metadata": {}, |
221 | 241 | "outputs": [], |
222 | 242 | "source": [ |
|
226 | 246 | }, |
227 | 247 | { |
228 | 248 | "cell_type": "markdown", |
| 249 | + "id": "5604458b", |
229 | 250 | "metadata": {}, |
230 | 251 | "source": [ |
231 | 252 | "...and test that things are printed to stdout:" |
|
234 | 255 | { |
235 | 256 | "cell_type": "code", |
236 | 257 | "execution_count": null, |
| 258 | + "id": "acfc8b62", |
237 | 259 | "metadata": {}, |
238 | 260 | "outputs": [], |
239 | 261 | "source": [ |
|
242 | 264 | }, |
243 | 265 | { |
244 | 266 | "cell_type": "markdown", |
| 267 | + "id": "dd9f4fd9", |
245 | 268 | "metadata": {}, |
246 | 269 | "source": [ |
247 | 270 | "### Foundations" |
248 | 271 | ] |
249 | 272 | }, |
250 | 273 | { |
251 | 274 | "cell_type": "markdown", |
| 275 | + "id": "fdc66d40", |
252 | 276 | "metadata": {}, |
253 | 277 | "source": [ |
254 | 278 | "fast.ai is unusual in that we often use [mixins](https://en.wikipedia.org/wiki/Mixin) in our code. Mixins are widely used in many programming languages, such as Ruby, but not so much in Python. We use mixins to attach new behavior to existing libraries, or to allow modules to add new behavior to our own classes, such as in extension modules. One useful example of a mixin we define is `Path.ls`, which lists a directory and returns an `L` (an extended list class which we'll discuss shortly):" |
|
257 | 281 | { |
258 | 282 | "cell_type": "code", |
259 | 283 | "execution_count": null, |
| 284 | + "id": "53cd9c94", |
260 | 285 | "metadata": {}, |
261 | 286 | "outputs": [ |
262 | 287 | { |
|
277 | 302 | }, |
278 | 303 | { |
279 | 304 | "cell_type": "markdown", |
| 305 | + "id": "ae689ed3", |
280 | 306 | "metadata": {}, |
281 | 307 | "source": [ |
282 | 308 | "You can easily add you own mixins with the `patch` [decorator](https://realpython.com/primer-on-python-decorators/), which takes advantage of Python 3 [function annotations](https://www.python.org/dev/peps/pep-3107/#parameters) to say what class to patch:" |
|
285 | 311 | { |
286 | 312 | "cell_type": "code", |
287 | 313 | "execution_count": null, |
| 314 | + "id": "1180fdcd", |
288 | 315 | "metadata": {}, |
289 | 316 | "outputs": [ |
290 | 317 | { |
|
307 | 334 | }, |
308 | 335 | { |
309 | 336 | "cell_type": "markdown", |
| 337 | + "id": "065b3f6f", |
310 | 338 | "metadata": {}, |
311 | 339 | "source": [ |
312 | 340 | "We also use `**kwargs` frequently. In python `**kwargs` in a parameter like means \"*put any additional keyword arguments into a dict called `kwargs`*\". Normally, using `kwargs` makes an API quite difficult to work with, because it breaks things like tab-completion and popup lists of signatures. `utils` provides `use_kwargs` and `delegates` to avoid this problem. See our [detailed article on delegation](https://www.fast.ai/2019/08/06/delegation/) on this topic.\n", |
|
317 | 345 | { |
318 | 346 | "cell_type": "code", |
319 | 347 | "execution_count": null, |
| 348 | + "id": "b242359e", |
320 | 349 | "metadata": {}, |
321 | 350 | "outputs": [ |
322 | 351 | { |
|
344 | 373 | }, |
345 | 374 | { |
346 | 375 | "cell_type": "markdown", |
| 376 | + "id": "8e379de8", |
347 | 377 | "metadata": {}, |
348 | 378 | "source": [ |
349 | 379 | "Looking at that `ProductPage` example, it's rather verbose and duplicates a lot of attribute names, which can lead to bugs later if you change them only in one place. `fastcore` provides `store_attr` to simplify this common pattern. It also provides `basic_repr` to give simple objects a useful `repr`:" |
|
352 | 382 | { |
353 | 383 | "cell_type": "code", |
354 | 384 | "execution_count": null, |
| 385 | + "id": "f7223633", |
355 | 386 | "metadata": {}, |
356 | 387 | "outputs": [ |
357 | 388 | { |
|
375 | 406 | }, |
376 | 407 | { |
377 | 408 | "cell_type": "markdown", |
| 409 | + "id": "820715ec", |
378 | 410 | "metadata": {}, |
379 | 411 | "source": [ |
380 | 412 | "One of the most interesting `fastcore` functions is the `funcs_kwargs` decorator. This allows class behavior to be modified without sub-classing. This can allow folks that aren't familiar with object-oriented programming to customize your class more easily. Here's an example of a class that uses `funcs_kwargs`:" |
|
383 | 415 | { |
384 | 416 | "cell_type": "code", |
385 | 417 | "execution_count": null, |
| 418 | + "id": "4a0e346e", |
386 | 419 | "metadata": {}, |
387 | 420 | "outputs": [ |
388 | 421 | { |
|
405 | 438 | }, |
406 | 439 | { |
407 | 440 | "cell_type": "markdown", |
| 441 | + "id": "cbe09f40", |
408 | 442 | "metadata": {}, |
409 | 443 | "source": [ |
410 | 444 | "The `assert not kwargs` above is used to ensure that the user doesn't pass an unknown parameter (i.e one that's not in `_methods`). `fastai` uses `funcs_kwargs` in many places, for instance, you can customize any part of a `DataLoader` by passing your own methods.\n", |
|
416 | 450 | }, |
417 | 451 | { |
418 | 452 | "cell_type": "markdown", |
| 453 | + "id": "df95dbde", |
419 | 454 | "metadata": {}, |
420 | 455 | "source": [ |
421 | 456 | "### L" |
422 | 457 | ] |
423 | 458 | }, |
424 | 459 | { |
425 | 460 | "cell_type": "markdown", |
| 461 | + "id": "effd869a", |
426 | 462 | "metadata": {}, |
427 | 463 | "source": [ |
428 | 464 | "Like most languages, Python allows for very concise syntax for some very common types, such as `list`, which can be constructed with `[1,2,3]`. Perl's designer Larry Wall explained the reasoning for this kind of syntax:\n", |
|
435 | 471 | { |
436 | 472 | "cell_type": "code", |
437 | 473 | "execution_count": null, |
| 474 | + "id": "c2a76a2d", |
438 | 475 | "metadata": {}, |
439 | 476 | "outputs": [ |
440 | 477 | { |
|
454 | 491 | }, |
455 | 492 | { |
456 | 493 | "cell_type": "markdown", |
| 494 | + "id": "8c2adcc9", |
457 | 495 | "metadata": {}, |
458 | 496 | "source": [ |
459 | 497 | "The first thing to notice is that an `L` object includes in its representation its number of elements; that's the `(#3)` in the output above. If there's more than 10 elements, it will automatically truncate the list:" |
|
462 | 500 | { |
463 | 501 | "cell_type": "code", |
464 | 502 | "execution_count": null, |
| 503 | + "id": "5987aef4", |
465 | 504 | "metadata": {}, |
466 | 505 | "outputs": [ |
467 | 506 | { |
|
482 | 521 | }, |
483 | 522 | { |
484 | 523 | "cell_type": "markdown", |
| 524 | + "id": "461ab0e4", |
485 | 525 | "metadata": {}, |
486 | 526 | "source": [ |
487 | 527 | "`L` contains many of the same indexing ideas that NumPy's `array` does, including indexing with a list of indexes, or a boolean mask list:" |
|
490 | 530 | { |
491 | 531 | "cell_type": "code", |
492 | 532 | "execution_count": null, |
| 533 | + "id": "754c0de2", |
493 | 534 | "metadata": {}, |
494 | 535 | "outputs": [ |
495 | 536 | { |
|
509 | 550 | }, |
510 | 551 | { |
511 | 552 | "cell_type": "markdown", |
| 553 | + "id": "a1171e2d", |
512 | 554 | "metadata": {}, |
513 | 555 | "source": [ |
514 | 556 | "It also contains other methods used in `array`, such as `L.argwhere`:" |
|
517 | 559 | { |
518 | 560 | "cell_type": "code", |
519 | 561 | "execution_count": null, |
| 562 | + "id": "bbe0cd97", |
520 | 563 | "metadata": {}, |
521 | 564 | "outputs": [ |
522 | 565 | { |
|
536 | 579 | }, |
537 | 580 | { |
538 | 581 | "cell_type": "markdown", |
| 582 | + "id": "5192093b", |
539 | 583 | "metadata": {}, |
540 | 584 | "source": [ |
541 | 585 | "As you can see from this example, `fastcore` also includes a number of features that make a functional style of programming easier, such as a full range of boolean functions (e.g `ge`, `gt`, etc) which give the same answer as the functions from Python's `operator` module if given two parameters, but return a [curried function](https://en.wikipedia.org/wiki/Currying) if given one parameter.\n", |
|
546 | 590 | { |
547 | 591 | "cell_type": "code", |
548 | 592 | "execution_count": null, |
| 593 | + "id": "e00419dc", |
549 | 594 | "metadata": {}, |
550 | 595 | "outputs": [ |
551 | 596 | { |
|
566 | 611 | { |
567 | 612 | "cell_type": "code", |
568 | 613 | "execution_count": null, |
| 614 | + "id": "75ff9241", |
569 | 615 | "metadata": {}, |
570 | 616 | "outputs": [], |
571 | 617 | "source": [] |
572 | 618 | } |
573 | 619 | ], |
574 | | - "metadata": { |
575 | | - "jupytext": { |
576 | | - "split_at_heading": true |
577 | | - }, |
578 | | - "kernelspec": { |
579 | | - "display_name": "python3", |
580 | | - "language": "python", |
581 | | - "name": "python3" |
582 | | - } |
583 | | - }, |
| 620 | + "metadata": {}, |
584 | 621 | "nbformat": 4, |
585 | | - "nbformat_minor": 4 |
| 622 | + "nbformat_minor": 5 |
586 | 623 | } |
0 commit comments