Python Bytes - #351 A Python Empire (or MPIRE?)
Episode Date: September 6, 2023Topics covered in this episode: mpire mopup - the macOS Python.org Updater Immortal Objects for Python Common Docstring Formats in Python Extras Joke See the full show notes for this episode on ...the website at pythonbytes.fm/351
Transcript
Discussion (0)
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 Auchin.
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 Brian Auchin,
and at Python Bytes, all at fostodon.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 slash 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 empire, 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 multi-processing capabilities,
your machine and so on. So yeah, let's, let's dig into it. So the whole acronym for the name
is multi-processing 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-on-write shared objects.
We're going to come back to that later as well. But also the ability to have like init and exit setup 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 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.
Yeah.
Very cool.
Yeah.
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 efficient was that multi-processing 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,
you can say how many things can run to avoid memory problems. You could even say, I want to use five processes, but after, you know, 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. You can create a pool of them through a daemon
option, a whole bunch of stuff 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. Yeah. So the API is super, super simple.
From Empire, import WorkerPool.
With WorkerPool, 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, 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 physically using shared memory, I think, is how that it works.
So that it's more efficient instead of trying to pick a load across.
I think they have to be read-only or something.
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 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, um, across all the processes, instead of trying to copy them over,
um, you have a setup and a teardown thing that you can do, um, to like prepare the worker when
it gets started. Uh, You can ask it for the
insights, like I said, and then benchmarks, it shows it's significantly faster. Not just,
they compare it 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. And it's,
it's pretty much hanging there with the best of them, isn't it?
Yeah. Just a titch faster than Ray everywhere. Yeah.
Just yeah, just, just a bit. One other thing, I don't remember where it was in this huge
long list of things. 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 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'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.
If you're already using multi-processing,
you might check this out.
If you care about performance for real, you know.
Why are you using multi-processing if you don't care about performance though?
Well, I mean, you're looking to pull out the final little bits of performance, I suppose.
Yeah, yeah, 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.
So 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 JobLib, and you get some kind of exception, it just
says exception occurred.
Yikes.
Whereas with this one, with Empire, you 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 uh 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
in parallel it's hard right so having the arguments like these are the ones that cause the error is is pretty excellent yeah cool anyway empire that's what i got for number one
all right cool um i want to have i have something else that starts with m
mop up um so mop up is uh is something that i learned about from an article by Glyph.
So 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 why.
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 3.11.4 installed
and now Python 3.11.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 MopUp. So what MopUp does is you
just pip install MopUp. And it's like the only thing I install on my global Python versions,
like 3.11 pip install. I update pip and install this and that's it
everything else goes into a virtual environment but or or pip x install this exactly um 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 3.11.4 to python 3.11.5 without me having to re-download
anything other than this so um i'm gonna 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 installed and um and i want
all of them to be on the latest bug fix release so um i'm just going to use probably use
uh um brett cannon's uh pi installer um or python installer pi um 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 Python.org installer is how they update it.
So, nice.
Yep.
Interesting, 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 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 312 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 three 10,
like,
I don't care.
I already have greater than three 10.
And so I don't know.
That's the world I'm living in now,
but that's,
that's okay for me.
Oh,
okay.
So yeah,
I'm,
I'm a package maintainer.
So I,
I have multiple versions on,
um,
on my box,
but it's,
uh,
but in a lot of people like pie and for that reason, but I, I don't, um, but my box, but it's, uh, but in a lot of people like
PI and for that reason, but I don't.
Um, but anyway,
I've always, I've, I've had trouble with PI of two, especially around the
apple Silicon Rosetta compiler mismatch.
Like there's just the, like it wouldn't install for me.
And so, uh, yeah, I think the whole, I think the python.org
it's a good recommendation though.
Okay, cool.
Yep. Yep. All right. Uh, before we move on.org is a good recommendation. 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 surface,
but did you know that you can 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 the requests of your system,
starting from the very beginning, like the user action, all the way to the backend,
database, and third-party services. This can help you identify if the cause of an error in one
project is due to an error in another project.
That's very useful.
Every system can benefit from distributed tracing,
but they are useful 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 a visual overview of which services were called during the execution of certain requests.
Aside from debugging and visualizing your 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.
Learn more and see examples in the tracing section of their docs at docs.sentry.io.
To take advantage of all the features of the Sentry platform, create your free account.
And for Python Bytes listeners, be sure to use code Python Bytes, all one word, and activate a free month of their premium paid services.
Get started today at PythonBytes.fm slash Sentry.
Thank you, Sentry, for supporting PythonBytes.
Indeed, thank you, Sentry.
And I want to bring it back to a similar,
not bingo, bring it back to something
I gave a shout out to before,
multithreading and meta.
I talked about both those.
And I want to cover this article posted on engineering at meta, which is on the facebook.com
domain, actually not the meta domain, but whatever engineering at meta, because it's
really about Instagram anyway.
And it talks about this new thing called immortal objects.
And Brian, would you want to live forever like a vampire?
No.
Me either.
Definitely, definitely not.
For a while.
I mean, I could take a few more years, but not infinity.
But Python objects, they can benefit from this infinity.
And so I want to go through this new PEP, PEP683, which is accepted accepted accepted in 312 so that's pretty exciting this is part of the
cinder performance work that's coming out of the meta team and i want to look at it not originally
but let's look it over on omnivore.app this is my new favorite way for research because I can put highlights and notes. So Instagram has introduced
immortal objects to pep 683. Now, Python objects can bypass reference counting checks and live
forever through the entire execution of the runtime, at least from when they're created
to the end. So you know, traditionally, typically, I guess I should say Python objects have a
whole bunch of information about them on the object that is allocated on the heap.
This even includes numbers.
And those things change over time.
So if I 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 type Python
on the terminal and then I open up a new terminal and 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 number that
it's going to reuse instead of when you say the number seven, it doesn't always create a new seven,
just 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 100 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 write thing for the stuff that actually changes. But those other pieces,
you want them to stay the same yeah 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 um 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
on 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 its reference count number change.
So throughout the behavior of one app, it might go, well, four was used
300 times here and 280 over there.
So those are not the same four.
Because 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 pep this whole idea is we're going to make those types of things so that their reference count can't change, 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.
Well, 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 add ref, remove ref, decrement ref,
which are two of the hottest bits of code
in all of Python,
as in the most performance critical.
So if you make a change to it,
or you make all of Python slower for this,
that's bad.
And they did make Python slower,
but only 2%,
and they believe that the benefit they get
is actually worth it,
because you bring in,
for heavy workloads, you get actually better performance. So it's 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 I 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 maybe i missed something but everything i got
from the pep was it the the c layer it was you know here's the pi immortal you know call that
you make in the c api so what i would like to see is something where you set a decorator 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.
It's 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 are immortal or something.
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 be, you know?
Just tell it it's the same.
Yeah, I'd be curious to see in this,
as they're implementing it, it does seem like parts 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 don't have to do a lot of stuff okay like the garbage collection
cycles that happen over time right 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 and 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, um, 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,
you know, 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 up 684, as
well as making the global interpreter lock optional and see Python 703.
Because if you know an object is never going to change, not even its ref count, not even
its GC state, and
definitely not its data. Well, you can have at it with multi threading, right? The problem
with multi threading is something has changed in between two operations. And if you know
it's never going to change, you can just completely remove all the checks that you need to do
and make it a lot faster. 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 312, 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 Dock String Formats in
Python. And dock 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 I wrote the first element. Anyway, the first line is a little, it's usually 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 reference to a function, you can say dot dunder doc and you can see the doc string.
And a lot of like IDEs 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.
Anyway,
uh,
which formats should this be?
So he covers a handful of different formats.
Uh,
there's a restructured,
uh,
restructured text doc stream format.
So you've got all this like,
uh, descriptions of parameters
and the types and stuff.
This is scary looking to me.
Let's go through next.
Google doc stream format.
This one makes a little more sense,
but again, I don't know.
It says int and it talks about the different parameters.
And if you really have to describe them,
this is probably one of my favorites.
This looks pretty good.
What is the information that it returns?
What are the arguments and why?
Some one-liner explanations.
Not too bad.
There's a NumPy SciPy doc format.
This is also pretty clear.
Maybe a little, let's compare the two.
I guess it's got an extra line
because you're doing the underscore line, which is, I guess, okay, it looks sort of,
I don't know, there's 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 would be great just right in the right.
Exactly.
The parameters.
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 do use type hints, and then also have a description of if you're
going to do a docstring, and it's not obvious from the name of the function, then have a description
of what the function does. And that's it. And then if it's unclear about what really what the stuff is,
the behavior of different parameters,
then add that.
But again, I'd love to hear back from people.
Go ahead and send me a message
on at brianocken at fosadon.org.
This worked great last week.
I got some great feedback.
And so I'd love to hear
what people are doing
for their docstring formats. Do you use docstring to hear what people are doing for their docstring formats.
Do you use docstring formats, Michael?
I'm familiar with docstring 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 know 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 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 something like that, is it inclusive of both numbers?
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 ring.
I don't know.
Whatever.
Something like that.
Right.
Yeah.
Yeah.
I do like an explanation of what's returned though.
Often it's not obvious.
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. And 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, 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
in 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.
Any of those three,
then you could have editors
where you just hit like alt enter,
write the error handling goes,
bam, bam, bam.
Here's the three types of things you might catch.
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 a function,
you don't know if it's going to raise an exception possibly.
Possibly.
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. Uh, 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. Um,
so last week you asked about GitHub releases, who uses these?
Should I be bothered?
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 Turnball, who's been on Talk Python to talk about building Mac apps
or, uh, with Python, uh, 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 in the command line
every time I push to PyPI.
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 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
the contributor badges you're going to get yeah i'm just kidding
all right let's talk about one more thing uh 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 can be done
but lazareth hackers who are i'm pretty sure yeah north korean state-sponsored hacking group
uploaded a fake vmware vm connect library targeting it. 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 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 when I announced that. That was great. I believe it was Mike,
right? Hopefully I got the name right. Yeah.
Over to you. That's what I got. Okay. Well,
I was having a conversation
with, it's actually this,
I don't know how to pronounce that,
JNY, JNY,
on the PyBytes
Slack. We were
talking about using
TRS-80 computers, And I said, Hey, I remember
typing in lunar lander on my TRS 80 way back when I copied it out of the back of a magazine. And
he's, he said, Oh, I've got a copy, copy of lunar lander that works on Python. I'm like,
Oh, I want to try it. um and i i still can't i'm
gonna get back to him but um i can't get his to work and then i looked around and there was this
other cool one um uh 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 uh
screenshots and it shows good fonts too yeah and it looks exactly like the lunar lander that i
typed into my trs80 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 with modern Python,
I would love to play with it.
I also want to 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 Felder is here.
The security guy and people are thanking him is bring on the Luderlander. I like it. Yeah. Mike, Mike Felder is here. Five dollars here.
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.
Programmer bingo.
I love it. So, you know, bingo? Sure. All right. This is our joke. Programmer 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 programmer 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 don't
know, something diagonal, I don't play that much bingo, but you win.
Right.
And so this is, this is a possible programmer 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.
Brian.
So we've got number one written code without comments.
Everybody can check that one off for all of the C inspired language.
People forgot a semi- semicolon at the end of
the line. That's good. I can certainly relate with number three, close 12 tabs after fixing an issue.
Oh yeah. 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, asked 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 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 could just go on and on
yeah yeah like um have 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, 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. Pip is out of date.
Yeah.
Uh, 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?
Python by slash sentry.
Thanks Brian.
Thank you.
Bye.
See y'all.