WEBVTT

00:00:00.001 --> 00:00:04.720
Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds.

00:00:04.720 --> 00:00:10.580
This is episode 196, recorded August 19th, 2020.

00:00:10.580 --> 00:00:12.100
And I am Brian Okken.

00:00:12.100 --> 00:00:12.980
And I'm Michael Kennedy.

00:00:12.980 --> 00:00:15.880
And actually, we have a sponsor this week, Datadog.

00:00:15.880 --> 00:00:16.680
Thank you, Datadog.

00:00:16.680 --> 00:00:16.960
Yeah.

00:00:16.960 --> 00:00:18.340
Thanks, Datadog.

00:00:18.340 --> 00:00:20.680
First off, I want to talk about Django a little bit.

00:00:20.680 --> 00:00:23.980
I've always heard Django is super easy, and that's why people choose it,

00:00:23.980 --> 00:00:28.900
because it's really easy to get started, and it has all these things that make working with Django easy, and so on, right?

00:00:28.900 --> 00:00:31.040
Yeah, I think there's a lot going for it.

00:00:31.040 --> 00:00:33.200
The community seems pretty awesome.

00:00:33.200 --> 00:00:34.680
There's a lot of tutorials.

00:00:34.680 --> 00:00:37.020
There's a lot of expertise that they can help you out.

00:00:37.020 --> 00:00:44.040
So there's an interesting article by Dan Verrazzo called Surviving Django, If You Care About Databases.

00:00:44.040 --> 00:00:51.340
So, I mean, Surviving Django, right off the start, it's an odd title for an article about Django.

00:00:51.340 --> 00:00:57.800
It's going to be kind of hard to summarize, but basically, the take on it is a little bit of a...

00:00:57.800 --> 00:01:02.000
There's a lot of people that are going to be able to do that.

00:01:02.000 --> 00:01:02.940
It's a lot of people that are going to be able to do that.

00:01:02.940 --> 00:01:14.440
And it's an interesting perspective, but the gist of it really centers around that there's a lot of parts of Django that seem to be database agnostic.

00:01:14.440 --> 00:01:17.880
So you could use MySQL or Postgres or something else.

00:01:18.080 --> 00:01:22.280
But he says, kind of in reality, people don't do that.

00:01:22.280 --> 00:01:24.460
People don't really switch databases that much.

00:01:24.460 --> 00:01:33.500
So if you really want to utilize the database and some of the great things about whatever database you pick, maybe not being database agnostic is good.

00:01:33.840 --> 00:01:44.800
Also, he talks about how to set up schemas and database migrations using the database, not using the built-in Django stuff.

00:01:44.800 --> 00:01:48.100
It seems a little bit more like, why would I do that?

00:01:48.100 --> 00:01:51.100
It seems more technical than I want to do with Django.

00:01:51.480 --> 00:01:53.180
But there is some reasoning around it.

00:01:53.180 --> 00:01:58.260
And then he also shows exactly how to do this, how to do migrations, how to do schemas.

00:01:58.260 --> 00:02:00.040
And it really doesn't look that bad.

00:02:00.040 --> 00:02:06.020
The interesting take, I was curious about what the rest of the Django community would feel about this.

00:02:06.140 --> 00:02:10.140
But then after the article, there's comments on the article.

00:02:10.140 --> 00:02:19.040
There's a really nice civilized discussion between the author and someone named Paolo Melchior, I think, and Andrew Godwin.

00:02:19.040 --> 00:02:20.840
Definitely have heard of Andrew before.

00:02:20.840 --> 00:02:25.060
And some others talking about basically that take.

00:02:25.060 --> 00:02:33.940
And one interesting comment was articles like this that point out some of the pitfalls of there possibly are pitfalls with Django.

00:02:33.940 --> 00:02:38.760
And some well-written articles are a good way to point those out.

00:02:38.760 --> 00:02:44.400
And because there's a lot of fans of Django that really aren't going to talk about the bad parts.

00:02:44.400 --> 00:02:46.720
And this isn't necessarily the bad part.

00:02:46.720 --> 00:02:47.900
It's just something to be aware of.

00:02:48.460 --> 00:02:56.500
Another really interesting comment by Andrew was, I agree that at some point in a project or company's life, when it's big enough,

00:02:56.500 --> 00:03:01.260
SQL migrations are the way to go instead of the Django migrations.

00:03:01.260 --> 00:03:06.700
Migrations in the out-of-box state are mostly there to supplement rapid prototyping.

00:03:06.700 --> 00:03:16.760
Like a lot of Django, it can be removed or ignored progressively if and when you outgrow the single set of design constraints when you chose them.

00:03:16.960 --> 00:03:24.200
So that kind of take of using Django's migrations and all the agnostic stuff might be good early on.

00:03:24.200 --> 00:03:30.100
And then maybe slowly going towards using your database more later.

00:03:30.100 --> 00:03:30.680
Yeah.

00:03:30.680 --> 00:03:31.640
That's an interesting take.

00:03:31.640 --> 00:03:32.220
Yeah, that's cool.

00:03:32.220 --> 00:03:35.360
A bit of a practicality beats purity on both ends there.

00:03:35.360 --> 00:03:41.360
This article also made me really appreciate the Django community because this was not a flame war.

00:03:41.360 --> 00:03:44.840
This was a civilized discussion about a technical topic.

00:03:44.840 --> 00:03:46.000
What, on the internet?

00:03:46.000 --> 00:03:46.640
For real?

00:03:46.640 --> 00:03:46.980
Yeah.

00:03:46.980 --> 00:03:49.320
It was great.

00:03:49.320 --> 00:03:50.220
Yeah, that's really cool.

00:03:50.220 --> 00:03:53.440
However, you know, a few comments.

00:03:53.440 --> 00:04:00.940
One, I've switched from one database back in to another three or four times on major projects as you're like, you know what?

00:04:00.940 --> 00:04:04.340
This is just not doing it or it's outgrown this or whatever.

00:04:04.340 --> 00:04:05.460
So it happens.

00:04:05.460 --> 00:04:09.340
But at the same time, like that's usually not my SQL to Postgres.

00:04:09.440 --> 00:04:14.520
It's usually like relational to non-relational or something massive where it's going to require rewrite anyway.

00:04:14.520 --> 00:04:23.520
So I do like the idea of saying you have this capability to be completely agnostic, but you're working with the lowest common denominator there.

00:04:23.720 --> 00:04:27.300
And that's usually not the best choice if you're writing an application.

