Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds.
This is episode 351, recorded September 5th, and I am Brian Okken.
And I'm Michael Kennedy.
And this episode is also sponsored by Sentry. Thank you, Sentry.
And if you want to reach any of us or the show, either of us or the show,
we're at mkennedy, at brianauken, and at pythonbytes, all at fostadon.org.
And if you're listening later and you'd like to join the show live sometime,
you can join us on YouTube at pythonbytes.fm/live.
And we'd love to have you here.
Indeed.
What do you got for us first, Michael?
Let's talk about multiprocessing.
Empire, not pyre, but mpire, right?
Pyre is the type checker from meta.
Empire is something entirely different about multiprocessing.
And so it's a Python package for easy multiprocessing that's faster than the built-in
multiprocessing, but has a similar API, has really good error reporting and a bunch of
other types of reporting, like how well did that session go?
You know, like how well did you utilize the multiprocessing capabilities of your machine
and so on?
So yeah, let's dig into it.
So the whole acronym for the name is multiprocessing is really easy,
which is not what most people say, right?
No.
But it's a package that's faster than multiprocessing in most cases, has more features,
and is generally more user-friendly than the default multiprocessing package.
It has APIs like multiprocessing.pool, but it also has the benefits of things like copy,
unwrite, shared objects.
We're going to come back to that later as well.
But also the ability to have like init and exit, set up teardown for your workers, some more
state that you can use and so on.
So pretty cool.
It has a progress bar.
It has TQDM progress built right into it across the multiple processes.
So you can say things like, here is some work I want you to do.
There's a hundred items.
Split that across five cores.
And as those different processes complete the work for individual elements, give me a unified
progress bar, which is pretty awesome, right?
Yeah.
Very cool.
Yeah.
It's got a progress dashboard.
Actually, I have no idea what that is.
It has a worker insights that you can ask when it's done.
Like how well did, you know, how efficient was that multiprocessing story?
Graceful and user-friendly exception handling.
It has timeouts.
So you can say, I would like the execution of the work to not take more than three seconds.
And actually you can even say things such as if the worker process itself takes 10 seconds
or more to exit, maybe there's like some, something happening over there.
That's like a hung connection on a database thing or who knows, right?
Some network thing.
You can actually set a different timeout for the process, which is pretty cool.
It has automatic chunking.
So instead of saying, I have a hundred things, let's go individually one at a time, hand them
off to workers.
It can break them up into bigger blocks, including numpy arrays, which is pretty cool.
You can set the maximum number of tasks that are allowed to run at any given moment.
I guess, you know, you can set the workers, but also like, if it does this chunking and
you can say how many things can run to avoid memory problems.
You could even say, four, I want to use five processes, but after.
You know, 10, 10 bits of work on any given process.
Give me a new worker and shut down the others in case there's like leaky memory or other
things along those lines.
Uh, you can create a pool of them through a daemon option.
A whole bunch of stuff.
It uses dill to serialize across, the multi-process processes, which is cool because it gives you
more exotic, serialization options for say objects that are not pickleable lambdas functions and other
things in IPython and Jupyter notebooks.
So all that's pretty awesome.
Right?
Yeah.
So the API is super, super simple from empire import worker pool with worker pool jobs equal
five, pull that map, some function, some data go.
So this jobs here tells you how many processes to run basically for the progress bar.
Uh, you just set progress bar equals true.
That's not too bad.
Another thing that's cool is you can have shared objects.
So you can have, some shared data that's passed across without, basically using shared
memory.
I think is how that it works so that it's more efficient instead of trying to pickle it
across.
I think they have to be read only or something.
And there's a whole bunch about it.
Oh, interesting.
But you pass it into the worker pool.
Okay.
Yeah.
You say worker pool, these things, I want you to set them up in a way to be shared.
And I think, like I said, in a read only way across, across all the processes instead
of trying to copy them over.
Um, you have a setup and a teardown thing that you can do, to like prepare the, the worker,
when it gets started, you can ask it for the, the insights, like I said, and then
benchmarks.
It shows it's significantly faster.
Not just the compared, not just against multi-processing, but they say, here's how you do it.
Here's what happens if you do it in a serial way.
Here's what multi-processing and process pool executors look like.
But it also compares against job lib, dask, and array.
Mm.
And it's, it's pretty much hanging there with the best of them, isn't it?
Yeah.
It does a titch faster than ray everywhere.
Yeah.
Just, yeah.
Just, just a bit.
One other thing.
Uh, I don't remember where it was, in this huge, long list of things.
Um, but you can also pin the CPUs to CPU cores, which can be really valuable when you're thinking
about, taking advantage of, you know, L1, L2 CPU caches.
So if your processes are bouncing around back and forth, one's working with some data, then
it switches to another core.
And then it has to pull a new data into the, into the L2 cache, which is like hundreds
of times slower than real memory.
And that's how that slows it down.
Then they switch back and they keep running.
So you can say, you know, pin these workers to these CPUs and you know, you've got a better
chance of them not redoing their cache all the time.
So that's kind of cool.
So there's just a bunch of neat little features in here.
Yeah.
If you're already using multiprocessing, you might check this out.
Oh, if you care about performance for real, you know,
why are you using multiprocessing if you don't care about performance?
Well, I mean, you're, you're looking to pull out the final little bits of performance, I
suppose.
Yeah.
Right.
Like these benchmarks are cool, but they're doing, you know, computation on the workers.
Right.
Whereas a lot of what you're doing is like talking to queues and talking to networks and talking
to databases.
Like it doesn't matter what framework you used to do that long as you're doing something
parallel.
Right.
Yeah.
Well, yeah.
Well, I don't know.
That's why you have to do your own benchmarks.
Yeah, for sure.
And then there's that article over on medium by the creator as well.
That gives you a whole lot of background on all this stuff.
Oh, neat.
Nice.
Yeah.
This is quite a long article.
And I think it's actually more relevant.
Like, for example, it's got screenshots where it shows, you know, if you use something like,
let me read really quickly.
Ray or job lib and you get some kind of exception.
It just says exception occurred.
Yikes.
Whereas with this one, with empire, you get things like here's the call stack that goes
back a little more correctly to the right function.
And here's the arguments that were passed.
Oh, interesting.
Over.
So when it crashes, you know, because you have like five processes, potentially all doing
stuff.
One of them crashed.
Like what data did they have?
I don't know.
It's a parallel.
It's hard.
Right.
So having the arguments like these are the ones that cause the error is pretty excellent.
Yeah.
Cool.
Anyway, empire.
That's what I got for number one.
All right.
Cool.
I want to have, I have something else that starts with M.
Mop up.
So mop up is something that I learned about from an article by glyph.
So I'll let me jump to the article first.
So glyph wrote an article saying, get your Mac Python from python.org.
That's what I already do.
I've tried all the other stuff and I just like just the downloader from python.org.
So this article talks about reasons why that's probably what you want.
And that's probably what if you're writing a tutorial, it's probably what your users need
to do, too.
If they're if they're using a Mac and I won't go through all the details, but he he goes through
reasons why you probably want this one and not things like what are the others?
homebrew.
You can brew install your Python, but he doesn't recommend it.
And you can read all the pyenv.
I've tried it.
It like messes up other stuff for me.
So I like the downloader from Python.
But one of the things that I don't like is that if like if I had Python 311 4 installed and
now Python 311 5 is out.
How do I get that on my computer?
Do I just reinstall it?
Yes, you can.
But Glyph made a new thing called mop up.
So what mop up does is you you just pip install mop up.
And it's like the only thing I install on my global my global Python versions like 311 pip install.
I update pip and install this and that's it.
Everything else goes into a virtual environment.
Or pip x install this.
Exactly.
But mop up.
What's the usage?
So I just tried it this morning.
I didn't pass it any flags.
I just installed it and ran it.
And it updated me from Python 311 4 to Python 311 5 without me having to re-download anything
other than this.
So I'm going to set up something that goes through.
I've got a lot of versions on my computer.
I've got I think well I've got 3.7 through 3.12 install.
And and I want all of them to be on the latest bug fix release.
So I'm just going to use probably use Brett Cannon's pi installer or Python installer pi on
my Mac to go to each of the versions and run mop up on all of them to update it.
So that's what I'd like to do.
Anyway it's cool.
I'm I'm really excited about this because this was like the one hole in using the install
the the python.org installer is how they update it.
So nice.
Yep.
Interesting.
I got to admit I'm still a brew install Python 3 sort of person.
Okay.
And the drawback the main drawback that glyph makes an argument for which is valid is you
don't control necessarily the version of Python that you get.
Because if you brew install I don't know some other you know YouTube downloader app or whatever
rando thing it might say well I need a Python 3.12 and you only have 3.8 right.
And it'll auto upgrade on you without you knowing.
But I'm always running the absolute latest Python anyway.
And so you know when it those other packages say greater than 3.10 like I don't care I already
have greater than 3.10.
So I don't know that's the world I'm living in now but that's okay for me.
Oh okay.
So yeah I'm I'm a package maintainer so I I have multiple versions on my box but it's
but in a lot of people like PyEmp for that reason but I yeah I don't.
But anyway I've always I've had trouble with PyEmp too especially around the Apple Silicon
Rosetta compiler mismatch like there's just like it wouldn't install for me and so yeah I think the
whole I think the python.org it's a good recommendation though.
Okay cool.
Yep yep.
All right.
before we move on to our next topic Brian.
Well I'd like to thank Sentry for sponsoring this episode of Python Bytes.
You know Sentry for their error tracking service but did you know that you can take take it all
the way through your multi-tier and distributed app with their distributed tracing feature?
How cool is that?
Distributed tracing is a debugging technique that involves tracking your the request of your
system starting from the very beginning like the user action all the way to the back end
database and third-party services.
This can help you identify if the cause of the an error is one project in one project is due to an
error in the other another project.
That's very useful.
Every system can benefit from distributed tracing but they are useful for especially for microservices
in microservice architecture logs won't give you the full picture so you can't debug every request
in full by reading the logs but distributed tracing with a platform like Sentry
can give you visual overview of which services were called during the execution of certain requests.
Aside from debugging and visualizing architecture distributed tracing also helps you identify
performance bottlenecks through a visual like Gantt chart.
You can see if a particular span in your stack took longer than expected and how it could be causing slowdowns in other parts of your app.
You can see if you can see the results of your app.
You can see the results of your app.
So if you have x equals a string, and then I say y equals x, it goes up to that thing
and says plus plus, you know, plus equals one on the reference count.
And when y goes away, then it minus minuses it, right?
When that number hits zero, it gets cleaned up.
There's also stuff on the object for cycles and garbage collection.
So there's a lot of stuff that's happening there, right?
Yeah.
And so what they're doing is they're running a lot of Django for Instagram, which is pretty awesome.
However, what they're trying to take advantage of is the fact that there's a lot of similar data, similar memory usage.
When I load up Python, so if I write type Python on the terminal, and then I open up a new terminal type Python, it's gone through exactly the same startup process, right?
So it's loaded the same shared libraries or DLLs.
It's created.
It's, it's, you know, negative 255 to 255 flywheel numbers.
There's going to reuse instead of when you say the number seven, it doesn't always create a new seven.
You always have the seven that was created at startup exceptions, those kinds of things, right?
Well, if you have a web server that's got 10 or 20 or a hundred worker processes that all went through the same startup for a Python app, you would want to have things like that number seven or some exception type or whatever modules, right?
Core modules that are loaded.
You would like to have one copy of those in memory on Linux, and then have a copy on right thing for the stuff that actually changes.
But those other pieces, you want them to stay the same.
Yeah.
Like there's no point in having like a different representation of the number four for every process.
If there's some way to share that memory that was created at startup.
And we don't need reference counts updated and all that stuff.
Because exactly exactly.
So the, I, what they found was, while many of their Python objects are practically or effectively immutable, they didn't actually over time behave that way.
So they have graphs of private memory and shared memory.
And what you would hope is that the shared memory stays pretty stable over time, or maybe even grows.
Maybe you're doing new stuff.
That's like pulled in similar things, but that's not what happens in practice.
I'm current Python.
The shared memory goes down and down and down, because even though that object, or let's say that flywheel number that got created to be shared, it's still got his reference count number change.
So throughout the behavior of one app, it might go, well, four was used for a long time.
Four was used 300 times here and 280 over there.
So those are not the same four.
Cause on the reference count, they have 281 and 301 or whatever it is.
Right.
And so that, that shared memory is falling down because the garbage collector and, just the interacting with the ref count is very in very meaningless and small ways, changing pieces of the shared memory to make them fall out of the shared state.
So this whole path, this whole idea is we're going to make those types of things.
Uh, so that their reference count can't change.
And their GC structures can't change.
They cannot be changed.
They're just always set to some magic number for like this thing's reference count is unchanged.
Right.
So if you look at like the object header, it's got a GC header reference count object type, and then the actual data for the ones that don't change.
Now these new ones can be set.
So even their GC header and the reference counts don't change.
Cool.
Right.
Yeah.
And what that means is if you come down here, it says there's some challenges.
First, they had to make sure that applications wouldn't crash.
If some objects suddenly had different ref counts.
Second, it changes the core memory representation of a Python object, which if you work in the C level, just directly with the memory, that's, you know, pointers to the object.
That can be tricky.
And finally, the core implementation relies on adding checks explicitly to the increment and decrement, the, you know, add ref, remove ref, decrement ref, which are two of the hottest bits of code in all of Python.
Yeah.
As in the most performance critical.
So if you make a change to it, or you make all the Python slower for this, that's bad.
And they did make Python slower, but only 2%.
And they believe that the benefits they get is actually worth it.
Cause you bring in, you know, for like heavy workloads, you get actually better performance.
So it's a trade off, but there it is.
One of the things that was reading this article.
And one of the things that confused me was, is there, is this just something internal to Python that, that it's going to happen under the hood or do I need to change my syntax in any way?
Yes.
I was looking for that as well.
And every single thing about, I went and read the PEP and everything I remember from reading the PEP and maybe I missed something, but everything I got from the PEP was it, the C layer.
It was, you know, here's the pie immortal, you know, call that you make in the C API.
So what I would like to see is something where you set a decorate, like kind of like a data class.
Like this thing is outside of garbage collection.
This class is out or this, I don't know, in some way to say in Python, this thing is a moral for now, at least.
Yeah.
But I didn't see it either.
It also would be good even if we could just do like, like, that would be kind of like a constant then also.
We could set up some, some constants in your system that, that are immortal or something.
Yeah.
Okay.
Yeah.
Like the dictionary of a module that loads up, if you're not dynamically changing it, which you almost never do, unless you're like mocking something out, like, let it, let it be, you know, it's just tell it it's the same.
Don't, don't reference combat.
Yeah.
I'd be curious to see in this implement, as they're implementing it, it does seem like parts of the system are going to go a little bit slower, but also parts of it are going to go faster because you don't have to do all that work, but.
Exactly.
Right.
Yeah.
You don't have to, you don't have to do a lot of stuff.
Okay.
Like the garbage collection cycles that happen over time, right?
Yeah.
These things will just be excluded from garbage collection entirely.
So that's cool.
So they have some graphs of what happened afterwards and the before and after and on the after in the shared memory.
Well, sorry, the before it went almost to zero.
Like it went from, you know, Y axis with no numbers really high to Y axis low with no numbers.
But I don't know exactly what this is.
Maybe a percent, but like I said, it doesn't really, really say, but after processing as few as 300 requests, it was like a 10th of the original shared memory was left.
And that was it now after it's down to it's 75, 80% still shared, which is pretty excellent.
Okay, cool.
But as you said, this is like one of the internal core things from what I can tell.
Yeah.
They do say that this is foundational work for the per interpreter Gil that six, eight, four, as well as making the global interpreter lock optional and see Python seven or three.
So that's why I'm not going to be able to do that.
And that's why I'm not going to be able to do that.
Well, I'm not going to be able to do that.
I'm not going to be able to do that.
So that's why it's there to support.
And that's why it's relevant for some of these parallelism peps.
So anyway, pretty cool.
This is coming in 3.12, I guess.
Nice. Cool.
Well, I'd like to talk about something that I don't really think about that much in that
that is doc strings for doc string formats.
And I just ran across this article and I'm covering it partly just as a question to the audience.
So the article is from Scott Robinson and it's called Common Doc String Formats in Python.
And doc strings, people forget what they are.
Like, let's say you have a function called add numbers or something.
You can do, you can really do any kind of quote, but the first string in a function,
if it's not assigned to a variable, is the doc string.
And it's the first element.
Anyway, the first line is a little, it's usually one line.
one line and then maybe a space and then some other stuff.
And there apparently there's several format, common formats of this.
You can also, you can get access to it by the underscore doc attribute of something.
So if you have a, a, a reference to a function, you can say dot dunder doc, and you can see the doc string.
And a lot of like ID is use this to pop up hints and stuff.
That's one of the reasons why you want to have like the, the first, at least the first line be an explanation that is good for somebody to see if it pops up on them and stuff.
So anyway, which formats should this be?
So it covers a handful of different formats.
There's a restructured, restructured text doc stream format.
So you've got all this like descriptions of parameters and the types and stuff.
Um, this is scary looking to me.
Um, there's the, the, let's go through next, Google doc stream format.
This one makes a little more sense, but again, I don't know.
Um, it says, and it talks about the different parameters.
And if, if you really have to describe them, this is probably one of my favorites is what looks pretty good.
Uh, return.
What, what, what, what, what is the information that it returns?
Uh, what are the arguments and why some like one liner explanations, not too bad.
There's a numpy scipy doc format.
This is also pretty clear.
Um, maybe a little, let's compare the two.
I guess this is it got an extra line because you're doing the, the underscore line, which is, I guess.
Okay.
It looks sort of, I don't know.
This is a lot of space, but I would, I'm just curious if people are really using this.
Looking at this, I can see the benefit of describing if it's not clear, from the name of your function, describing stuff.
And I also like type hints.
So this seems like a great argument for type hints because the types we,
the types would be great.
Just right in the right.
Right.
Exactly.
The parameters.
Uh, and then if you don't have to describe the type, maybe just have variable names that are more clear.
So, I, my personal preference really is, use type hints and then also, have a description.
If you're going to do a doc string and it's not obvious from the name of the function, then have a description of what the function does.
And that's it.
Um, and then if it's unclear to about what really what the stuff is, the behavior of different parameters, then add that.
But, I, again, I'd love to hear back from people, go ahead and, send me a message on, at Brian Okken at faucet.org.
This worked great last week.
I got some great feedback.
Um, and so I'd love to hear what people are doing for their doc string formats.
Do you, do you use doc string formats, Michael?
I'm familiar with doc string formats and I've played with them.
I like the Google one best, I think, but I'm with you.
Like if you have good variable names, do you need the parameter information?
If you use type hints, do you need the parameter information to say the type?
If you have a return declaration with a type, do you need to have the returns?
The function has a good name, like get user, you know, area angle bracket, optional user, like, oh, well, it returns a user or it returns none.
How much more do you need to say about what it returns?
You know?
Right.
Like there's a lot of it's, it's a little bit of a, a case study and yes, you want to be very thorough, but also good naming goes a really long way to like limit the amount of comments and docs you got to put onto a thing.
There are times when it makes sense though.
Like if, you're talking about, range or.
Uh, something like that.
Uh, is it inclusive of both, both numbers?
Um, and if I say one to 10, do I get one, two, three, four up to 10 or I get one, two, three, four up to nine.
Right.
Like those, those situations where you might need to say the non-inclusive upper bound of the rain.
I don't know, whatever.
Something like that.
Right.
Yeah.
Um, I do like, an explanation of what's returned though.
Uh, often it's not obvious.
Um, and it's, even if you are doing a type hint and you can get the type of what's returned, what's the meaning of what's returned is if that's not obvious, please put that in a doc string.
But yeah, anyway, cool.
I wonder, I don't, I, this is an honest question.
I have no idea if you express it in any of this documentation or if the editors consume it.
But what would be really awesome is if there was a way to express all possible exception types and the entire call stack, right?
Like you could get a value error, you could get a database connection error, or you could get a uniqueness constraint exception with any of those three.
Then you could have editors where you just hit like alt enter, right?
The error handling goes, bam, bam, bam.
Here's the three types of things you might catch.
Right.
That would be awesome.
But I don't know if you can express the possible range of exceptions in there or not.
Or unless you've, yeah.
And especially if you're calling any extra functions within, within a function.
Yeah.
You don't know if it's going to raise an exception possibly, but.
Possibly.
Yeah.
Anyway, that's something I would see actually really useful there that you don't express in like the type information or the name or any of those things.
Yeah.
Cool.
Yeah.
Cool.
Well, those are our items.
Michael, do you have any extras for us?
I have an extra for you in particular.
How about that?
Okay.
Let's start with that one then.
So last week you asked about GitHub releases.
Who uses these?
Should I be bothered?
Yeah.
There's this person that seems to be telling everyone on GitHub.
They should use releases if they're not.
Do I care?
And Rhett Turnbull, who's been on talk Python to talk about building Mac apps or with Python.
GitHub said there said GitHub releases questions.
I use them and I like them so people can subscribe to be notified of new releases.
I use GH, the GitHub command line.
GitHub release create to create one of the command line every time I pushed up IPI.
I'm sure this can be done as an action, but I don't push that often.
So it's fine with me.
Anyway, there's some feedback for you.
Thanks.
Yeah, I actually got quite a few people reaching out and I really appreciate it.
And it did convince me that I'm going to start trying to figure it out using GitHub releases, but I also want to make sure that it's automated as much as possible.
I don't want to add redundant work just for the heck of it.
So you're going to set up some automation to go around and tell everyone on GitHub who doesn't have releases going yet?
No.
They should do releases.
Think of all the contributor badges you're going to get.
Yeah.
I'm just kidding.
All right.
Let's talk about one more thing.
We've heard about IPI issues where people are uploading malicious packages and a lot of times it's crypto kitties and other idiots who are doing that or researchers to like just prove a concept that it's not going to be.
Prove of concept that it can be done.
But Lazarus hackers who are I'm pretty sure.
Yeah.
North Korean state sponsored hacking group is uploaded a fake VMware VM connect library targeting it professionals.
So it only had 237 downloads.
But when you start to think about state actor hacking level of stuff getting installed onto your machine, that's like a at minimum format.
The OS maybe just throw it in the trash.
I don't know.
It's like pretty bad level of being infected.
So I don't know.
That's I have no action or further thoughts.
Just like a, Hey, that's worth checking out.
Yeah.
And maybe we do need to care about our, our, you know, pipeline and whatever.
But yeah, the supply supply chain, but we do have the, the new security person, Mike, that was hired.
Right.
So that's excellent.
Yes.
Yeah.
He was in the audience.
So that was great.
I believe it was Mike.
Right.
Hopefully I got the name, right?
Yeah.
Over to you.
That's what I got.
It's actually this, the, I don't know how to pronounce that J and Y J and Y, on PI, the PI bytes slack.
Um, we were, talking about using, talking about using TRS 80 computers.
And I said, Hey, I, I remember typing in lunar lander on my TRS 80 way back when, copied it out of the back of a magazine.
And he's, he said, Oh, I've got a copy, copy of, of, lunar lander that works on Python.
I'm like, Oh, I want to try it.
And, and I, I still can't, I'm going to get back to him, but, I can't get his to work.
And then I looked around and there was this other cool one.
Um, lunar lander Python.
I found that's a four years old and apparently it was done as part of a fundamentals of computing course.
Uh, which is, which is pretty impressive.
I couldn't get it to work, but their website looks great.
So they have a website with it.
You got attached to it with, with like a screenshots and it shows good fonts too.
Yeah.
And it looks exactly like the lunar lander that I typed into my TRS 80.
So I'm pretty excited about that.
Um, anyway, but I can't get that to work either.
So if anybody's got like a lunar lander, copy or something that works on, works with modern Python, I would love to, to play with it.
I also want to like hack with it with my daughter and stuff.
So anyway, that's the only extra thing I got is, bring on the lunar lander.
That's really cool.
I like it.
Yeah.
Mike, Mike Felder is here.
Uh, Fidel is here.
Uh, the security guy and people are thinking him and stuff for all the security work.
So just getting started, but yeah, it's, it's not an easy job.
I'm sure.
Yeah.
And we're pretty excited.
I can't think of a better person to do this job.
So indeed.
So shall we play some bingo?
Sure.
All right.
This is our joke.
Program or bingo.
I love it.
So, you know, bingo works.
Everybody gets a different card with different options.
Typically it's numbers, but in this case it's program or actions or statements you call out or have happened.
And as they get called out, you mark them off and whoever completes a row or column or I dunno, something diagonal.
I don't play that much bingo, but you win.
Right.
And so this is, this is a possible program or bingo card.
We should come up with one, a whole bunch of them.
So I'll just read you some of the options out of this card.
Okay.
Ryan.
So we've got number one written code without comments.
Everybody could check that one off for all of the C inspired language.
People forgot a semi-colon at the end of a line.
That's good.
I can certainly relate with number three, close 12 tabs after fixing an issue.
Oh yeah.
Also related.
Number four, 20 warnings, zero errors.
Works on my machine, man.
Yeah, exactly.
The number five is program.
Didn't run on someone else's computer.
Yeah.
And instantiation of the works on my machine problem.
And then this number six to do list greater than completed tasks.
Number seven copied code from stack overflow.
I'm pretty sure we can all check that one off.
Close program without saving it.
Okay.
Number nine, ask to fix a laptop because you're a programmer.
I have a problem with my computer.
Like, please don't, please don't.
Number 10 turned your bug into a feature.
11 deleted block of code and regretted it later.
Finally learned a new programming language, but never used it.
Hello TypeScript.
We could come up with so many of these.
We should, we should totally do.
We should do more.
So good.
Aren't they?
You can just go on and on.
Yeah.
You can do a backup copy of your code repository, even though it's like a hub.
Yeah.
Zip is my source control.
Yeah.
And then the, the, the, there's usually a free one in the middle that could just be a need
to need to update pip.
That's exactly.
Pip is out of date.
Yeah.
Awesome.
Well, as usual, pleasure to talk with you, Michael, and thank you so much Sentry for sponsoring
this episode.
Again, everybody check out Sentry and go to, what was that link again?
Pythonbytes.fm/sentry.
Thanks Brian.
Thank you.
Bye.
See y'all.
Thank you. Bye.