summaryrefslogtreecommitdiffstats
path: root/2025/captions/emacsconf-2025-python--interactive-python-programming-in-emacs--david-vujic--main.vtt
blob: d63a36c89d0552400b7f6854807f48736870a1de (plain) (blame)
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
WEBVTT captioned by sachac

00:00:00.000 --> 00:00:04.439
Okay, so welcome to this session about interactive Python

00:00:04.440 --> 00:00:09.679
programming. My name is David Vujic and I live and work in

00:00:09.680 --> 00:00:15.319
Stockholm, Sweden.  a developer and today I focus

00:00:15.320 --> 00:00:20.439
mainly on Python software development. So I do this at work

00:00:20.440 --> 00:00:25.999
and I also do this on my spare time in my open source projects.

00:00:26.000 --> 00:00:30.479
Before that, I've been part of the Lisp community. I've

00:00:30.480 --> 00:00:33.700
been a Clojure developer, and also, like, way back,

00:00:33.701 --> 00:00:40.279
I was in the Microsoft world and developed C# and .NET stuff.

00:00:40.280 --> 00:00:45.999
What I've been doing lately is to try to improve the

00:00:46.000 --> 00:00:52.399
developer experience when you write Python code. So what I

00:00:52.400 --> 00:00:56.159
want to talk about is this, but also I want to begin with

00:00:56.160 --> 00:01:00.839
feedback loops because I think it's very related to this

00:01:00.840 --> 00:01:05.359
interactive programming style, like having this nice

00:01:05.360 --> 00:01:07.067
feedback when you write code.

00:01:07.068 --> 00:01:10.533
So I'm going to begin with that.

NOTE Feedback loops

00:01:10.534 --> 00:01:14.199
So this image, you know, this circle is supposed to be a

00:01:14.200 --> 00:01:19.879
visualization of a feedback loop. Let's say we write our

00:01:19.880 --> 00:01:25.239
code and then we deploy it to production. Then when it's

00:01:25.240 --> 00:01:29.639
running there, we can check if things work, or if maybe someone

00:01:29.640 --> 00:01:35.319
else will let us know. Maybe our customers will let us know.

00:01:35.320 --> 00:01:39.639
That's a pretty slow feedback loop with potential risks of

00:01:39.640 --> 00:01:41.867
damaging your business or whatever.

00:01:41.868 --> 00:01:44.167
This is obvious, of course.

00:01:44.168 --> 00:01:50.000
So a faster feedback loop probably is to have

00:01:50.001 --> 00:01:54.066
some kind of automation when you do commits

00:01:54.067 --> 00:01:59.733
or maybe you have this pull request things and even reviews.

00:01:59.734 --> 00:02:02.933
So maybe not always as fast as deploy,

00:02:02.934 --> 00:02:05.839
don't deploy directly to production, but

00:02:05.840 --> 00:02:10.539
it's probably safer and often you get this automated

00:02:10.540 --> 00:02:16.199
feedback faster anyway. But it's still kind of slow. You

00:02:16.200 --> 00:02:20.239
have to wait. You have to push things to GitHub maybe and

00:02:20.240 --> 00:02:24.279
wait. So there's faster ways for sure to get feedback.

00:02:24.280 --> 00:02:27.967
So a much faster way is to write code,

00:02:27.968 --> 00:02:31.367
and write some unit tests, and run those unit tests.

00:02:31.368 --> 00:02:33.467
So then you do everything on your local machine

00:02:33.468 --> 00:02:39.039
and you will fairly quickly learn if your code does

00:02:39.040 --> 00:02:47.159
what you think it does or if it doesn't. I want to zoom in to

00:02:47.160 --> 00:02:55.999
this test write code and test flow a bit. Let's do that.

NOTE Test-driven development

00:02:56.000 --> 00:02:59.759
As a developer, I have used a thing called test-driven

00:02:59.760 --> 00:03:05.999
development for quite some time. I find that this way of

00:03:06.000 --> 00:03:11.259
working is very fast when it comes to getting feedback on

00:03:11.260 --> 00:03:14.519
what your code does and how you should continue the

00:03:14.520 --> 00:03:19.980
development. So, test-driven development,

00:03:19.981 --> 00:03:24.220
basically that you start writing a test for

00:03:24.221 --> 00:03:27.020
something that you want to develop, and then you continue

00:03:27.021 --> 00:03:31.019
developing that, and then you go back to the test, and modify