00:04:27.300 --> 00:04:31.500
Maybe if you're working with a library, tons of people are going to use it in ways you don't anticipate.

00:04:31.740 --> 00:04:35.840
But if it's an application, you know how it's going to be used most often.

00:04:36.020 --> 00:04:36.120
Yeah.

00:04:36.120 --> 00:04:45.440
Also, some of those speed and speed improvements you can get out of a database, you really can't do too much of with the agnostic front end.

00:04:45.440 --> 00:04:48.880
You kind of need to know the specifics of that database.

00:04:48.880 --> 00:04:49.780
Yeah, pretty cool.

00:04:49.780 --> 00:04:55.860
For this next one, I want to talk about an interesting pattern that Python uses, I guess.

00:04:55.860 --> 00:04:57.040
Interesting technique.

00:04:57.040 --> 00:04:59.240
So you know the ID function, right?

00:04:59.240 --> 00:05:05.660
You can say ID of a thing and it'll give you a number back and it basically tells you what it is, like where it is in memory.

00:05:05.660 --> 00:05:06.660
Are you familiar with this?

00:05:06.660 --> 00:05:07.840
I guess I don't use this.

00:05:07.840 --> 00:05:08.080
Yeah.

00:05:08.080 --> 00:05:16.560
So if you want to know, like if I'm giving two variables, are they actually referring to the same object or do they just have the same value?

00:05:16.560 --> 00:05:17.460
Right?

00:05:17.460 --> 00:05:25.060
Like if I had a dictionary and I want to know, is it the same dictionary or does it just have the same keys and the same values for those keys?

00:05:25.500 --> 00:05:30.100
You can say ID of one thing and ID the other.

00:05:30.100 --> 00:05:32.380
And in CPython, that'll actually give you the memory address.

00:05:32.380 --> 00:05:40.340
But in all Python, that gives you a unique identifier that is guaranteed to be different if they're different objects, the same if it's the same object, right?

00:05:40.340 --> 00:05:40.620
Okay.

00:05:40.620 --> 00:05:40.980
Okay.

00:05:40.980 --> 00:05:53.540
So one of the things that Python does that's really interesting, and this is all research I've pulled up from working on my Python for memory management course that is probably out by the time that this comes out.

00:05:53.540 --> 00:05:56.240
But you don't have to take that to care about this.

00:05:56.240 --> 00:06:02.040
So one of the things that's really interesting in Python is everything is a pointer, right?

00:06:02.040 --> 00:06:12.040
Allocated on the heap, including numbers and strings and other small stuff that might be allocated on the stack in languages like C# or C++ or whatever, right?

00:06:12.780 --> 00:06:21.660
So numbers in Python are way more expensive than they are in languages that treat them as value types rather than reference types.

00:06:21.660 --> 00:06:33.440
So for example, the number four uses 28 bytes of memory in Python, whereas the number four could use one, two, four, or eight in the languages that treat them as value types, depending if they're like shorts or longs or whatever.

00:06:33.580 --> 00:06:33.740
Okay.

00:06:33.740 --> 00:06:33.940
Right?

00:06:33.940 --> 00:06:39.040
So there's this cool design pattern called the flyweight pattern.

00:06:39.040 --> 00:06:41.420
And I'll just give you the quick rundown on that.

00:06:41.420 --> 00:06:43.780
So flyweight is a software design pattern.

00:06:43.780 --> 00:06:49.740
A flyweight is an object that minimizes memory usage by sharing as much data as possible with similar objects.

00:06:49.740 --> 00:06:50.220
Right?

00:06:50.220 --> 00:06:51.040
So that's from Wikipedia.

00:06:51.040 --> 00:06:51.780
I'll link over to that.

00:06:52.280 --> 00:06:54.540
In Python, Python does that for numbers.

00:06:54.540 --> 00:07:10.400
So if you compute, like through some mathematical function, if you compute the number 16 and then some other way you compute the number 16 and then somewhere else you parse a string, the number 16, those are all literally the same 16 in memory.

00:07:10.400 --> 00:07:10.780
Okay.

00:07:10.780 --> 00:07:11.140
Okay?

00:07:11.140 --> 00:07:13.480
Because 16 is pretty common.

00:07:13.480 --> 00:07:20.100
But if you computed 423, the three different ways, that would be three copies of 423.

00:07:20.800 --> 00:07:25.420
So Python uses this flyweight pattern for the numbers from negative 5 to 256.

00:07:25.420 --> 00:07:30.340
And you'll only ever have one of those in the language, in the runtime.

00:07:30.340 --> 00:07:36.960
But beyond 256 or below negative 5, those are always recreated.

00:07:36.960 --> 00:07:37.820
Isn't that interesting?

00:07:37.820 --> 00:07:38.880
It is very interesting.

00:07:38.880 --> 00:07:39.140
Yeah.

00:07:39.140 --> 00:07:41.220
So it doesn't matter how they come out.

00:07:41.220 --> 00:07:49.000
Basically, if the runtime is going to generate the number, say, 7 as an integer, it's going to use the same 7, which is pretty cool.

00:07:49.420 --> 00:07:51.660
I actually have some example code that people can play with.

00:07:51.660 --> 00:07:59.500
It creates like two lists of a whole bunch of numbers, separate ways, and then says, you know, are these the same number or not, which is pretty cool.

00:07:59.500 --> 00:08:00.820
I was just playing with it right now.

00:08:00.820 --> 00:08:07.640
So you can, if you assign x to 1, you can do an ID of both x and 1, and it'll show up as the same number.

00:08:07.760 --> 00:08:13.220
But if you assign x to minus 10, x and minus 10 are different IDs.

00:08:13.220 --> 00:08:13.860
Isn't that funky?

00:08:13.860 --> 00:08:14.580
Yeah.

00:08:14.580 --> 00:08:17.860
It's because the numbers in Python are extra expensive.

00:08:17.860 --> 00:08:23.400
So Python takes special care to not recreate these very common numbers.

00:08:23.400 --> 00:08:26.120
And apparently, very common means negative 5 to 256.

00:08:26.380 --> 00:08:34.820
Anyway, I thought that might be interesting to people, this flyweight design pattern concept and then applied to the numbers might be interesting.

00:08:34.820 --> 00:08:36.940
And there's a little example code that I included it there.

00:08:36.940 --> 00:08:40.680
So it's not quite an article, but it's like an idea with some code.

00:08:40.880 --> 00:08:40.960
Yeah.

