Transcript #20: Finding similar but not identical images in 128 bits via Python
Return to episode page view on github00:00 Hello and welcome to Python Bytes. This is episode 20 where we are delivering Python news and headlines directly to your earbuds.
00:08 I'm Michael Kennedy.
00:10 And I'm Brian Ockin.
00:11 And we've got a bunch of stuff lined up for you today. I'm really excited to share, especially this first article, which is so clever that you chose, Brian.
00:18 Before we do, I want to say thank you. Thank you to Rollbar, who's back to sponsor a bunch more Python Bytes.
00:25 And we'll talk more about Rollbar later, but thanks, Rollbar.
00:28 That's awesome.
00:28 Yep. So we were just talking about pictures. Like I have many gigabytes of pictures.
00:33 And if you ran a website that accepted uploads in large numbers of pictures, how do you deal with all that data?
00:41 Especially there's probably a lot of duplicate data, right?
00:43 I'm not sure. And so this is an interesting article. There's an article from jetsetter.com.
00:49 And they're an invitation-only travel community.
00:53 But the article is duplicate image detection with perceptual hashing in Python.
00:58 And that actually sounds more...
01:01 Perceptual hashing. That's awesome.
01:03 Perceptual hashing. It's awesome.
01:06 And the idea is they've got... I mean, the site's got a bunch of pictures of different places around the world.
01:12 And they don't want pictures that are mostly close to each other.
01:17 I mean, for family photos, you got a ton that are close to each other.
01:20 But I get for like...
01:21 There's a lot of cases where you don't want things that are almost the same.
01:24 Right. Like pictures of hotels or pictures of a marina to say, here's the view out of the hotel.
01:31 Like if they're going to have a listing on like some location of some hotel and they ask people to upload them, they don't need like 100 ones from this one view.
01:38 And if you check out jetsetter.com, it is an intensely photo-heavy site.
01:43 Like I'm pretty impressed with the number of photos on that page.
01:46 With the idea of perceptual hashing, I was definitely interested in reading about this.
01:50 And I expected it to be a fairly complicated algorithm.
01:54 But it's actually ingenious.
01:56 And it's a...
01:57 They use Python and get...
02:00 Transfer the image down to just a 9x9 square.
02:03 I don't get...
02:04 Of gray values even.
02:05 I don't get how that's enough information.
02:07 But it is apparently enough to determine whether or not an image is close to another image.
02:15 And they do a delta.
02:16 I'm not going to be able to...
02:18 Can you explain that much better?
02:19 I can try.
02:20 I mean, when I read, we take a 5 megapixel image and we generate a 128-bit hash.
02:28 And that means a thing.
02:30 Like that means uniqueness.
02:31 Or actually it means similarity, which is actually more important.
02:34 I was like, okay, I have to figure this out.
02:36 And I guess what they do is they take a large image and they like average it down to a 9x9.
02:42 They say for larger images, like a 17x17 image.
02:46 And to determine the similarity, maybe somebody's off by 5 feet to one side or the other to take a picture of a hotel or a view or something.
02:54 But if you kind of average it down to that 9x9, that's where the similarities kind of collapse into those grids.
03:01 And then they run an algorithm on that grayscale grid, right?
03:04 Yeah.
03:05 And then the interesting thing is that, of course, it's clear to me that you could come up with a hash algorithm for an image.
03:12 But the difference in the hashes is enough to tell you how close the image is.
03:19 Yeah.
03:19 And it's actually the opposite that really blows me away is like two similar images that are not the same generate the same hash.
03:27 That's what's the magic.
03:28 Like that totally blows my mind.
03:29 I could see like, well, obviously hash is different, images are different, but images are similar, not the same, hash the same.
03:35 That blows me away.
03:36 Yeah.
03:37 And I like it that it's not that complicated of an algorithm and it's a fun read.
03:42 Yeah.
03:42 That's, you know, so I think there's a couple levels of interesting that you brought up this article.
03:47 And one of them I think is really interesting is when I first heard that, I thought, okay, one, this is going to be super hard, super computational.
03:53 Two, maybe this is like machine learning or something like that.
03:58 Like two machines, like two images given to an AI, like a deep learning neural network or something.
04:02 You say, yeah, these are sufficiently similar in ways that I don't really, people don't really understand.
04:07 But magic on GPUs and lots of, you know, neurons, it works out somehow.
04:11 But the fact that it's really, really a simple algorithm is what's, what's I think kind of special about it.
04:18 Right.
04:18 It's like, hey, there's still lots of places to be clever and not just throw AI plus GPUs at a thing.
04:24 Yes, definitely.
04:25 Yeah.
04:26 And not only that, you get to take it with you, right?
04:29 It's available on GitHub.
04:30 Yeah, they do have it.
04:31 It's a, what is it?
04:32 P-Y-B-K-Tree?
04:34 Py-B-K-Tree, whatever that means.
04:37 Okay, awesome.
04:37 I'm sure it's part of the algorithm.
04:39 Excellent.
04:40 So keeping with open source projects that you can go find and just grab and do cool things with,
04:45 one of the listeners pointed me towards, pointed us towards Google open source.
04:50 In fact, it was the guy from Google Fire, Python Fire, which we'll talk more about later.
04:56 But he has one of the projects there.
04:58 And on Google open source, they've basically created like a listing directory of all of the open source projects.
05:05 Now, many of the projects still live on GitHub.
05:07 But this is like a place where you can go search and analyze and discover projects from Google.
05:13 And what's cool is you can sort by language.
05:15 So show me the Python projects.
05:17 Show me the C++ projects, whatever.
05:18 So I grabbed six or seven interesting projects.
05:21 I just wanted to run them down for you, Brian.
05:23 Okay.
05:24 Yeah.
05:24 So one of them is subprocess 32, a reliable subprocess module for Python 2.
05:29 Apparently, subprocess built in is not reliable for Python 2.
05:34 I don't know.
05:35 But I didn't know that either.
05:37 That's partly why it's interesting to me.
05:39 But also, you know, there it is.
05:40 That's cool.
05:41 Grumpy.
05:42 We've talked about grumpy before.
05:43 Grumpy is Python on Go instead of Python on CPython.
05:46 Yeah.
05:47 Yeah.
05:47 That's a good one.
05:47 That's a...
05:48 Python Fire, of course.
05:49 Python Fire, of course.
05:50 Like I pointed out, that's a way to take any Python object or module and turn it into a command
05:55 line interface.
05:56 There's a Python client for Google Maps services.
05:59 So if you want to consume Google Maps from Python, do it.
06:03 There's Hue, H-Y-O-U, a Python interface for manipulating Google spreadsheets.
06:10 That's cool, right?
06:11 Okay.
06:11 I'm going to have to try that out.
06:13 That's neat.
06:13 Yeah.
06:13 I mean, I've seen the stuff for working with Doc.
06:16 XLS, X files, the Microsoft Office ones, but I didn't know about the Google spreadsheet.
06:21 So this is cool.
06:21 Another thing that's always tricky for me is working with OAuth, right?
06:25 There's always this, like, I've got some app.
06:27 The app needs to go, like, open a browser window, and there's some sort of funky callback, and
06:31 things happen.
06:32 And so one of the places that's especially challenging, I think, is over a command line interface.
06:38 Well, there's OAuth 2L.
06:41 I think it's L.
06:43 And what that is, is it's a way, a command line tool to get an OAuth token.
06:47 Just let that sink in for you.
06:49 Okay.
06:50 So I want to log in as Google.
06:51 I can do that, like, through my app.
06:53 Like, I could basically create a shell script that, through the CLI, gets an OAuth token from
06:59 the user.
06:59 That's pretty interesting.
07:01 Okay.
07:01 And also, I talked about the Google Maps API.
07:04 Like, that sounds like that's something that's really hard to, like, unit test or test at
07:08 all without actually going to Google.
07:10 So there's a mock maps API.
07:12 So a small little app engine app for testing, like, basically mocking out Google Maps API.
07:18 And last but not least, TensorFlow.
07:21 The amazing deep learning, machine learning stuff.
07:24 That's about 50% Python, 50% C++, and a lot of GPUs in action there.
07:31 And I don't know where I read this, but I think that this Google open source location is not
07:37 just all projects.
07:39 It's projects that they consider still active.
07:41 Okay.
07:42 Yeah, that's cool.
07:43 I mean, obviously, you don't want just, like, a dumping ground, right?
07:45 Yeah, cool.
07:47 I mean, everything in there looked pretty neat and fresh, so it's good.
07:49 It's a fairly neat interface, too, with, I guess, panels and stuff.
07:54 Yeah, it's worth checking out.
07:55 Okay.
07:55 What do we got next?
07:56 Oh, next is me.
07:57 Yeah, more machine learning type stuff.
08:00 Yeah.
08:00 So there's an article from Jason Brownlee called, and I just clicked away, How to Handle Missing
08:07 Data with Python.
08:08 And this is something that I definitely deal with measurement values that deal with at work,
08:13 but there's, the gist of it is, is a lot of times you're dealing with a lot, large or small
08:18 data sets, and some of the values are missing.
08:21 And there's a whole bunch of different ways you can deal with missing data, but there are
08:26 a few of them that he talks about are replacing, you know, you have to know what the magic number
08:31 is that some data collection will fill in a zero, maybe, if there's no data, or some other
08:36 known number.
08:38 But all your math is going to get messed up if you actually just leave that there.
08:43 So there's a couple ways to get around it.
08:46 One of the ways he lists is using magic, not a number values.
08:50 And I think pandas can deal with that correctly and not average those in.
08:55 Yeah, what I think is really nice about it is like, I could be given a CSV file or some
09:00 sort of data thing, set of data, and I could like work my way through it and maybe find
09:06 the bad data and fill it in potentially.
09:08 But his fix are like, you run this one line in pandas and magic happens and it's better,
09:14 right?
09:14 It's like the fix is so much better than the fixes that I would come up with.
09:17 Yeah.
09:18 And I do like that he's talking about different ways to deal with it with numpy, even without
09:23 pandas also, because you might not be using pandas.
09:26 But the, like one of the ways you would do it with any math package really would be to,
09:31 oh, I guess I don't know how to do that.
09:33 Actually.
09:34 Nevermind.
09:35 Filling in the, you'd somehow have to find all of the values anyway and fill them in with,
09:41 like one of the ways is if you're, if you're calculating an average, calculate the average
09:45 of everything else and then fill in the blanks with the average.
09:49 Right.
09:50 I guess it depends on what you're going to do.
09:51 Are you going to average it?
09:52 Are you going to max it in a minute?
09:53 You could like push that through, right?
09:55 Yeah.
09:55 Yeah.
09:55 Interesting.
09:56 The best solution definitely, I think is, is using the not a number and letting the,
10:01 the libraries take care of it for you.
10:03 But it's definitely, I wanted to bring this up partly because anybody that's working with
10:08 data collection or, and doing math with that has to deal with the fact that sometimes there's
10:14 not numbers there and you have to deal with it.
10:16 So.
10:16 Okay.
10:16 Awesome.
10:17 He's from machine learning mastery.com.
10:20 I think.
10:20 And he's got just a ton of cool stuff going on over there.
10:25 Right.
10:25 It's not just this one article.
10:27 Right.
10:27 So if you're into these kinds of things, definitely check it out.
10:29 Yeah.
10:30 It looks good.
10:30 Okay.
10:32 So what's up next is the hug rest framework.
10:37 But before we get to them, I want to give roll bar a hug.
10:39 Roll bar is awesome.
10:41 I've been, as people know, I've been using them for a long time on the websites and the
10:45 websites are getting more and more traffic.
10:46 And I recently, I'm not sure whether it was a wise decision or not, because I'm really busy
10:52 with other stuff, but I just got really frustrated with the way my servers are working, the way
10:57 I could sort of move them around and performance and stuff.
11:00 So I said, that's it.
11:01 One day I just woke up, so that's it.
11:03 Converting it all to MongoDB.
11:05 And so that was last week.
11:07 And that took like three days of rewriting all my sites to Mongo, which I really think
11:12 Mongo is the right choice.
11:13 And I'm just loving the way it's working now.
11:16 But that was a pretty serious, like take the guts out of all my web apps and stick in a new set
11:22 of guts that are similar, but not entirely compatible.
11:25 I spent a little time with roll bar and they, they, they helped me out and find a few
11:30 problems like where maybe types used to be strings.
11:33 I compare them where one was no longer a string and they didn't compare the same.
11:37 So I got weird errors, but roll bar made it super easy to track that down.
11:40 So if you want to have reliability and most importantly, awareness of the state of your
11:47 apps, plug in roll bar to your web apps.
11:49 You can use it in pyramid, flash, Django, whatever, just plug it in and you'll get notifications
11:55 right away.
11:56 So be sure to visit rollbar.com slash Python bytes, and you'll get a special offer to get
12:01 started there.
12:02 And I bet that you definitely noticed those messages, but I didn't even notice you were
12:07 mucking with things.
12:08 And I'm pretty sure that nobody else did or very few people did either.
12:11 Yeah, that's true.
12:12 And thank you for saying that.
12:13 But I actually know how many people ran into problems, right?
12:17 There was a couple, but I got an email from a couple of people saying, Hey, I had this problem
12:22 with your app.
12:23 I'm like, I know, but I didn't know your email address.
12:26 But I know what your problem was, and it's already fixed.
12:28 I just couldn't contact them.
12:30 So because they hadn't actually created an account yet.
12:32 So it was really nice to be able to just say, yeah, actually, the problem you're telling me
12:36 is already fixed.
12:36 I just couldn't communicate that back to you.
12:38 Really sorry about that.
12:39 It's awesome.
12:40 You seem like a big team then because of that.
12:42 So oh, yeah, definitely.
12:44 It's all the folks here in the cubicle farm.
12:46 We're busy.
12:47 You know, one of the next things that I want to do is build some some nice APIs.
12:54 And I think it's really an interesting time for the web in Python.
12:58 There's a lot of flowers blooming, if you will.
13:00 Right?
13:01 We've got Pyramid, Django, Flask.
13:03 Those guys are all doing super stuff.
13:06 And like most of my stuff is Pyramid.
13:07 But we've got Jopronto coming along, Sanic.
13:10 And another one that I just learned about is called Hug at Hug.rest.
13:15 How's that for a name and a domain?
13:17 Yeah, actually, it is.
13:19 It's www.hug.rest.
13:20 Hug.rest.
13:21 That's beautiful.
13:22 So Hug is a Python framework, web framework, just specifically for building restful, documented,
13:30 documentable, versionable APIs.
13:33 And it's built both for like super simplicity and flexibility as well as performance.
13:39 So I started looking this up.
13:41 Wow, this is quite interesting.
13:42 Okay.
13:43 So the idea is you can create an API once and you can consume it in all these different ways.
13:49 So you can import it as a module or a package into your project and use the API that way.
13:54 You can communicate it, obviously, over HTTP as like a RESTful API.
13:59 Or it also has a CLI, command line interface, way to expose that.
14:03 So if you write like some kind of a web app or functionality you want to expose over an API,
14:07 but you also want to call it locally, it's like the same code.
14:11 Oh, wow.
14:12 That's interesting.
14:12 It's also written in Python 3.
14:14 It uses Cython all over the place.
14:17 So it's like super fast.
14:20 It's one of the fastest web frameworks out there for these kinds of things.
14:23 At least the non-async version, let's say.
14:27 If you compare those, it's pretty cool.
14:28 It's got a decorator model, so the code looks really clean.
14:31 Yeah, and the decorator model is cool because the decorator model will do like version management.
14:35 You can have like version 1 and version 2 of the API that have like different data formats.
14:40 And they can just coexist.
14:41 You get automatic documentation based on that.
14:45 Like it'll do type annotations and then like use the type annotations as part of the documentation and things like that.
14:50 Oh, that's great.
14:51 It's a pretty cool, simple little framework.
14:53 So, you know, hug for those guys.
14:55 Nice job.
14:55 Definitely.
14:56 Speaking of CLIs.
14:57 Yeah, speaking of CLIs, I'm actually working on, I had an example I wanted to do that I'm running with the pytest book that I'm working on.
15:06 And for the front end of it, I was punting before and not using actually putting a front end on the application.
15:13 But I wanted to at least put a command line interface in.
15:16 And my first attempt was to go down arg parse.
15:19 And the particular quirks of this application, I needed sub commands.
15:24 Actually, just the tutorials I found were out of date.
15:28 It doesn't work.
15:29 And I was having a little bit of difficulty.
15:32 So I went ahead and tried CLIQ.
15:34 I'd heard of CLIQ before and hadn't tried it.
15:37 And, man, a tutorial from like three years ago was about what I needed.
15:42 And it works right away.
15:45 I've got like half a page of code and my interface, my command line interface is done.
15:50 That's really cool.
15:50 That's also decorator heavy, right?
15:52 Yeah, in my sublime editor, it's colored nicely.
15:55 And my wife walked by and said, that's such beautiful code.
15:58 Oh, lovely.
16:00 Let's take that on many, many levels, right?
16:02 That's awesome.
16:03 Yeah, that's by Armin Roeneker, a guy from Flask.
16:05 So definitely.
16:06 Oh, did he do CLIQ?
16:08 I think so, yeah.
16:08 I believe so.
16:09 Yeah, nice.
16:11 CLIQ is cool.
16:11 I've done a little bit of work with it.
16:13 And I've liked what I've seen.
16:14 But I also kind of want to, yeah, we'll talk about it later.
16:17 But I might want to try adding a different CLI interface to it as well.
16:21 Yeah, cool.
16:22 So the last one that I chose for us is kind of a refresher, back to the fundamentals type
16:27 thing.
16:27 So Python inheritance class and instance class and static methods demystified.
16:34 So this one is on realpython.com.
16:37 And I went over there and checked it out.
16:38 And I said, oh, OK, realpython.com.
16:40 That's cool.
16:41 And then I realized this is actually from Dan Bader.
16:42 And we seem to be covering a lot of Dan's stuff over here.
16:45 And I actually have more to say about Dan later still.
16:47 So this was a guest pose Dan did for that, although I didn't realize that until I started
16:52 getting into it.
16:52 And the idea was to demystify what's behind class methods, static methods, and regular
16:58 instance methods.
16:59 If you learn Python classes, if you learn classes and inheritance and object-oriented programming
17:05 only through Python, this will be obvious to you.
17:09 But if you come from other languages like C++ or Java or C# or JavaScript, there's differences
17:16 to the way Python classes and inheritance works.
17:19 And it's worth kind of a compare and contrast.
17:22 So he comes up with a class.
17:24 And it's got like a regular method, a class method.
17:26 So an at class method decorator.
17:28 And takes a CLS parameter.
17:30 And a static method with an at static method decorator.
17:33 Nothing.
17:34 And basically compares and contrasts how they work.
17:36 And so some of the things that I think are not obvious when you're first getting started
17:41 is like instance classes.
17:42 Those are pretty straightforward.
17:43 Like you call them on instances like all other languages.
17:47 But the fact that I can call static methods or class methods on instances, that's a little
17:54 bit funky.
17:55 Right?
17:56 Yeah.
17:56 That seems a little weird.
17:57 And then the other one, the main one I think is like what's the difference?
18:00 Why are there two things like static method and class method?
18:02 They seem the same.
18:03 Why are there two?
18:04 And then like when would I use one versus the other?
18:07 Right?
18:08 The class method takes a CLS method, which is literally the type that it's on.
18:12 And the static method just doesn't.
18:14 But other than that, they seem the same.
18:15 Right?
18:16 And so if you're going to say like interact with the class, like during the class method,
18:21 if you're going to create an instance of the class, you can use the CLS parameter to support
18:27 like inheritance and stuff.
18:28 So if I got like a, let's say a vehicle class and a car, like a Tesla car class, that static
18:36 method could say like allocate a CLS, whatever that is.
18:39 And if you called it on a Tesla static ish function class method, it would actually create
18:44 a Tesla.
18:44 It would change like the thing, the type that it knows it is, where the static method is
18:48 just like a grouping.
18:49 So I thought that was interesting.
18:51 Does the class method follow then the hierarchy then?
18:54 So if I declare a class method on a base class, does it, is it available to the subclass?
19:01 Yes, always.
19:02 And that's always true for static methods.
19:03 But the difference is the static method doesn't really know what type it's being called on.
19:08 Oh, okay.
19:09 Whereas the class method, it's given the type.
19:12 So if there's like, you call it farther down in the inheritance chain, that whatever level
19:16 you're at, that instant or that type actually is communicated to it.
19:20 And so you're kind of, you're told where you are in the hierarchy in a class method, where
19:24 in static, it's just like, it's just a method.
19:26 Go for it.
19:27 Okay.
19:27 Yeah.
19:27 I don't think I've ever used static methods for anything.
19:29 Yeah.
19:29 Well, they're out there hanging out with their friend class methods.
19:32 Interesting.
19:34 Indeed.
19:35 So I have a quick follow-up from the last show.
19:37 David Bieber from Google, the guy who works on Python Fire, sent us a note.
19:41 And you said something to the effect of, look, Python Fire is awesome, but IPython is a serious
19:48 dependency to take if I just want to see a lie, right?
19:51 And I think that's fair.
19:52 That's fair.
19:52 But he said, hey, you know what?
19:55 One of our primary plans is to remove IPython as a dependency.
19:59 We're just not there yet.
20:00 So if anybody in the audience wants to help those guys move forward, they're totally working
20:05 on that.
20:05 And so Python Fire from Google is definitely getting some interesting thinning out, and
20:13 it'll be very nice.
20:13 And actually, I like to hear that, that they're working on eventually getting rid of that dependency.
20:19 And it's pretty cool.
20:21 Also, it's something I had mentioned when we talked about Python Fire, that your development
20:27 time is important, too.
20:28 And putting an interface together with that is pretty fast.
20:32 So keep that in mind.
20:33 Yeah.
20:34 It's not always about optimizing for the machines.
20:36 Definitely.
20:36 Hey, one more follow-up is we did cover PDR2 or PDR a couple episodes ago with the DUR colors
20:47 prints out.
20:48 One of the complaints I had was that it didn't look that great on my black terminal.
20:54 I had the same problem.
20:55 I like darker stuff.
20:56 And I'm like, wait, where's all the words?
20:58 They just updated it.
21:00 And I guess yesterday, I think.
21:02 And it does have color configuration now.
21:05 So you can drop a PDR2 config file in your home directory.
21:11 And I set my background color to magenta so that it was visible for docs, visible on both
21:18 black and white.
21:18 And now it looks great.
21:19 Oh, nice.
21:19 PDR2 now has themes.
21:21 Love it.
21:22 All right.
21:24 How's the book coming?
21:26 I heard there's a spotting.
21:27 Yeah.
21:28 So on Twitter the other day, somebody, a guy named Jacob Jarros, I think that's right,
21:34 noticed that it was listed on the Pragmatic Publishers website.
21:38 So it's out there.
21:40 That's awesome.
21:40 I love the cover.
21:41 The rocket is cool.
21:43 Yeah.
21:43 A 50s sci-fi nerd.
21:44 So yeah.
21:45 And just the perfect, it's perfect.
21:47 Like it's 50s, 60s vintage rocket.
21:51 So how about you?
21:51 Well, it has been a super busy couple of weeks.
21:55 I've been working on a couple of classes.
21:57 One of them I'm about to release.
22:00 By the time this recording comes out, it will be out.
22:02 So tomorrow, basically.
22:04 A course called Using and Mastering Cookie Cutter.
22:07 So really deep dive into what is cookie cutter?
22:10 How do you create and manage projects with cookie cutter?
22:13 I think it's going to be a really fun course.
22:15 And I also just a few hours ago launched Managing Python Dependencies with pip in Virtual Environments,
22:22 which Dan Bader, speaking of Dan Bader, came over to join me to write a class for us over here.
22:28 And we're shipping that as well.
22:30 So I took that course and I actually learned quite a bit from it.
22:34 It's not just like pip install done.
22:36 It's what is the process that you use to manage your dependencies?
22:41 How do you like, what is the thinking and workflow you use to evaluate whether a package is worth taking a dependency on?
22:48 And all sorts of cool stuff like that.
22:49 Bunch of best practices.
22:50 Launched both of those.
22:51 And I just started selling course bundles on Talk Python training as well to sort of go along with those.
22:57 So lots of stuff.
22:58 That's pretty exciting.
22:59 I got to check out the cookie cutter thing.
23:01 Yeah, thanks, Ed.
23:02 It'll be out tomorrow morning.
23:03 For everyone listening, that's today.
23:04 That's today.
23:05 But for you, Brian, that's tomorrow morning.
23:08 The magic of time travel.
23:10 Thanks so much for finding all these great items.
23:13 That was fun as always, Brian.
23:15 It was fun for me too.
23:16 And thanks to everybody for all your feedback that you send in.
23:19 Yep.
23:19 Thanks, everyone.
23:20 And thank you, Rollbar, for supporting the show.
23:23 Thank you for listening to Python Bytes.
23:25 Follow the show on Twitter via at Python Bytes.
23:27 That's Python Bytes as in B-Y-T-E-S.
23:30 And get the full show notes at pythonbytes.fm.
23:34 If you have a news item you want featured, just visit pythonbytes.fm and send it our way.
23:38 We're always on the lookout for sharing something cool.
23:41 On behalf of myself and Brian Okken, this is Michael Kennedy.
23:44 Thank you for listening and sharing this podcast with your friends and colleagues.