00:03:31.020 --> 00:03:35.079
and modify the code, and you go back and forth between the

00:03:35.080 --> 00:03:36.959
tests and the code.

00:03:36.960 --> 00:03:44.419
It's sort of like a ping-pong game. I find this very

00:03:44.420 --> 00:03:50.519
effective when you want to get feedback and to know how to

00:03:50.520 --> 00:03:57.233
continue the development. The most important thing

00:03:57.234 --> 00:04:01.700
that I feel is that you know what the code does.

00:04:01.701 --> 00:04:05.559
You learn very quickly.

NOTE REPL-driven development

00:04:05.560 --> 00:04:12.199
Let's zoom into this TDD flow a little bit. The last couple of

00:04:12.200 --> 00:04:17.379
years, I've been doing a slightly different thing which is

00:04:17.380 --> 00:04:21.979
called REPL-driven development. REPL-driven

00:04:21.980 --> 00:04:25.719
development is very similar to test-driven development,

00:04:25.720 --> 00:04:31.159
but I find it even quicker. You get feedback even quicker

00:04:31.160 --> 00:04:34.979
than with a regular TDD setup. So REPL-driven development

00:04:34.980 --> 00:04:41.199
is about writing and evaluating code in a REPL basically.

00:04:41.200 --> 00:04:46.839
And you can do experiments and you can refactor and

00:04:46.840 --> 00:04:51.699
re-evaluate and you get instant feedback on what the code

00:04:51.700 --> 00:04:54.799
does and what you need to change. So I think that's even

00:04:54.800 --> 00:04:59.519
faster than test-driven development.

00:04:59.520 --> 00:05:02.899
Okay, REPL driven development. Let's go back. What's the

00:05:02.900 --> 00:05:10.759
REPL? Most of developers know what a REPL is. The most common

00:05:10.760 --> 00:05:16.399
setup is you open this shell and you use the REPL for your

00:05:16.400 --> 00:05:19.359
programming language. In this case I'm using the Python

00:05:19.360 --> 00:05:25.619
REPL or the IPython REPL which is an enhanced REPL for Python

00:05:25.620 --> 00:05:30.679
development. So what happens here is that we start a REPL

00:05:30.680 --> 00:05:34.919
session in isolation. So this session knows about the

00:05:34.920 --> 00:05:38.119
Python environment. So it knows about the Python language

00:05:38.120 --> 00:05:42.359
basically. So as soon as we start writing things, adding

00:05:42.360 --> 00:05:47.359
variables or creating writing functions or even doing

00:05:47.360 --> 00:05:51.679
imports. Then the session will be more and more aware of the

00:05:51.680 --> 00:05:55.819
code so we will add things to the to the session and then that

00:05:55.820 --> 00:06:00.519
means that we can run functions we can print out these

00:06:00.520 --> 00:06:05.859
variables and things like that. But with REPL driven

00:06:05.860 --> 00:06:09.839
development it's not really that well at least not what I

00:06:09.840 --> 00:06:14.039
mean with REPL driven development. So what I'm thinking of

00:06:14.040 --> 00:06:19.639
is that you are in your code editor where you have your

00:06:19.640 --> 00:06:22.799
autocomplete, and you have your syntax highlighting and

00:06:22.800 --> 00:06:30.459
your favorite theme, color theme, and all of those things. But

00:06:30.460 --> 00:06:34.979
instead, you have this running REPL in the background or in a

00:06:34.980 --> 00:06:41.139
smaller window or buffer. So that means that you write code

00:06:41.140 --> 00:06:45.319
and you can send that code to the running REPL, to the REPL

00:06:45.320 --> 00:06:50.399
session. You write and do everything as you would do when

00:06:50.400 --> 00:06:55.219
writing your code basically. In this case, in this

00:06:55.220 --> 00:07:00.599
example, I have evaluated these two functions. I've sent

00:07:00.600 --> 00:07:05.819
them to the REPL session so it's aware of these functions.

00:07:05.820 --> 00:07:10.399
Then I switched to a separate different module and

00:07:10.400 --> 00:07:14.039
evaluated that one. So the REPL session now knows about

00:07:14.040 --> 00:07:19.039
these two functions and also these two variables. That

00:07:19.040 --> 00:07:23.999
means that I can evaluate the state of those variables and

00:07:24.000 --> 00:07:28.999
change code and re-evaluate and things like that. So in this

00:07:29.000 --> 00:07:33.639
example if you look in the smaller area there you see that I