00:08:40.960 --> 00:08:45.200
So can you, I mean, as a user, can I use the flyweight pattern in Python for other stuff?

00:08:45.200 --> 00:08:46.040
You totally should.

00:08:46.040 --> 00:08:46.520
Yeah.

00:08:46.520 --> 00:08:54.120
Like imagine you've got some objects you're creating and instead of recreating them over and over, they're being used in a lot of places.

00:08:54.120 --> 00:08:59.240
You could totally create some kind of like shared lookup for certain common ones.

00:08:59.240 --> 00:09:03.920
Like maybe you create, you're creating states and the state has a bunch of information about it.

00:09:03.920 --> 00:09:06.260
Like US states or countries or something.

00:09:06.260 --> 00:09:08.860
But then you often have to go like, all right, what state is this?

00:09:08.860 --> 00:09:09.720
Give me that information.

00:09:09.720 --> 00:09:10.160
Right.

00:09:10.160 --> 00:09:11.520
You don't need to necessarily recreate that.

00:09:11.520 --> 00:09:14.920
You could just create 50 states, keep them in memory and never allocate them again.

00:09:14.920 --> 00:09:15.380
Okay.

00:09:15.380 --> 00:09:22.660
I guess I'm like caching and memoization are ways to do something similar, but with only one thing at a time.

00:09:22.660 --> 00:09:23.100
Exactly.

00:09:23.100 --> 00:09:27.860
The big important thing here to make this work correctly is they have to be immutable.

00:09:27.860 --> 00:09:28.620
Right.

00:09:28.620 --> 00:09:35.620
Because if one person gets the state Georgia and it has certain values and another person gets it, oh, it has a new county.

00:09:35.620 --> 00:09:36.240
Let's add that.

00:09:36.240 --> 00:09:36.880
And like, wait a minute.

00:09:36.880 --> 00:09:37.340
That's not.

00:09:38.000 --> 00:09:44.060
I've now not recreated a different thing or like, you know, so it's got to be immutable, which is why it works for numbers.

00:09:44.060 --> 00:09:46.180
And you could do it for strings and things like that.

00:09:46.180 --> 00:09:46.460
Okay.

00:09:46.460 --> 00:09:46.840
Cool.

00:09:46.840 --> 00:09:47.560
Yeah.

00:09:47.560 --> 00:09:47.980
Pretty cool.

00:09:47.980 --> 00:09:50.260
Something else that's really cool is Datadog.

00:09:50.380 --> 00:09:52.940
So thank you, Datadog for sponsoring this episode.

00:09:52.940 --> 00:09:54.300
Let me ask you a question.

00:09:54.300 --> 00:09:57.540
Do you have an app in production that's slower than you like?

00:09:57.540 --> 00:10:00.980
It's performance all over the place, sometimes fast, sometimes slow.

00:10:00.980 --> 00:10:02.820
Now here's an important question.

00:10:02.820 --> 00:10:04.080
Do you know why?

00:10:04.080 --> 00:10:05.980
With Datadog, you will.

00:10:05.980 --> 00:10:10.260
You can troubleshoot your app's performance with Datadog's end-to-end tracing.

00:10:10.820 --> 00:10:16.440
Use the detailed flame graphs to identify bottlenecks and latency in that finicky app of yours.

00:10:16.440 --> 00:10:20.940
Be the hero that got the app back on track with your company.

00:10:20.940 --> 00:10:26.120
Get started today with a free trial at pythonbytes.fm/Datadog.

00:10:26.120 --> 00:10:26.420
Awesome.

00:10:26.420 --> 00:10:27.120
Thanks, Datadog.

00:10:27.120 --> 00:10:28.020
You know what else is awesome?

00:10:28.020 --> 00:10:28.560
What is awesome?

00:10:28.560 --> 00:10:34.020
Pip installing a thing that when I pip install something and it happens right away and it's

00:10:34.020 --> 00:10:39.900
not like 30 seconds of compile time, like say MicroWhisgy is, to get the thing installed

00:10:39.900 --> 00:10:44.900
and I don't have to have like MSBuild or VCVarsBat set up right or whatever.

00:10:44.900 --> 00:10:47.020
Yeah.

00:10:47.020 --> 00:10:49.240
So definitely I'm grateful for wheels.

00:10:49.240 --> 00:10:54.620
It was still a world that we didn't, there was less wheels in it when we started this podcast.

00:10:54.620 --> 00:10:55.560
I'm pretty sure.

00:10:55.560 --> 00:10:55.940
Yep.

00:10:56.260 --> 00:11:02.400
Most of the common packages, a lot of them have migrated to distributing wheels and package

00:11:02.400 --> 00:11:04.560
authors have had to care about this a lot.

00:11:04.560 --> 00:11:10.680
And so I want to talk about this article that's on the RealPython blog from Brad Solomon called

00:11:10.680 --> 00:11:13.020
What are Python Wheels and Why Should We Care?

00:11:13.020 --> 00:11:18.240
One of the things I really love about this is like I said, a lot of package authors have already

00:11:18.240 --> 00:11:21.220
gone through this and understand some of the ramifications.

00:11:21.540 --> 00:11:27.440
But as a normal casual user of pip install, we don't really think about it.

00:11:27.440 --> 00:11:33.060
But this is the first half of this article talks about kind of what the user's perspective is.

00:11:33.060 --> 00:11:34.820
And it's kind of a nice look.

00:11:34.820 --> 00:11:41.080
When you say pip install something and it's a cool because it as an example, I'm glad they list an example.

00:11:41.080 --> 00:11:46.780
And it's a particular version of MicroWhisky because most packages are wheels now.

00:11:46.880 --> 00:11:50.840
But if you install something that is not a wheel, it's probably a tarball.

00:11:50.840 --> 00:11:53.940
And I don't know if there's other options other than tarballs.

00:11:53.940 --> 00:11:57.660
But anyway, a tarball is something that ends in tar.gz.

00:11:57.660 --> 00:12:00.460
So it's a tarred and zipped.

00:12:00.460 --> 00:12:03.300
And that's a whole bunch of Unix speak that you don't really have to care about.

00:12:03.300 --> 00:12:07.020
But it downloads this blob of stuff and then unpacks it.

00:12:07.220 --> 00:12:12.660
And then pip calls setup and some other stuff to build the wheel after you download it.

00:12:12.660 --> 00:12:16.340
And then it labels it and then it installs it.

00:12:16.340 --> 00:12:18.020
There's a whole bunch of steps in there.

00:12:18.020 --> 00:12:19.880
Plus, it's calling setup.py.

