diff options
Diffstat (limited to '2025/info/swanky-after.md')
| -rw-r--r-- | 2025/info/swanky-after.md | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/2025/info/swanky-after.md b/2025/info/swanky-after.md index 9386bdb6..53c84315 100644 --- a/2025/info/swanky-after.md +++ b/2025/info/swanky-after.md @@ -1,6 +1,386 @@ <!-- Automatically generated by emacsconf-publish-after-page --> +<div class="transcript transcript-mainVideo"><a name="swanky-mainVideo-transcript"></a><h1>Transcript</h1> + +[[!template text="""Hello everyone, I'm Scott""" start="00:00:00.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and I'll be talking about Swanky Python,""" start="00:00:02.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which is a development environment for Python""" start="00:00:04.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""based on Emacs' Slime package.""" start="00:00:06.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So what is that and why might you find it interesting?""" start="00:00:08.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""SLIME is the Superior Lisp Interaction Mode for Emacs.""" start="00:00:11.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""It's an Emacs package for developing Common Lisp,""" start="00:00:15.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and it's a bit different from the way we develop most languages""" start="00:00:18.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""in that you're always connected""" start="00:00:20.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""to a running instance of your application,""" start="00:00:22.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and you kind of build up your application, piece by piece,""" start="00:00:25.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""modifying one expression at a time""" start="00:00:27.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""without ever having to restart your application.""" start="00:00:30.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So why might you want to develop this way?""" start="00:00:34.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""One advantage is that you can get a faster feedback loop.""" start="00:00:36.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""For some kinds of software, it doesn't make a big difference.""" start="00:00:40.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Like, if you're developing a web backend""" start="00:00:42.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""where all state is stored externally in a database,""" start="00:00:43.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""then you can have a file watcher""" start="00:00:48.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that just restarts the whole Python process""" start="00:00:50.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""whenever you make any edit,""" start="00:00:52.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and you're not really losing anything,""" start="00:00:54.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because all the state is stored outside the Python process""" start="00:00:56.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""in a database. So it works great.""" start="00:00:59.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But for other kinds of software, like""" start="00:01:01.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""let's say you're developing an Emacs package""" start="00:01:03.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""or a video game,""" start="00:01:05.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""then it can be a real pain to restart the application""" start="00:01:07.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and recreate the state it was in before""" start="00:01:10.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""just to test the effect of each edit you want to make.""" start="00:01:12.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Another advantage is the runtime introspection you have available.""" start="00:01:17.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So since you're always connected""" start="00:01:21.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""to a running instance of your application,""" start="00:01:22.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you can inspect the values of variables,""" start="00:01:25.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you can trace functions, and all sorts of other information""" start="00:01:27.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""to help you understand your application better.""" start="00:01:30.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And lastly, it's just a lot of fun to develop this way,""" start="00:01:36.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""or at least I find it fun developing with SLIME,""" start="00:01:39.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so I wrote a SLIME backend for Python""" start="00:01:43.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so I could have more fun when I'm coding in Python.""" start="00:01:45.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""As for the name swanky-python, within SLIME,""" start="00:01:48.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""swank is the name of the Common Lisp backend""" start="00:01:52.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that runs within your Common Lisp application""" start="00:01:56.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and connects to Emacs. So I'm not too creative.""" start="00:01:59.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""swanky-python is just a swank implementation in Python.""" start="00:02:02.920" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Demo""" start="00:02:08.000" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""So let's see it in action. So we started up with M-x slime.""" start="00:02:08.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And what that does is it starts a Python process,""" start="00:02:15.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""starts swanky-python within it, and connects to it from Emacs.""" start="00:02:19.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And you can configure how exactly it runs Python.""" start="00:02:25.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Or you can start swanky python manually""" start="00:02:29.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""within a Python application running on a remote server""" start="00:02:32.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and forward the port locally""" start="00:02:35.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and connect to it in Emacs, from Emacs remotely.""" start="00:02:36.614" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Within the README, there's more documentation""" start="00:02:40.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""on other ways to start it.""" start="00:02:43.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But just M-x slime is the basic way that works most of the time.""" start="00:02:45.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So within the REPL, the first thing you'll notice is that""" start="00:02:52.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""REPL outputs are clickable buttons,""" start="00:02:55.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""what SLIME calls presentations.""" start="00:02:58.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So you can do things like inspect them.""" start="00:03:02.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And for each presentation, in the Python backend,""" start="00:03:04.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it holds on to the reference to the object.""" start="00:03:09.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So for an int, it's not too interesting,""" start="00:03:12.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but let's do a more complex object like a file.""" start="00:03:14.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Then we can inspect the file.""" start="00:03:20.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can describe it, which will bring up documentation""" start="00:03:22.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""on that class. We can use it in further expressions""" start="00:03:26.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""like if we copy it, it will use the actual Python object""" start="00:03:33.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""in this expression.""" start="00:03:39.432" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can assign it to a variable.""" start="00:03:43.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""SLIME uses presentations everywhere""" start="00:03:48.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that a Python object would be displayed.""" start="00:03:51.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So instead of just their string representation,""" start="00:03:53.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""when you have a backtrace on an exception,""" start="00:03:56.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""or you... within the inspector or anywhere else really,""" start="00:04:00.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""anywhere that the string representation""" start="00:04:03.966" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""of an object would be displayed,""" start="00:04:06.020" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it displays a presentation that you can go on to""" start="00:04:07.941" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""inspect, reuse, or send to the REPL and so on.""" start="00:04:10.741" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""One useful utility function is pp for print presentation.""" start="00:04:14.961" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We haven't imported it yet.""" start="00:04:23.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So when we get a name error exception""" start="00:04:25.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and SLIME sees that that name is available for import somewhere,""" start="00:04:29.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it'll give us the option of importing it.""" start="00:04:33.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Since it's available for import from multiple modules,""" start="00:04:38.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it'll prompt us for which one we want to import it from.""" start="00:04:40.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We want to import it from swanky-python,""" start="00:04:43.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""not from the standard library.""" start="00:04:45.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Then it will print a presentation of that object.""" start="00:04:48.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Within the REPL, this is not really useful""" start="00:04:52.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because all REPL outputs are already presentations.""" start="00:04:55.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But I use this now whenever I would use print debugging,""" start="00:04:58.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""just whenever I would use insert print statements in my program""" start="00:05:02.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""to see what's going on, I have it print a presentation""" start="00:05:05.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because that way I can go back and inspect it later,""" start="00:05:08.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""copy it to the REPL and further manipulate it and so on.""" start="00:05:11.200" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Inspector""" start="00:05:16.600" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's look at the inspector more.""" start="00:05:16.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""If we go back and inspect the file object,""" start="00:05:20.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you can write custom inspector views""" start="00:05:25.580" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for different kinds of objects.""" start="00:05:27.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So far, I just have a couple. One for sequences,""" start="00:05:28.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""one for mappings, and one for every other kind of object.""" start="00:05:32.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Like if we inspect a mapping, there's a shortcut""" start="00:05:36.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""inspect last result, which is what I normally use""" start="00:05:45.980" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""to open the inspector. Then we see the values,""" start="00:05:48.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and each value in the inspector is a presentation""" start="00:05:52.380" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that we can go on to inspect, and so on.""" start="00:05:56.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Let's go back to inspecting the file object.""" start="00:05:58.420" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Again, we can inspect each of the values,""" start="00:06:03.980" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we can copy them back to the REPL and so on.""" start="00:06:06.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""It just displays all the attributes for the class""" start="00:06:10.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and their values.""" start="00:06:13.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can configure what attributes we want to show.""" start="00:06:15.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""There's a transient menu where we can toggle""" start="00:06:18.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""if we want to show private attributes, dunder attributes,""" start="00:06:21.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""doc strings, so on, or everything,""" start="00:06:23.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which is a bit much to show by default.""" start="00:06:26.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So we'll reset it to the default.""" start="00:06:28.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""In the future, I want to add graphical inspector views""" start="00:06:33.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for different kinds of objects, and also support""" start="00:06:37.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""showing plots in both the inspector and the REPL,""" start="00:06:40.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but that's future work I haven't started on yet.""" start="00:06:43.000" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Evaluating Python""" start="00:06:47.720" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Let's look at the different options for evaluating Python.""" start="00:06:47.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So we can evaluate a whole file.""" start="00:06:52.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can evaluate just a class.""" start="00:06:59.100" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can evaluate just the method we're working on.""" start="00:07:00.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can evaluate a Python statement,""" start="00:07:03.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and it will show the result in an overlay next to the cursor.""" start="00:07:06.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can select some code and just evaluate the highlighted region.""" start="00:07:11.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can sync the REPL to the active file.""" start="00:07:17.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So now everything we evaluate in the REPL will be in the""" start="00:07:24.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""context of the eval_demo module.""" start="00:07:27.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can also set the module that the REPL is in.""" start="00:07:29.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can go back to main.""" start="00:07:35.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But let's go back to the eval_demo module for now.""" start="00:07:38.280" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Updating""" start="00:07:43.680" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""One useful thing is when you update a class or a function,""" start="00:07:43.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it updates old instances of that class or function.""" start="00:07:49.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So right now, f.bar is foobar.""" start="00:07:54.540" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But if we edit that class, it will actually edit the code""" start="00:07:58.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for the old instance of that class.""" start="00:08:03.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And that's provided by code I copied""" start="00:08:05.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""from IPython's autoreload extension.""" start="00:08:07.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""It helps when you're trying to develop in Python""" start="00:08:12.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""without having to restart the Python process""" start="00:08:14.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""whenever you make a change.""" start="00:08:16.499" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Auto reload in Python is a big topic""" start="00:08:20.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that I don't really have time to go into here,""" start="00:08:22.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but right now it is more limited""" start="00:08:26.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""than what is done in Common Lisp.""" start="00:08:29.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Like for example, if you have a data class in Python""" start="00:08:32.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and you add a new field to the data class,""" start="00:08:35.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it won't automatically update old instances""" start="00:08:37.620" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""of the data class with a new field.""" start="00:08:41.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So there's more that needs to be done with that,""" start="00:08:43.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but I am perhaps naively optimistic""" start="00:08:46.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that Python's runtime is quite dynamic and flexible,""" start="00:08:50.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and that I can fully implement autoreload in Python,""" start="00:08:54.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but there's still work to be done,""" start="00:08:59.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and it's a big topic to go into.""" start="00:09:02.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Next up, let's look at the backtrace buffer.""" start="00:09:05.420" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But as it is right now, autoreload is actually useful.""" start="00:09:08.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""I mostly develop in Python without having to restart the process""" start="00:09:12.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and without running into issues from old state""" start="00:09:16.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that hasn't been updated properly.""" start="00:09:19.600" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Backtraces""" start="00:09:22.900" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""So if we go on to look at the backtrace buffer,""" start="00:09:22.900" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""whenever we get an exception in Python...""" start="00:09:26.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Let's go back to it.""" start="00:09:32.820" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Whenever we get an exception, it will...""" start="00:09:37.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""let's change the code so that it actually""" start="00:09:41.420" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""gets an exception...""" start="00:09:43.699" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we will get an interactive backtrace buffer""" start="00:09:49.966" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""where we can browse the source code for the different stack frames""" start="00:09:52.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and the local variables within the stack frames,""" start="00:09:57.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which are all presentations that we can inspect and so on.""" start="00:10:00.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can also open a REPL in the context of any stack frame.""" start="00:10:04.340" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Or we can, when we go to the source for a given stack frame,""" start="00:10:10.620" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we can select some Python code and evaluate it""" start="00:10:16.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""within the context of that stack frame.""" start="00:10:20.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""One major limitation compared to SLIME for Common Lisp""" start="00:10:25.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""is that in Common Lisp, you have the option to""" start="00:10:30.700" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""restart or resume execution from a given stack frame""" start="00:10:33.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""after an exception happens, where in Python,""" start="00:10:38.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""what we have right now is pretty much equivalent to""" start="00:10:42.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""the postmortem debugger.""" start="00:10:45.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You can view the state that the call stack was in""" start="00:10:47.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""at the time of the exception,""" start="00:10:50.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but you can't actually resume execution,""" start="00:10:51.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which you often might want to do,""" start="00:10:55.660" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because when you're coding in a dynamic language,""" start="00:10:57.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you're going to get runtime errors.""" start="00:10:59.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So if you're writing a script that does like some sort of""" start="00:11:01.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""long-running computation or processes a ton of files""" start="00:11:04.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and gets an exception parsing one file halfway through,""" start="00:11:08.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""normally you'd have to fix the script, and then rerun it""" start="00:11:11.940" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and have it process all the same files all over again,""" start="00:11:16.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and lose a bunch of time for every bug you run into""" start="00:11:19.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and fix you have to make.""" start="00:11:23.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So right now we've got a kind of mediocre workaround""" start="00:11:24.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which is you can add the restart decorator to a function""" start="00:11:28.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and then... where in the case of a script""" start="00:11:34.020" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""processing a bunch of files,""" start="00:11:37.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you would add the restart decorator to the function""" start="00:11:38.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that processes a single file.""" start="00:11:41.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You'd add it to the function""" start="00:11:43.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that represents kind of the smallest unit of work""" start="00:11:45.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that might fail with an exception,""" start="00:11:47.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Then, when you get an exception,""" start="00:11:50.220" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you can actually edit the function.""" start="00:11:54.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Like, if we edit it so it doesn't throw an error,""" start="00:11:57.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and then we can resume execution,""" start="00:12:01.020" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""then it will return from foo using the""" start="00:12:07.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""the new version of baz,""" start="00:12:12.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""without having to run the script from the beginning again.""" start="00:12:15.041" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So in the example of a script that processes a bunch of files,""" start="00:12:18.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that would let you,""" start="00:12:22.380" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""as you run into files that cause an exception,""" start="00:12:24.300" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""fix your code to deal with it""" start="00:12:27.620" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and resume execution without having to restart the script""" start="00:12:29.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""from the beginning.""" start="00:12:31.881" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But this is obviously a pretty terrible hack,""" start="00:12:33.081" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""having to add the restart decorator to the function.""" start="00:12:36.121" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""I would like it to be able to restart from any function.""" start="00:12:38.841" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""without needing the decorator, as you can in Common Lisp,""" start="00:12:46.740" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but I think that will require patching CPython""" start="00:12:49.632" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and I really have no idea how to do that.""" start="00:12:54.032" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So if you do know anything about CPython internals""" start="00:12:56.580" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and are interested in helping, please reach out.""" start="00:13:00.532" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""pydumpling""" start="00:13:03.721" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Another feature we have with the backtrace buffer is""" start="00:13:03.721" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""there's this library called PyDumpling""" start="00:13:07.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which can serialize a traceback and store it to a file.""" start="00:13:09.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So you can use PyDumpling with your applications running in""" start="00:13:14.660" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""production to serialize a traceback""" start="00:13:17.860" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""whenever they have an exception and save it to a file.""" start="00:13:21.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Then you can transfer the file locally""" start="00:13:24.900" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and load it into your local Emacs with slime-py-load-pydumpling.""" start="00:13:28.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""This will load the same backtrace buffer,""" start="00:13:38.860" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and you see all the same local variables""" start="00:13:41.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""at the time of the exception.""" start="00:13:44.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You can inspect them and get a REPL""" start="00:13:45.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""in the context of the stack frame.""" start="00:13:48.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Well, this will only work for variables""" start="00:13:51.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that can be serialized with pickle.""" start="00:13:54.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Or actually, the library uses dill,""" start="00:13:57.620" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which can serialize a bit more than pickle can.""" start="00:13:59.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But yeah so this can help you inspect and debug errors""" start="00:14:03.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for applications running in production remotely""" start="00:14:10.201" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that you don't want to have SLIME connected to 24-7.""" start="00:14:12.881" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Documentation browser""" start="00:14:20.060" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's look at the documentation browser.""" start="00:14:20.060" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can bring up documentation for any module,""" start="00:14:24.860" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and all this information is generated""" start="00:14:29.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""from runtime introspection,""" start="00:14:33.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""from the doc strings for the module""" start="00:14:35.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and the classes and so on.""" start="00:14:37.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So you won't see documentation for libraries""" start="00:14:39.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that you don't have actually loaded""" start="00:14:41.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""into your running Python process.""" start="00:14:43.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Then you can go browse to classes.""" start="00:14:45.940" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""It'll show all the attributes, their methods, and so on.""" start="00:14:50.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""By each method to the right, it will show""" start="00:14:54.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""the base class where the method was originally inherited from.""" start="00:14:57.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You can also bring up a screen with all the Python packages""" start="00:15:02.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that are installed, and browse that with imenu,""" start="00:15:09.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and bring up information on any package and so on.""" start="00:15:14.440" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Thread view""" start="00:15:20.360" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's take a look at the thread view.""" start="00:15:20.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So let's run this and then bring up the thread view""" start="00:15:28.500" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and this will show information on all running threads.""" start="00:15:31.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You can configure it to refresh after a given interval,""" start="00:15:35.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""like every second, but I don't have that set up right now,""" start="00:15:38.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so I have to manually refresh it.""" start="00:15:41.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Probably the most useful thing is that""" start="00:15:45.660" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""you can bring up a backtrace for any thread""" start="00:15:47.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which won't pause the thread or anything,""" start="00:15:49.740" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but will just give you the call stack""" start="00:15:51.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""at the time you requested the backtrace.""" start="00:15:53.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""You can again view the stack frames, local variables,""" start="00:15:55.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""open a REPL in the context of the thread, and so on.""" start="00:15:59.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""There's also a viewer for async tasks,""" start="00:16:04.140" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but I'm not going to demo that right now,""" start="00:16:07.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because for that to work, you have to start swanky-python""" start="00:16:10.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""after the async event loop has started,""" start="00:16:14.160" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""from within the same thread.""" start="00:16:16.600" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""If you go to the project readme,""" start="00:16:18.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""there's a demo of how to use the async task viewer""" start="00:16:20.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""with a fastapi project.""" start="00:16:23.920" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Tracing functions""" start="00:16:27.440" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's look at tracing functions.""" start="00:16:27.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So here we got some random error,""" start="00:16:33.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""because this is still very much a work in progress.""" start="00:16:36.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But it looks like it executed""" start="00:16:39.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""correctly this time.""" start="00:16:42.360" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So now let's mark the fibonacci function""" start="00:16:43.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for tracing and execute it.""" start="00:16:47.566" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can see, every time the function is called,""" start="00:16:50.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""all its arguments and return values.""" start="00:16:56.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Again, there are presentations that we can inspect and so on.""" start="00:16:58.240" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But let's inspect a more complex object, like a file object.""" start="00:17:02.900" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""If we trace the count_lines function and run that code,""" start="00:17:06.080" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""then we can inspect the file it was passed, or the file object.""" start="00:17:11.340" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""One pitfall is that in Python, objects are mutable.""" start="00:17:15.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So in the trace buffer, the string representation""" start="00:17:21.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that's printed is the string representation""" start="00:17:25.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""at the time it was passed to the function.""" start="00:17:27.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But when we go to inspect it,""" start="00:17:31.220" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we're inspecting the object as it is right now,""" start="00:17:32.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which can be different than it was at the time""" start="00:17:34.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""the function saw it. So for this file object, for example,""" start="00:17:37.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it's closed now, when it was open at the time""" start="00:17:41.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""the function used it.""" start="00:17:44.280" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""AI integrations""" start="00:17:47.800" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's look at AI integrations.""" start="00:17:47.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So if you're used to SLIME with Common Lisp,""" start="00:17:50.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Emacs actually has a built-in AI that can help with the transition.""" start="00:17:54.520" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So it's just a joke, I actually really like Python.""" start="00:18:09.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""And for more serious AI integrations,""" start="00:18:14.560" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""I have some ideas for the future""" start="00:18:18.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but I haven't implemented anything yet.""" start="00:18:19.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""I think right now, people are mostly passing source code to LLMs""" start="00:18:21.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""but since we're embedded in the Python process at runtime,""" start="00:18:27.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we have a lot of more information available,""" start="00:18:32.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""like maybe we can trace all calls to functions,""" start="00:18:35.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and when we have a bug,""" start="00:18:39.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""we can feed the trace to the LLM,""" start="00:18:41.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and the LLM can point out maybe""" start="00:18:46.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""when this function was called with these arguments,""" start="00:18:48.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""its return value doesn't make sense,""" start="00:18:51.960" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so maybe that's the root cause of your bug.""" start="00:18:53.880" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""If you have any ideas of potential LLM or AI integrations,""" start="00:18:55.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""let me know. I'm happy to discuss.""" start="00:19:02.360" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""LSP-type features""" start="00:19:06.000" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""Next up, let's look at standard LSP-type features.""" start="00:19:06.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So we've got completions. It's fuzzy completions right now,""" start="00:19:09.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so it's showing everything with a PR in the name.""" start="00:19:14.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can bring up documentation for each one.""" start="00:19:16.320" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""When we start calling a method in the minibuffer at the bottom""" start="00:19:21.780" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""it'll show the signature.""" start="00:19:26.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""There's some refactoring available.""" start="00:19:28.860" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""We can extract a function or variable,""" start="00:19:33.720" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""or rename something,""" start="00:19:37.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""like, let's rename fib to fib2,""" start="00:19:39.500" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and it will rename all the uses of it.""" start="00:19:42.920" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""All these features are based on Jedi,""" start="00:19:47.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""which is the Python library used by IPython.""" start="00:19:49.760" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But as it is right now,""" start="00:19:55.400" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""if you want the most complete Python development experience""" start="00:19:57.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""in Emacs, I'd probably recommend using LSP""" start="00:20:02.040" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for everything LSP can do, and then just using swanky-python""" start="00:20:05.580" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""for the object inspector and backtrace buffer,""" start="00:20:10.440" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and the interactive features it has""" start="00:20:13.680" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""that an LSP can't provide.""" start="00:20:15.360" video="mainVideo-swanky" id="subtitle"]] + +<div class="transcript-heading">[[!template new="1" text="""Wrapping up""" start="00:20:18.032" video="mainVideo-swanky" id="subtitle"]]</div>[[!template text="""And that's it really.""" start="00:20:18.032" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""Shortly we'll have questions and answers""" start="00:20:23.340" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""as part of EmacsConf, and later on,""" start="00:20:25.866" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""if you have any questions, ideas, or issues""" start="00:20:28.800" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""feel free to reach out over email""" start="00:20:31.200" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""or create an issue on the repository.""" start="00:20:34.640" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""I should probably warn you,""" start="00:20:38.000" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""if you want to try out the project:""" start="00:20:39.332" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so far I'm probably the only user of it""" start="00:20:41.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""and I've only tested it on my own Emacs setup,""" start="00:20:45.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""so it's quite likely you'll run into issues""" start="00:20:48.280" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""trying to get it installed and working.""" start="00:20:50.840" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""But if you do run into problems, please reach out,""" start="00:20:53.480" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""let me know. I'm happy to help and try and fix them.""" start="00:20:56.120" video="mainVideo-swanky" id="subtitle"]] +[[!template text="""So that's it. Thanks for listening.""" start="00:20:59.280" video="mainVideo-swanky" id="subtitle"]] + +</div> + +Captioner: sachac + Questions or comments? Please e-mail [sczi@disroot.org](mailto:sczi@disroot.org?subject=Comment%20for%20EmacsConf%202023%20swanky%3A%20Swanky%20Python%3A%20Interactive%20development%20for%20Python) |