00:07:33.640 --> 00:07:39.639
have evaluated this res variable on line 6 and the output was

00:07:39.640 --> 00:07:42.399
that it's a dictionary with two keys and two values

00:07:42.400 --> 00:07:51.219
basically. So this setup works in basically any of your

00:07:51.220 --> 00:07:54.079
favorite code editors. So you can do this in Visual Studio

00:07:54.080 --> 00:08:01.239
Code, you can do this in PyCharm or Vim. But what I have done is

00:08:01.240 --> 00:08:07.119
that... More like what I have missed is that when I write code

00:08:07.120 --> 00:08:10.239
and do this evaluation, this is really cool, but then I need

00:08:10.240 --> 00:08:15.459
to switch context if I want to see the result. I have to switch

00:08:15.460 --> 00:08:21.979
context to this other window. I

00:08:21.980 --> 00:08:25.759
have my focus on the code and then I have to look in a different

00:08:25.760 --> 00:08:31.799
place to know the results. And if it's a larger output, then

00:08:31.800 --> 00:08:37.479
maybe I need to scroll. So I wanted to find out if it was

00:08:37.480 --> 00:08:43.479
possible to make this even smoother and faster, this

00:08:43.480 --> 00:08:45.479
feedback loop even faster, so I don't have to switch

00:08:45.480 --> 00:08:52.119
context. What I've done here is that... I can select a row or a

00:08:52.120 --> 00:08:58.079
region and I can evaluate and then an overlay, a small pop-up

00:08:58.080 --> 00:09:03.119
shows up with the evaluated result right next to it. So I can

00:09:03.120 --> 00:09:07.519
change code and re-evaluate and quickly see the result of it

00:09:07.520 --> 00:09:12.640
without doing this context switching. So the way I've done

00:09:12.641 --> 00:09:20.679
it is that I wanted to reuse the existing tooling that I

00:09:20.680 --> 00:09:27.739
already had. I know that my in-editor REPL, the IPython

00:09:27.740 --> 00:09:31.559
REPL, already does this evaluation. So I figured maybe I can

00:09:31.560 --> 00:09:35.359
extract the data and do this visualization as a separate

00:09:35.360 --> 00:09:40.839
thing. That's how I've done it. What I've done is that

00:09:40.840 --> 00:09:47.199
I've created this overlay and placed it where my cursor

00:09:47.200 --> 00:09:50.859
currently is, right next to the code. Then I've

00:09:50.860 --> 00:09:55.719
extracted the evaluated result and put it in this overlay.

00:09:55.720 --> 00:10:01.039
I also want this overlay to have this nice looking syntax,

00:10:01.040 --> 00:10:04.759
so I've set it to this Python mode, so we get this syntax

00:10:04.760 --> 00:10:10.559
highlighting. Make it look very readable. And as a nice

00:10:10.560 --> 00:10:16.879
developer experience thing,

00:10:16.880 --> 00:10:20.379
when you move the cursor, of course you don't want the

00:10:20.380 --> 00:10:25.679
overlay to be there. You want it to disappear. So those kinds

00:10:25.680 --> 00:10:28.999
of things I've added. So putting the overlay at the right

00:10:29.000 --> 00:10:33.279
place and feed it with the evaluated data and then make it

00:10:33.280 --> 00:10:39.839
disappear when it's not interesting to look at anymore.

00:10:39.840 --> 00:10:44.639
What I've described so far is something that I use on a

00:10:44.640 --> 00:10:50.639
daily basis, and it covers most of my needs while doing Python

00:10:50.640 --> 00:10:56.119
development. But one thing I still miss, and I miss it from my

00:10:56.120 --> 00:11:03.479
days as a Clojure developer, because over there we could

00:11:03.480 --> 00:11:07.919
have a running app on our local machine and we can have our

00:11:07.920 --> 00:11:12.719
editor, and the app and the editor were connected. So when I

00:11:12.720 --> 00:11:17.199
did some changes in the code, the app would change without

00:11:17.200 --> 00:11:20.559
any restarts or anything like that. And the same if I would

00:11:20.560 --> 00:11:24.679
change the state of the app, I can inspect the state from the

00:11:24.680 --> 00:11:28.919
code. So they were connected. They are connected. So I was

00:11:28.920 --> 00:11:32.839
thinking, hey, this would be really cool if we could have

00:11:32.840 --> 00:11:39.199
something like this in Python. And that reminded me of