00:12:19.880 --> 00:12:21.760
So there could be really any code in there.

00:12:21.760 --> 00:12:23.440
And so that's kind of creepy.

00:12:23.440 --> 00:12:31.640
The difference is often with if you actually have a wheel instead of a tarball, pip install will just pull this down and install it.

00:12:31.640 --> 00:12:33.260
And it doesn't call setup.py.

00:12:33.260 --> 00:12:40.860
That's really nice, actually, because one of the things I think a lot of people don't realize until they're like, oh, wait, what just happened?

00:12:40.860 --> 00:12:46.360
When you pip install something, you're running semi-arbitrary code off of the internet.

00:12:46.360 --> 00:12:48.080
That's not ideal.

00:12:48.080 --> 00:12:48.440
Right.

00:12:48.440 --> 00:12:50.020
With the wheels, you don't have to run.

00:12:50.020 --> 00:12:54.400
Because basically that runs the setup.py in the sdisk version, I believe.

00:12:54.400 --> 00:12:59.380
So this is really nice that wheels can cut out that Python execution bit.

00:12:59.380 --> 00:13:00.260
It cuts that out.

00:13:00.380 --> 00:13:03.000
Plus, also, I'm not sure what the technology is here.

00:13:03.000 --> 00:13:08.160
Well, I think it's probably just it's already pre-compiled and there's operating system specifics.

00:13:08.160 --> 00:13:12.940
But wheels tend to be smaller than the tarballs, so they download a lot faster.

00:13:12.940 --> 00:13:15.580
Wheels have a bunch of stuff in the name.

00:13:15.580 --> 00:13:17.440
And it's not just random stuff.

00:13:17.440 --> 00:13:18.880
It's specific stuff.

00:13:18.880 --> 00:13:21.600
But it talks about what distribution it is.

00:13:21.600 --> 00:13:22.640
It's got the version number.

00:13:22.640 --> 00:13:27.780
It's got maybe build identifiers and which Python it's for.

00:13:27.780 --> 00:13:31.400
If it's Python 2 versus Python 3 or a specific version.

00:13:31.400 --> 00:13:35.140
And then the platform is one of the important bits.

00:13:35.140 --> 00:13:43.320
So if you have compiled code, then there's kind of a different CI pipeline to try to build all those wheels.

00:13:43.580 --> 00:13:46.400
But on the user end, we don't have to care about it.

00:13:46.400 --> 00:13:56.880
So one of the different things is one of the interesting bits about going and moving towards wheels is there's a whole bunch, often a whole bunch of different, whole bunch of packages up there.

00:13:56.880 --> 00:14:02.360
And that's something that users will see if they look at what downloads are available.

00:14:02.360 --> 00:14:03.860
There'll be this whole slew of stuff.

00:14:04.560 --> 00:14:06.400
And for the most part, you don't have to care about that.

00:14:06.400 --> 00:14:09.860
If you do pip install, it'll just pick the right one for your operating system.

00:14:09.860 --> 00:14:24.300
However, it's good to be aware of those because if you are creating like a cache of stuff at your, if you have your office or something, you may want to cache more of those depending on what operating systems are being used around.

00:14:24.300 --> 00:14:26.920
So that little discussion, I think, is pretty cool.

00:14:26.920 --> 00:14:27.360
Absolutely.

00:14:27.360 --> 00:14:29.260
Anyway, I'm not going to get too much into it.

00:14:29.260 --> 00:14:33.400
This is a good article for, yeah, I use wheels, but what are they?

00:14:33.580 --> 00:14:36.500
And this is, this doesn't get too deep into it, but it's nice.

00:14:36.500 --> 00:14:36.800
Yeah.

00:14:36.800 --> 00:14:38.300
Well, wheels are definitely nice.

00:14:38.300 --> 00:14:41.160
And another solid article from RealPython.

00:14:41.160 --> 00:14:41.960
So very nice.

00:14:41.960 --> 00:14:43.320
You know what else is good?

00:14:43.320 --> 00:14:44.400
Pandas.

00:14:44.400 --> 00:14:46.580
I've heard that Pandas does a lot of cool stuff.

00:14:46.580 --> 00:14:48.280
Now, actually, Pandas is really, really cool.

00:14:48.280 --> 00:14:50.460
You could do a whole bunch of interesting things with it.

00:14:50.460 --> 00:14:55.060
And Jack McHugh, he's been on fire lately.

00:14:55.060 --> 00:14:58.800
He's created all these different projects that he keeps sending them over and like, oh, this is another one I found.

00:14:58.800 --> 00:15:00.140
He's like, no, this is another one I created.

00:15:00.140 --> 00:15:01.940
And a lot of them are cool.

00:15:02.120 --> 00:15:04.320
One of the things he created was awesome Python bytes.

00:15:04.320 --> 00:15:05.860
So hat tip to Jake on that.

00:15:05.860 --> 00:15:06.220
That's cool.

00:15:06.220 --> 00:15:08.780
Like all the awesome stuff that we happen to have covered periodically.

00:15:08.780 --> 00:15:11.740
But this one is called Pandas Alive.

00:15:11.740 --> 00:15:19.980
And so, Brian, to get the experience of this one, you need to open it up and just scroll through the readme on the GitHub page and just look at the animations.

00:15:20.720 --> 00:15:27.320
So you probably have seen these racing histograms or racing bar charts that show stuff happening over time.

00:15:27.320 --> 00:15:30.980
Like here's the popularity of web browsers all the way back from 1993.

00:15:31.420 --> 00:15:35.480
Where it was Mozilla and then Netscape and then IE and then, you know, whatever.

00:15:35.480 --> 00:15:37.780
And you see them like growing and moving over time.

00:15:37.780 --> 00:15:38.060
Yeah.

00:15:38.060 --> 00:15:48.600
So this is a package that if you have a Pandas data frame in a really simple format where the columns are basically the different things you want to graph.

00:15:49.160 --> 00:15:51.980
And they're all arranged by a common date.

00:15:51.980 --> 00:15:53.000
And they just have numbers.

00:15:53.000 --> 00:16:02.640
You can turn that into a really cool like bar chart race type of thing or line graph race where it's just this animation of those over time of the dates that you have in there.

00:16:02.640 --> 00:16:03.740
Oh, I really like this.

00:16:03.740 --> 00:16:04.420
Isn't this cool?

00:16:04.620 --> 00:16:04.780
Yeah.

00:16:04.780 --> 00:16:07.760
And the, I mean, like the race charts and stuff, those are cool.

00:16:07.760 --> 00:16:13.660
But then you can also do the, like the line, the line graphs, the growing, zooming.

00:16:13.660 --> 00:16:14.120
Yeah.

00:16:14.120 --> 00:16:14.280
Yeah.

00:16:14.280 --> 00:16:19.800
You can do like line graphs and you can do other types of things, little plot, scatter plot type things.

00:16:19.800 --> 00:16:23.580
You can also do pie charts, but you can even have them together.

00:16:23.580 --> 00:16:25.560
So you can have maps.

00:16:25.560 --> 00:16:31.340
So if you want to have a map evolving over time with like different countries or counties fading in and out,

00:16:32.060 --> 00:16:36.180
you could have like those two graphs animated side by side at the same time.

00:16:36.180 --> 00:16:42.180
So you could have like the chart of the bars as well as the map all animated together in like one graph.

00:16:42.180 --> 00:16:42.440
Cool.

00:16:42.440 --> 00:16:43.300
Seems pretty awesome.

00:16:43.300 --> 00:16:44.380
Well done, Jack.

00:16:44.380 --> 00:16:47.720
It's based on, I believe, matplotlib.

00:16:47.720 --> 00:16:53.640
And basically it'll render a bunch of different matplotlib renderings into an animated GIF.

00:16:53.640 --> 00:16:59.480
So all you have to do is just go like dataframe.plotanimated, give it a file name, and then this happens.

00:16:59.480 --> 00:17:00.140
Oh, that's cool.

00:17:00.260 --> 00:17:03.220
So then you can just generate this GIF and then put it wherever.

00:17:03.220 --> 00:17:03.780
Exactly.

00:17:03.780 --> 00:17:04.620
You can put it on your website.

00:17:04.620 --> 00:17:05.680
You can put it wherever you want.

00:17:05.680 --> 00:17:07.360
You can share it on Twitter, I guess, even.

00:17:07.360 --> 00:17:07.960
Right?

00:17:07.960 --> 00:17:15.140
But it doesn't require like a JavaScript backend running something and your Jupyter notebook and then all that kind of stuff to wire up.

00:17:15.140 --> 00:17:17.200
Like, no, it's just an animated GIF that comes out.

00:17:17.200 --> 00:17:17.480
Neat.

00:17:17.480 --> 00:17:18.520
This is mesmerizing.

00:17:18.520 --> 00:17:19.740
I could just watch these all day.

00:17:19.740 --> 00:17:21.400
You could watch it for quite a while.

00:17:21.400 --> 00:17:22.560
So, yeah.

00:17:22.560 --> 00:17:29.700
Anyway, really think that's a cool project if you want to visualize data over time, which, you know, there's a lot of good reasons to do that.

00:17:29.700 --> 00:17:32.700
One of the things that it has there is animated maps.

00:17:32.700 --> 00:17:35.120
But maps are something else also.

00:17:35.120 --> 00:17:39.240
There's also a map function, which has nothing to do with geographic maps.

00:17:39.440 --> 00:17:44.460
You probably learned a Python a long time ago, but do you remember being surprised by map and all?

00:17:44.460 --> 00:17:48.600
Yeah, map and all those things, they always confuse me and I've always tried to basically avoid them.

00:17:48.600 --> 00:17:51.820
And I've successfully mostly done that.

00:17:51.820 --> 00:17:53.340
But I know also.

00:17:53.340 --> 00:17:53.900
Yeah, yeah.

00:17:53.900 --> 00:17:55.240
I also know how useful they can be.

00:17:55.240 --> 00:17:56.340
So tell us about it.

00:17:56.340 --> 00:18:01.220
This is an article from Catherine Hancock's How to Use the Python Map Function.

00:18:01.220 --> 00:18:06.200
And I know we're sure people have heard of maps and map, the map function.

00:18:06.620 --> 00:18:09.580
It's an extremely useful function, a useful thing.

00:18:09.580 --> 00:18:11.580
So it's a built-in.

00:18:11.580 --> 00:18:16.200
And what it does, if you're not familiar with it, it takes two or more parameters.

00:18:16.200 --> 00:18:19.860
The first parameter to map is the function that you want to apply.

00:18:19.860 --> 00:18:25.820
And then, like, let's say if you give it as the second argument an iterable, like a list or something,

00:18:25.820 --> 00:18:32.020
it takes that function that you passed in and applies it to absolutely every element of the iterable, the other one.

00:18:32.720 --> 00:18:42.460
So, like, if I have quick, like, the normal often use is using a lambda function or something to apply some, like, quick thing.

00:18:42.460 --> 00:18:48.000
Like, if I want to do x times squared, x times squared, x times two or x squared or something like that.

00:18:48.000 --> 00:18:50.040
And apply that to every element.

00:18:50.040 --> 00:18:51.060
You can do that.

00:18:51.060 --> 00:18:52.680
And you can make one list into another.

00:18:53.440 --> 00:19:03.940
I think it's good for people to, like, read about them every once in a while if they're not using them often because they do come in handy in places that you, all the time, for me at least.

00:19:04.100 --> 00:19:09.020
So, it's not an obvious thing if you're not used to this sort of a function from other languages.

00:19:09.020 --> 00:19:11.040
I wasn't coming from C.

00:19:11.040 --> 00:19:11.380
Yeah.

00:19:11.380 --> 00:19:14.100
And maybe Perl has something like this, but I never used it.

00:19:14.100 --> 00:19:16.900
So, that's the normal use of applying it.

00:19:16.900 --> 00:19:20.860
One of the things I like about this tutorial is it goes through a few different things.

00:19:20.860 --> 00:19:27.380
So, applying lambdas to a list or an iterable, and then the function that you apply doesn't have to be a lambda.

00:19:27.380 --> 00:19:32.280
It could be your own user-defined function, or it could be a built-in function that you map to it.

00:19:32.280 --> 00:19:40.260
I want to, like, warn people, the part where she's talking about the user-defined function, it's oddly complex for some reason.

00:19:40.260 --> 00:19:48.140
I'm not sure why this was made so complex, because a user-defined function just works like anything, any other function that's using for map.

00:19:48.300 --> 00:20:00.160
But one of the things that I even got out of it is I had forgotten that map applies the function to the iterable one element at a time, and it doesn't do it ahead of time.

00:20:00.160 --> 00:20:03.180
So, like, for instance, and I'm like, really?