00:11:39.200 --> 00:11:43.839
Jupyter and Jupyter notebooks because I think notebooks,

00:11:43.840 --> 00:11:49.659
the way you do things there, is very similar to what I was

00:11:49.660 --> 00:11:56.879
trying to achieve. So I was reading up a little bit on how this

00:11:56.880 --> 00:12:00.919
notebook thing works. It turns out that a notebook is a

00:12:00.920 --> 00:12:05.279
client that talks to a server, that communicates with a

00:12:05.280 --> 00:12:08.799
server. It's on the server that all this Python

00:12:08.800 --> 00:12:14.159
evaluation and all this thing happens. Then what I've

00:12:14.160 --> 00:12:19.659
done is that instead of starting up IPython in my editor, I

00:12:19.660 --> 00:12:23.519
start the Jupyter console instead. And then I can give it

00:12:23.520 --> 00:12:27.159
that unique ID and it will be connected to that running

00:12:27.160 --> 00:12:30.919
kernel.

NOTE FastAPI CRUD

00:12:30.920 --> 00:12:37.199
In this example, I've created this FastAPI CRUD app that

00:12:37.200 --> 00:12:41.919
has this create, read, update, and delete endpoints. It

00:12:41.920 --> 00:12:46.399
has this, it's locally running, it has this database where

00:12:46.400 --> 00:12:51.639
you can do all these things. I'm running this FastAPI app

00:12:51.640 --> 00:12:58.059
in the kernel and then I've connected to, I've connected to

00:12:58.060 --> 00:13:03.239
the kernel in my editor too. Both of them are connected to

00:13:03.240 --> 00:13:09.719
the kernel. What I do now is that I want to initially create

00:13:09.720 --> 00:13:15.239
some data. I'm going to add this, creating this message.

00:13:15.240 --> 00:13:19.899
What I get back is a message ID. I want to experiment in

00:13:19.900 --> 00:13:24.359
my browser. What do I get with that message ID? I'm

00:13:24.360 --> 00:13:30.239
evaluating the read function. I instantly get this

00:13:30.240 --> 00:13:34.779
evaluated result, which was this hello world text. So what

00:13:34.780 --> 00:13:39.919
happens if I do some changes in this app? I'm going to grab

00:13:39.920 --> 00:13:49.659
this message ID and write something else.

00:13:49.660 --> 00:13:53.759
Now I can evaluate the same thing again, and you can see that

00:13:53.760 --> 00:14:02.399
the content has changed to this new value. My editor isn't

00:14:02.400 --> 00:14:07.719
in any debug mode or something like that. It doesn't know

00:14:07.720 --> 00:14:11.239
what database it is. It doesn't have any environment

00:14:11.240 --> 00:14:14.479
variables set up or something like that. It is only

00:14:14.480 --> 00:14:17.599
connected to the kernel, and the kernel is aware of that. It's

00:14:17.600 --> 00:14:20.479
running the app. It has the connection strings and

00:14:20.480 --> 00:14:28.799
everything that is needed. So that's how this thing works.

00:14:28.800 --> 00:14:34.199
Now I want to do some inline hacking because I want to store

00:14:34.200 --> 00:14:37.799
this input that is sent from this app because I want to work

00:14:37.800 --> 00:14:42.039
with it afterwards. I can add this dictionary that stores

00:14:42.040 --> 00:14:48.759
this message. I'm updating the source code of this app, and

00:14:48.760 --> 00:15:03.079
when I run any of these endpoints again, you will see that

00:15:03.080 --> 00:15:08.759
the state changes, and the new inputs, I can grab and I can use

00:15:08.760 --> 00:15:14.399
them for quick evaluation or testing. This example is

00:15:14.400 --> 00:15:18.519
really simple. It was just an integer. For example, if you

00:15:18.520 --> 00:15:23.519
are sending a more complex object, maybe a pydantic schema

00:15:23.520 --> 00:15:28.199
or something, and you want to inspect what's coming in, and if

00:15:28.200 --> 00:15:34.199
you have some sort of validation that you want to test out.

00:15:34.200 --> 00:15:38.399
The configuration or the code that I wrote to make this work

00:15:38.400 --> 00:15:44.159
is a little bit different than just adding an overlay. I'm

00:15:44.160 --> 00:15:50.999
using this overlay just like with the IPython example, but in

00:15:51.000 --> 00:15:57.839
this case, when I change code, I have to think about where that