00:20:03.180 --> 00:20:08.040
And I had to, like, prove it to myself by putting a print statement or something in a function to do it.

00:20:08.040 --> 00:20:15.820
But what happens is, like, let's say I've got iterable hooked up to grab, like, a huge data chunk out of a stream or something.

00:20:15.980 --> 00:20:23.760
I can apply some function to each element as I'm pulling it out and using map to do that, so I can iterate over map.

00:20:23.760 --> 00:20:28.820
So map returns a map object, which, whatever, it doesn't matter.

00:20:28.820 --> 00:20:35.500
It's just every element that you use, if you use it as an iteration, is the answer after you apply the function.

00:20:35.500 --> 00:20:37.800
It's like a custom generator type thing.

00:20:37.980 --> 00:20:38.260
Yeah.

00:20:38.260 --> 00:20:46.360
And then if you want it as something solid, you can convert it to a list or a tuple or something like that if you want to do everything.

00:20:46.360 --> 00:20:47.480
I'm done with generators.

00:20:47.480 --> 00:20:48.100
Throw it in a list.

00:20:49.440 --> 00:20:50.820
There's some honesty here, too.

00:20:50.820 --> 00:20:55.460
The other thing I often forget about map is that you can map it across.

00:20:55.460 --> 00:21:01.060
If you have a function that takes multiple arguments, you can pass it multiple iterables.

00:21:01.060 --> 00:21:04.160
And it'll take, you know, element-wise each one.

00:21:04.160 --> 00:21:12.640
So, like, the nth element out of each list and apply, pass it to the function and then return the answer to that, which is cool.

00:21:12.700 --> 00:21:19.780
The other thing, a good comment in this, because it's a similar problem area, is comprehensions kind of do the same thing.

00:21:19.780 --> 00:21:22.940
So when would you use map versus comprehension?

00:21:22.940 --> 00:21:32.040
And the advice in this article is comprehensions are very useful for smaller data sets, but often for large data sets, map can be more powerful.

00:21:32.040 --> 00:21:33.300
So that's reasonable.

00:21:33.300 --> 00:21:40.840
And sometimes you want to do operations that if you had to go over different collections of data would make a really nasty-looking comprehension and stuff.

00:21:40.840 --> 00:21:41.820
So, yeah, cool.

00:21:41.820 --> 00:21:42.120
Yep.

00:21:42.240 --> 00:21:46.560
You also can do, like, pandas type of things a little bit, like multiplying vectors, right?

00:21:46.560 --> 00:21:52.200
Like, if I've got two lists and I want to have pieces put together, like that power example that's in there, right?

00:21:52.200 --> 00:22:04.260
It'll take the first element of the first one, the second element of the second one, and then apply the function and generate a new list, effectively, that has, like, as if you had sort of done vector multiplication, which is cool.

00:22:04.260 --> 00:22:07.140
Or, like, cross, I don't know, cross multiplication.

00:22:07.140 --> 00:22:07.700
Yeah.

00:22:07.900 --> 00:22:14.520
I often use map also when I want to muck with something and it seems a little cleaner to me to iterate through something.

00:22:14.520 --> 00:22:21.000
If I know I'm looking for something and I'm not going to get the end of the data or I'm using endless data.

00:22:21.100 --> 00:22:21.260
Nice.

00:22:21.260 --> 00:22:25.940
So, we spoke earlier about databases and I've got another one for us.

00:22:25.940 --> 00:22:28.680
This cool thing called AutoMigrate.

00:22:28.680 --> 00:22:29.880
It's a project called AutoMigrate.

00:22:29.880 --> 00:22:30.240
Okay.

00:22:30.420 --> 00:22:38.560
So, what it does is it's kind of like you talked about Django migrations and we also have SQLAlchemy migrations with Olympic.

00:22:38.560 --> 00:22:52.240
But some people, either they're not using an ORM at all, in which case those tools are useless, or they want to very carefully write the SQL scripts that control their databases.

00:22:52.240 --> 00:22:58.340
Like, some people, there's, like, a group of DBAs that manage the database and that's that, right?

00:22:58.340 --> 00:23:01.380
We're not going to run just random tooling against the database.

00:23:01.380 --> 00:23:03.860
We're going to run scripts that are very carefully considered.

00:23:04.660 --> 00:23:16.880
So, this AutoMigrate thing, what it will do is if you have a, those DDL, data definition language scripts that say create table, add column, and so on.

00:23:16.880 --> 00:23:21.620
All it has to do is have the script that will say, here's how we create something from scratch.

00:23:21.620 --> 00:23:25.460
You put that into GitHub and then you make changes to it.

00:23:25.460 --> 00:23:30.380
Like, to add a column, I go and edit the create table thing and I just type in the new column in there.

00:23:30.580 --> 00:23:39.440
And what this will do is it'll look at your git history and it'll do diffs on the create table statements and it will generate the migration scripts from that.

00:23:39.440 --> 00:23:40.520
Oh, that's really cool.

00:23:40.520 --> 00:23:41.140
That's neat, right?

00:23:41.140 --> 00:23:48.640
So, all you got to do is, like, maintain the, here's how I create the database and it'll actually go, well, to go from this version to that version, here's the script that would actually do it.

00:23:48.640 --> 00:23:49.760
It'll do all that stuff for you.

00:23:49.760 --> 00:23:50.460
Oh, nice.

00:23:50.460 --> 00:23:50.780
Yeah.

00:23:50.780 --> 00:23:58.360
So, if that's your flow, if your flow is to work with these DDL files, these SQL files, this seems like a great tool.

00:23:58.480 --> 00:24:08.820
Now, they do say, oh, this is way better than, like, an ORM or something because in those, like Alembic, what you have to do is you have to go and write the migration scripts.

00:24:08.820 --> 00:24:10.160
Here's how you migrate up.

00:24:10.160 --> 00:24:11.320
Here's how you migrate down.

00:24:11.320 --> 00:24:19.080
But they left out a little important thing, --auto generate, which looks at all of your classes in your database and go, here's the difference.

00:24:19.080 --> 00:24:23.440
We automatically wrote that for you, which I think is way nicer even than this project.

00:24:23.440 --> 00:24:28.560
So, I think Alembic is better, but the big requirement there is you are using SQLAlchemy.

00:24:28.560 --> 00:24:43.960
If you're not using SQLAlchemy to do these migrations, then this tool, but you're using these scripts instead to define your database, like, I'm sure a lot of, like, especially the larger companies where there's, like, a database team or, like, DBAs and so on are doing.

00:24:44.100 --> 00:24:46.060
And this seems like a really cool project for it.

00:24:46.060 --> 00:24:46.320
Yeah.

00:24:46.320 --> 00:24:49.580
That said, the converse is actually pretty cool.

00:24:49.580 --> 00:24:54.980
So, what it can do is it can look at a database and it will generate your SQLAlchemy files for you.

00:24:54.980 --> 00:24:56.140
That's pretty cool.

00:24:56.140 --> 00:24:56.820
That's nice.

00:24:56.980 --> 00:25:04.040
Yeah, it'll generate ORM definitions from SQL, right, using the SQLAlchemy generator, which is pretty awesome.

00:25:04.040 --> 00:25:12.080
So, you can say, here is my create table scripts, generate me the corresponding SQLAlchemy thing to match that.

00:25:12.080 --> 00:25:14.720
So, in that direction, it's pretty awesome also.

00:25:14.720 --> 00:25:15.760
So, which does that?

00:25:15.760 --> 00:25:16.960
This one, this auto migrate.

00:25:16.960 --> 00:25:22.800
It'll look at your DDL, like, create these table scripts, and it'll turn it into Python SQLAlchemy classes.

00:25:23.360 --> 00:25:27.880
But the reverse, it was saying, like, oh, it's painful to use Olympic in the other direction.

00:25:27.880 --> 00:25:32.260
But if you use the auto generate feature of Olympic, then it's also not painful.

00:25:32.260 --> 00:25:36.380
But there's certainly a couple of use cases that are pretty awesome here.

00:25:36.380 --> 00:25:46.460
One, like, starting from all the create stuff, like, given a database, just ramp me up to getting a SQLAlchemy set of classes that'll talk to it as quick as possible.

00:25:46.460 --> 00:25:47.220
That's really cool.

00:25:47.220 --> 00:25:55.820
If I've got a schema change, is there a version number that's stored in the database somewhere to say which version of the schema is being used?

00:25:55.820 --> 00:25:58.580
Yeah, I have no idea about this thing.

00:25:58.580 --> 00:26:01.780
With SQLAlchemy and Olympic, there is a version number.

00:26:01.780 --> 00:26:04.180
It says, I'm version some hash.

00:26:04.180 --> 00:26:07.420
And then all the migrations, one of those is the hash.

00:26:07.420 --> 00:26:11.460
And each migration says, the one that came before me is this, and the one that comes after me is that.

00:26:11.540 --> 00:26:15.140
They can look at an existing database and say, your version X.

00:26:15.140 --> 00:26:16.200
Yes, exactly.

00:26:16.200 --> 00:26:18.160
For Olympic, I have no idea about this thing.

00:26:18.160 --> 00:26:27.740
This thing could potentially look at the table, basically run it, like, script this create table stuff for me, and then look at that compared to what it has.

00:26:27.740 --> 00:26:29.700
Potentially, I have no idea if it's that smart, though.

00:26:29.700 --> 00:26:29.980
Okay.

00:26:29.980 --> 00:26:31.900
Yeah, but it looks like it could be handy for a lot of folks.

00:26:31.900 --> 00:26:36.140
Well, I've had a rough week, so I've got no extra stuff.

00:26:36.140 --> 00:26:37.280
No extra stuff?

00:26:37.280 --> 00:26:38.380
No extra stuff.

00:26:38.420 --> 00:26:39.460
I don't have too much either.

00:26:39.460 --> 00:26:40.740
I have a little bit.

00:26:40.740 --> 00:26:45.240
I just want to give a shout out that we have a ton of new courses coming.

00:26:45.240 --> 00:26:55.720
And I want to just encourage people, if they're interested in these, to go to training. talkpython.fm/getnotified and put the email there if they haven't created an account or signed up there before.

00:26:55.720 --> 00:27:00.360
Because we have Excel, moving from Excel to Python with pandas coming out.

00:27:00.360 --> 00:27:03.080
We have getting started with data science coming out.

00:27:03.080 --> 00:27:05.340
We have Python memory management tips coming out.

00:27:05.960 --> 00:27:08.860
Those all three will probably be within, like, a couple of weeks.

00:27:08.860 --> 00:27:12.560
And then getting started with Git and Python design patterns as well.

00:27:12.560 --> 00:27:13.640
So there's a bunch of cool stuff.

00:27:13.640 --> 00:27:16.900
If you want to hear about any of those, just be sure to get on the mail list.

00:27:16.900 --> 00:27:17.440
Oh, wow.

00:27:17.440 --> 00:27:18.000
That's cool.

00:27:18.000 --> 00:27:21.460
If I didn't talk to you every week, I would totally get on this mailing list.

00:27:21.460 --> 00:27:22.580
Awesome.

00:27:22.580 --> 00:27:25.040
Actually, I think I'm already on it.

00:27:25.040 --> 00:27:26.000
I'm sure you are.

00:27:26.000 --> 00:27:28.320
Because you do talk to me, though, you get jokes.

00:27:28.320 --> 00:27:28.900
Definitely.

00:27:29.080 --> 00:27:30.800
But everybody who listens gets them also.

00:27:30.800 --> 00:27:31.280
That's right.

00:27:31.280 --> 00:27:33.880
This is a fun game to play.

00:27:33.880 --> 00:27:40.240
The idea is you take some actual, legitimate, classical painting.

00:27:40.240 --> 00:27:53.520
And if you go to an art gallery, it'll say, like, flowers in bloom, oil canvas, Monet 1722, or something like that,

00:27:53.580 --> 00:27:54.860
like, in the little placard underneath.

00:27:54.860 --> 00:28:00.740
So the game is to reinterpret these paintings in modern tech speak.

00:28:00.740 --> 00:28:01.880
Okay?

00:28:01.880 --> 00:28:02.240
Yeah.

00:28:02.340 --> 00:28:03.720
So here, I'll do the first one.

00:28:03.720 --> 00:28:07.220
I put three in the show notes that people can check out.

00:28:07.220 --> 00:28:09.700
I'll describe this to you, and then I'll read the little thing.

00:28:09.700 --> 00:28:18.340
So there's, like, a ship that seems to be on fire with some extremely strong guys trying to drag the ship out of the water.

00:28:18.340 --> 00:28:20.800
Maybe, no, they're pushing it into the water.

00:28:20.800 --> 00:28:24.100
And a bunch of folks on the edge sitting off.

00:28:24.100 --> 00:28:25.360
It's like a Viking ship.