00:15:57.840 --> 00:16:02.159
code lives, because it's the app that runs the code. So it's

00:16:02.160 --> 00:16:07.039
in the app context I need to manipulate with the data. If you

00:16:07.040 --> 00:16:11.919
have started the app from maybe a main function and that

00:16:11.920 --> 00:16:17.879
module imports namespaces, then you need to, if you want to

00:16:17.880 --> 00:16:22.359
update a function or something like that, you need to update

00:16:22.360 --> 00:16:26.679
it in the correct namespace. What I did before in IPython

00:16:26.680 --> 00:16:29.919
by adding and changing things, everything ends up in the

00:16:29.920 --> 00:16:34.439
global namespace. But here, if you want the app to actually

00:16:34.440 --> 00:16:38.479
react to the changes, you need to put it in the right

00:16:38.480 --> 00:16:43.479
namespace. So that's what I do here. I do some lookups, where

00:16:43.480 --> 00:16:49.139
is this function, and then I do this reload of this function or

00:16:49.140 --> 00:16:54.799
module. And when I was developing this, I was thinking, hey,

00:16:54.800 --> 00:16:59.319
this is really ugly. I'm in this REPL and do some

00:16:59.320 --> 00:17:03.559
manipulation of the imports and things like that. That

00:17:03.560 --> 00:17:09.759
didn't feel good. Then I was reminded of the IPython. And

00:17:09.760 --> 00:17:15.519
IPython has this feature to reload any updated

00:17:15.520 --> 00:17:19.119
submodules. I was curious how do they do it. I looked in the

00:17:19.120 --> 00:17:24.079
IPython source code and saw that they also use importlib and

00:17:24.080 --> 00:17:28.359
reloading of this module. Once I've learned that, then I

00:17:28.360 --> 00:17:32.599
stopped thinking that my code was hacky. I thought it was

00:17:32.600 --> 00:17:37.159
good enough at least.

NOTE Testing with an LLM

00:17:37.160 --> 00:17:45.059
But one thing that has bothered me for a long time is I quite

00:17:45.060 --> 00:17:50.199
often want to test out and evaluate individual rows that

00:17:50.200 --> 00:17:58.559
lives in a function. Quite often, this code uses the input

00:17:58.560 --> 00:18:02.639
to that function like the input parameters. To be able to

00:18:02.640 --> 00:18:07.719
do that, I need to manually type some fake data and set it to

00:18:07.720 --> 00:18:12.279
this variable, and then I can evaluate the code. But I think

00:18:12.280 --> 00:18:17.779
that takes... That slows me down. I was thinking, maybe I can

00:18:17.780 --> 00:18:23.439
do this in a quicker way, so I have this quicker feedback, so I

00:18:23.440 --> 00:18:27.933
can run this or evaluate this code much quicker.

00:18:27.934 --> 00:18:29.439
So my idea was maybe I

00:18:29.440 --> 00:18:35.239
can use an LLM for this. If I give it the parameters, maybe it

00:18:35.240 --> 00:18:41.119
can return some random data so I don't have to write it

00:18:41.120 --> 00:18:44.119
myself. I ended up doing that. I have this source code.

00:18:44.120 --> 00:18:50.399
I'm loading the REPL with the code. Then I select this

00:18:50.400 --> 00:18:56.719
function name and the parameters with its data type. I

00:18:56.720 --> 00:19:02.839
have this prompt that instructs the LLM to come up with fake

00:19:02.840 --> 00:19:06.239
data based on the tag name and on the data type. And then I can

00:19:06.240 --> 00:19:10.099
send that to the REPL. I do that with a key command. Then

00:19:10.100 --> 00:19:16.019
I can proceed by running the code within the function that

00:19:16.020 --> 00:19:21.719
uses these inputs. This works for all the data types. If

00:19:21.720 --> 00:19:26.279
there's a custom data type, you need to give the LLM extra

00:19:26.280 --> 00:19:30.399
context. So that's something to think about. Once it knows

00:19:30.400 --> 00:19:35.679
the context, it can generate this fake data that very often is

00:19:35.680 --> 00:19:39.839
good enough just to test out, you know, like I've done here, like

00:19:39.840 --> 00:19:45.399
string... sorry, list destructuring and parsing and things

00:19:45.400 --> 00:19:51.879
like that. I think that was all I had, and thank you for

00:19:51.880 --> 00:19:52.920
listening!