00:28:25.360 --> 00:28:27.160
I think they're actually cremating.

00:28:27.160 --> 00:28:27.860
Somebody's sitting out.

00:28:27.920 --> 00:28:33.360
Anyway, it's this historical picture, and it says, the placard says,

00:28:33.360 --> 00:28:36.920
Engineers remove dead code after dropping a feature flag.

00:28:36.920 --> 00:28:42.620
Sir Frank Bernard Dixie, 1893, oil on canvas.

00:28:42.620 --> 00:28:45.400
You want to do the next one?

00:28:45.400 --> 00:28:45.980
Oh, sure.

00:28:45.980 --> 00:28:47.240
Pull it up.

00:28:47.240 --> 00:28:48.360
Oh, okay.

00:28:48.360 --> 00:28:50.340
How do I describe this?

00:28:50.340 --> 00:28:53.180
This is like a picture.

00:28:53.180 --> 00:28:54.540
It's a Picasso picture.

00:28:54.540 --> 00:28:57.560
It's like an abstract violin that explodes or something.

00:28:58.400 --> 00:28:58.560
Yeah.

00:28:58.560 --> 00:29:00.880
It's hard to tell, really, what's going on.

00:29:00.880 --> 00:29:02.280
It kind of looks like a violin.

00:29:02.280 --> 00:29:05.720
And the title is CSS Without Comments.

00:29:05.720 --> 00:29:06.440
That's good.

00:29:06.440 --> 00:29:10.180
Pablo Picasso, 1912.

00:29:10.180 --> 00:29:10.800
All right.

00:29:10.800 --> 00:29:11.620
The last one.

00:29:11.620 --> 00:29:12.860
The last one we'll do.

00:29:12.860 --> 00:29:14.840
By the way, there's hundreds of these.

00:29:14.840 --> 00:29:15.660
They're all really good.

00:29:15.660 --> 00:29:18.560
So this one is a little disturbing.

00:29:18.560 --> 00:29:23.400
There's a person who looks deathly ill with a bunch of, like, gargoyles over them.

00:29:23.400 --> 00:29:30.200
A priest with a crucifix, kind of glowing, apparently trying to ward off the gargoyles.

00:29:30.200 --> 00:29:35.220
And the placard says, experienced developer deploys hotfix on production.

00:29:35.220 --> 00:29:38.900
Francisco Goya, oil on canvas, circa 1788.

00:29:38.900 --> 00:29:40.040
That's good.

00:29:40.040 --> 00:29:40.300
Yeah.

00:29:40.300 --> 00:29:41.920
So there's just so many of these.

00:29:41.920 --> 00:29:43.040
You can go through them all day.

00:29:43.040 --> 00:29:43.900
It's really fun.

00:29:43.900 --> 00:29:46.720
Didn't PyCon do that once at, like, one of the PyCons?

00:29:46.860 --> 00:29:47.880
I think you might have been with us.

00:29:47.880 --> 00:29:53.740
I know Chris Medina, Kelsey Hightower, and I were walking around the Portland Art Museum,

00:29:53.740 --> 00:29:56.000
like, basically playing this game.

00:29:56.000 --> 00:29:57.340
We were, like, coming up with a placard.

00:29:57.340 --> 00:29:57.880
It was fun.

00:29:57.880 --> 00:29:58.680
And were you there for that?

00:29:58.680 --> 00:29:59.180
You might have been.

00:29:59.180 --> 00:30:00.000
No, I wasn't.

00:30:00.000 --> 00:30:01.160
I missed that one, but.

00:30:01.160 --> 00:30:02.320
That was good.

00:30:02.320 --> 00:30:04.620
Remember that one when you could go to conferences?

00:30:04.620 --> 00:30:06.420
If there were people around you?

00:30:06.420 --> 00:30:07.020
Other people?

00:30:07.020 --> 00:30:08.020
Close?

00:30:08.020 --> 00:30:08.760
It was weird.

00:30:08.760 --> 00:30:13.400
Actually, we don't need anybody to contact us and tell us that we have no idea when different

00:30:13.400 --> 00:30:14.300
painters were alive.

00:30:15.040 --> 00:30:15.820
But thanks.

00:30:15.820 --> 00:30:16.640
And cool.

00:30:16.640 --> 00:30:17.760
Good for you if you know it.

00:30:17.760 --> 00:30:18.860
Awesome.

00:30:18.860 --> 00:30:19.920
Yeah, these are really good.

00:30:19.920 --> 00:30:23.720
If you enjoy this kind of stuff, there's hundreds of fun pictures to go through.

00:30:23.720 --> 00:30:28.600
And I think it's also amusing that we often pick visual jokes for an audio format.

00:30:28.600 --> 00:30:29.660
So, sure.

00:30:29.660 --> 00:30:30.260
Why not?

00:30:30.260 --> 00:30:31.280
Do it hard.

00:30:31.280 --> 00:30:31.840
That's what burgers do.

00:30:31.840 --> 00:30:32.880
Let's do it with, that's right.

00:30:32.880 --> 00:30:34.020
Let's do it with abstract art.

00:30:34.020 --> 00:30:34.300
Yeah.

00:30:34.300 --> 00:30:37.320
Yeah, it's funny.

00:30:37.320 --> 00:30:38.240
Anyway.

00:30:38.240 --> 00:30:38.540
Awesome.

00:30:38.540 --> 00:30:39.220
All right.

00:30:39.220 --> 00:30:39.720
Well, thanks, Brian.

00:30:39.720 --> 00:30:40.180
Thank you.

00:30:40.180 --> 00:30:40.420
Yep.

00:30:40.420 --> 00:30:40.600
Bye.

00:30:40.600 --> 00:30:40.860
Bye.

00:30:40.860 --> 00:30:42.560
Thank you for listening to Python Bytes.

00:30:42.560 --> 00:30:45.020
Follow the show on Twitter at Python Bytes.

00:30:45.160 --> 00:30:48.060
That's Python Bytes, as in B-Y-T-E-S.

00:30:48.060 --> 00:30:50.940
And get the full show notes at Python Bytes.fm.

00:30:50.940 --> 00:30:56.000
If you have a news item you want featured, just visit Python Bytes.fm and send it our way,

00:30:56.000 --> 00:30:58.060
where I was on the lookout for sharing something cool.

00:30:58.060 --> 00:31:02.240
This is Brian Okken, and on behalf of myself and Michael Kennedy, thank you for listening

00:31:02.240 --> 00:31:04.540
and sharing this podcast with your friends and colleagues.

