Brought to you by Michael and Brian - take a Talk Python course or get Brian's pytest book


Transcript #89: A tenacious episode that won't give up

Return to episode page view on github
Recorded on Thursday, Aug 2, 2018.

00:00 Hello and welcome to Python bites where we deliver Python news and headlines directly to earbuds. This is episode 89 Recorded August 2nd 2018. I'm Michael Kennedy and I'm Brian. Okken. Hey, Brian. How you doing? I'm doing great It's good to talk to you again. Yeah you as well and man We got some cool stuff lined up this week. We always say it but there's really so much happening in the Python space It's more exciting every week Yeah I think it's because we're here and then people are excited about Python and then they do more and then it builds on itself It's this awesome feedback loop. I can totally feel it.

00:32 Yeah.

00:33 What also is awesome is Datadog for sponsoring the show.

00:36 So if you need infrastructure monitoring or monitoring of your apps, check them out at pythonbytes.fm/datadog.

00:42 Tell you more about them later.

00:43 Brian, you found some code that's pretty tenacious out there, didn't you?

00:46 It just won't quit.

00:47 Tenacious is a fun word.

00:49 And there's a project called Tenacity that is pretty cool.

00:55 We'll, of course, have a link in the show notes.

00:57 But Tenacity is a general purpose retrying library to simplify the task of adding retry behavior to just about anything.

01:07 And there's a little code snippet, but the gist of it is you import retry, and it can have a lot of options, but the defaults just sort of work also.

01:18 You can just put this around a function, and if your function raises an exception, it just tries it again.

01:27 and eats the exception.

01:29 So for a lot of stuff, this is terrible.

01:32 It's a terrible idea in a lot of places.

01:34 But a few places, this might be reasonably good, especially like if, for instance, if you're gonna, like, I guess I'm not even gonna come with, for instance, you guys know, in places where retrying is a good idea, like maybe saving something to a file system or connecting to a service that sometimes has contention.

01:54 - Yeah, or even a database, Like what if you have to like restart the database server or something to that effect, right?

02:00 Or you need to really quickly apply a migration, right?

02:05 You could say, just keep retrying the database until you get there.

02:09 - Yeah, and then there's a whole bunch of extra conditions you can put on it.

02:12 You can say, try so many times or a wait time for, if it doesn't work after a while, then give up.

02:21 Then you can customize which exceptions it catches or doesn't catch.

02:25 It even has a retry on coroutines, which is kind of fun.

02:29 I haven't tried that, but I've tried the simple case.

02:32 But I'm going to use it right away.

02:34 We've got several conditions where we're in testing devices where sometimes it takes a device, we're doing like a little wifi devices, for example, it might be asleep.

02:44 So it might take a while for the thing to wake up and respond.

02:48 So a number of retries is good.

02:50 And we have like retry code all over our code base.

02:54 And so having decorator do that for us is just pretty slick.

02:57 I like it.

02:58 - Yeah, it's really cool.

02:59 And you know, like I said, the database thing, like you wouldn't just put retry and say continue to hammer the database indefinitely if you can't, if you run into any problems.

03:07 But you know, like the, like try five times with an exponential fall off, just so you could basically handle five second down times, things like that.

03:15 Be nice.

03:16 - Yeah, there's definitely be parts of, like you said, a distributed system where you got things that sometimes are not available for a little while.

03:25 Yeah, especially stuff that you don't control everything like other services you depend upon.

03:31 Yeah.

03:32 Yeah, pretty cool.

03:33 So I'm going to bring a theme into this show here for the next couple of topics that I'm going to bring in.

03:39 And I think it's actually going to touch on every one of the things I'm bringing.

03:43 So let's start with Anthony Shaw.

03:45 Of course, we've got to cover an article by Anthony Shaw, right?

03:48 Yeah, he writes a lot of good stuff.

03:50 Yeah, he does.

03:51 And one of the ones that I came across here is called, "Why is Python so slow?" So I think it's interesting to even ask the question, like, "Is Python slow?" and explain how that is.

04:02 So Anthony looks at some benchmarks comparing, say, Python to Java, to C#, to C++, and things like that, and talks about, well, in some cases it is slower, and why might that be, right?

04:14 answering the question like Python completes a comparable operation like two to ten times slower than say Java or C#. Why? And why can't we make it faster? So he has three main hypotheses which are pretty interesting. One is it's the GIL, the global interpreter lock. The other is it's because it's interpreted rather than compiled. And the final one is it's a dynamic language. So So those three ones are pretty interesting.

04:47 What do you think?

04:49 Okay, so he's not saying that these are the reasons, but these are theories.

04:53 These are theories, and then he goes through each one of them and pretty deeply looks at how they work and then compares them.

05:00 So for example, it's interpreted, not compiled.

05:03 Well what does that mean in terms of trade-off?

05:04 Let's compare that to say the way C# does things, the way C++ works.

05:09 What are the trade-offs there?

05:11 So let's go through some of them.

05:14 One is, it's the gill.

05:18 Now modern computers, modern processors have multiple cores.

05:23 If you actually look at Moore's law, Moore's law is still alive and well, the number of transistors in a chip.

05:30 But people sort of correlated that indirectly to, well, that means computers are getting faster and faster and faster.

05:38 But it turns out that a number of years ago, four, five, I don't know how many years ago, not too long ago, the actual clock speed no longer kept doubling along with Moore's law.

05:49 It sort of went flat more or less.

05:51 And what started happening was we got two core machines and then four core machines.

05:55 And I just got a new MacBook.

05:56 It has six hyperthreaded cores.

05:59 It's crazy, right?

06:00 But on Python, if I want to take advantage of that computationally, it's super hard within one process because of the gill.

06:07 So said, well, if you want to take advantage of modern hardware, maybe the GIL is the problem.

06:13 So he talks about some of the trade offs there, when it matters when it doesn't matter.

06:17 So for example, if what you're doing is IO bound, it basically doesn't much matter, right?

06:22 The GIL is released when you're waiting on like network calls and stuff like that.

06:25 So in some sense, like the GIL is not not the problem.

06:29 If you created a bunch of threads, and they all started, you know, reading, writing files, talking over the network, it should just automatically handle that.

06:37 But if on the other hand you try to create six threads and do computational stuff, you'll still probably get 12% CPU usage on my machine because it only really gets to run one at a time.

06:48 So that's one of the theories.

06:50 And I think this theory applies more in some places and less in others.

06:54 And I kind of touched on that a little bit.

06:57 If your goal is to do computational mathematical things, the GIL can really, really matter.

07:02 It makes a big difference, right?

07:05 you're trying to execute your Python code, it doesn't let go of the gill.

07:08 But if say you're building a web app, it probably doesn't matter.

07:12 There are some ways and you can do some things that would be better, but it doesn't really matter.

07:17 So for example, if I looked at the various servers before we came on today, the training.talkpython.fm site has 16 worker processes all running parallel versions of the website handling requests.

07:31 Talk Python itself has eight.

07:33 I think maybe Python bytes has eight as well.

07:35 So anyway, there's these eight processes and sure one of them may like lock up something with the gill, but there's a whole bunch of others that can leverage those other CPU cores and just keep on rocking.

07:45 So if you're doing web stuff, it matters less in this sense, I think.

07:49 And I mean, even if you are, there's ways to get around it.

07:53 Yeah, if you can break up your algorithm and do sub process type parallelism.

07:56 Okay, so that's the gill.

07:59 The other is, could be it's an interpreted language.

08:02 I think this one is the most interesting probably.

08:07 It is an interpreted language, but actually it does compile code to bytecode, but it doesn't JIT compile it.

08:14 One of the main considerations around JIT-compiled languages versus not is startup time.

08:21 If our Python code is going to start and run for a while, then doing a whole bunch of JIT optimization would be maybe a little slower to start but then faster to run.

08:30 But if we want to just do some CLI stuff that starts really quick, does a tiny thing and goes away, a whole bunch of JIT stuff might be, you know, sort of counterproductive.

08:40 So there's a pretty interesting comparison against C# and Java and CPython here.

08:45 The other thing I think that's worth throwing in here is because of C extensions and things like that, it's an interpreted language.

08:52 I think that's a simplistic view.

08:54 That's like, well, if you just take straight Python code and just run it on Python, you don't interact with any libraries.

09:00 But if you work with NumPy, or if you work with SQLAlchemy, or a whole bunch of stuff that has C extensions to make certain parts fast, well, all of a sudden it's not interpreted.

09:11 So there's these weird blends.

09:12 All right, last one.

09:14 It's because it's dynamically typed.

09:16 So this is also, I think this is really interesting.

09:19 I think actually this is probably why, and I'm going to throw in another one, unless I get distracted and forget it.

09:25 I think this is really why it's probably the slowest, is that it's a dynamic language.

09:30 It's not that you can't make a dynamic language fast, but because it's so flexible, it's hard to know how to optimize it.

09:41 You might want to inline a function, but somebody could monkey patch that function, and then you wouldn't be inlining the right thing, for example.

09:47 And then you monkey patch it only sometimes.

09:49 What do you do then?

09:50 Right?

09:51 So things like method inlining, which can really make things faster, is super hard because that could actually change.

09:57 Where say in C# or C++ or whatever, the method won't change.

10:01 Yeah.

10:02 Okay.

10:03 All right.

10:04 Final one.

10:05 This is mine.

10:06 I'm adding this to his.

10:07 And it sort of has to do with this dynamic typing thing is everything is a reference type allocated on a heap in Python.

10:12 Right?

10:13 Some of the stuff that makes C++ and C# really, really fast is things like numbers and other stuff are allocated on the stack.

10:21 And when you work with them, you never do pointer dereferencing, you never do reference counting, never do garbage collection or memory management, you just work with little bits on the stack.

10:30 And because that little thing on the stack could become a full blown list or something just by changing what it points at, I think probably that also makes a big difference.

10:39 - Okay, there's also like function calls are slower than they need to be.

10:45 And that's, I think that's one of the things the Python core team is working on, is to try to--

10:50 - And luckily there've been some advances there in the latest version of Python.

10:54 I think 20% or something they got, was that three seven, I think that they got that much faster.

10:58 So work is being done, but yeah, it could be more, I guess.

11:03 But what's really interesting is the trade-offs or sort of comparisons of the article.

11:07 - The thing that I want, there's a forest in the trees sort of issue I have here is that I don't think Python is slow.

11:16 - Yeah, I'm with you.

11:17 - And my people time is way more expensive than computer time for like 98% of the applications in the world as far as I probably.

11:27 - Well, and somebody made that comment below in the comment section of this article.

11:30 It said, "Well, if you're optimizing milliseconds "versus nanoseconds, yes, maybe.

11:35 if you're optimizing weeks versus month from idea to shipped, you know, you Python's not slow at all. It's really fast.

11:42 Yeah. And that I mean, that's what I see is the maintenance, development time, the maintenance time, all of those extra people time things. Python is way faster. And a lot of these things that we say is like the GIL is a problem or multi processing is difficult.

11:59 Well, multi-processing isn't easy.

12:01 Maybe it's easy in some languages.

12:03 I mean, Go is sort of designed to do that from the start. But getting a complex algorithm in C++ to utilize multi-cores, that's not a trivial task either.

12:13 No, it's not. It's definitely not.

12:15 And I would say on the web framework side, actually Python is really quite fast. I've compared it against other, like C#-based JIT compiled things like ASP.NET and stuff, or conference talk I did comparing Python to the .NET stuff. And actually Python was not just as fast but faster than the JIT compiled C# stuff. Yeah and also just for people that do think that maybe the Python speeds the problem really measure it and you can optimize there's ways in Python to optimize the parts that are slow. So yeah my next item will come back to this and show you some ways to make it faster. Okay. All right. So what's this Mew thing all about? Mew, you talked about Mew before, right? It's like a simplified IDE type thing. Is that right?

13:01 Right. So I'm going to highlight Mew again, partly because I think it's a neat project that's going on and there's a lot of cool people working on it. But there was an article called Keynoting in Mew, and Mew being MU. And in the EuroPython 2018, David Beasley did a talk in a demo called Die Threads. And what amused me is my first thought was, is this a German joke?" And he actually addressed it early on. It's not a German joke, but it's a good demo. But he used Mew during his Python talk, and this article talks about, and also asked him about it, why he did that. And it's just a simple thing. It's the same experience for everybody. Like for instance, I use PyCharm, but my environment in PyCharm, all the different colors I like or the plugins I use.

13:52 It's going to be different than everybody else.

13:54 It's one of those customizable things.

13:56 Having a very simple interface like Mew that works as a learning tool, but also shows people exactly what you're doing.

14:05 One of the features of it is that if you've got a little script that you want to run, you can just push run at the top and then automatically a little window pops up at the bottom and shows you the output of the thing you're running.

14:20 And that's just really handy.

14:22 And so it's kind of fun to watch that in a talk and being used.

14:27 And I think that would be, I use it, do little demos for people at work.

14:31 And I think I might try this also just to not have to answer questions like, what plugin are you using?

14:36 Or whatever.

14:37 - Yeah, just sort of keep the distractions to a minimum, huh?

14:40 - Yeah, and it's a real clean interface and looks like you can change the font size and it looks fun.

14:46 Plus, Mew is, if you haven't played around with it yet, it's also something that it automatically has hooks into things like running micro bit and Raspberry Pi and stuff.

15:00 - A micro Python and embedded IoT things?

15:03 - Yeah.

15:04 - Yeah, very cool.

15:05 Yeah, I like it.

15:05 That's a nice one.

15:06 So before we get on to my next performance thing, let me tell you guys about Datadog.

15:11 So Datadog is sponsoring this episode like they have many.

15:14 Thank you to them for that, of course.

15:16 So they have infrastructure monitoring, distributed tracing, and they've added logging.

15:21 They provide end to end visibility for requests across different parts of your infrastructure, and as well as health and performance monitoring of your Python apps. So get started with them today for a 14 day free trial. And of course, they'll send you a cool data dog t shirt on it, just go to Python bytes.fm slash data dog and check it out. So yeah, very cool stuff from data dog. Brian, I told you about the performance thing and whether Python is slow. I agree with you. I generally find for what I do, it's like actually blazing. So not a problem, but it sort of depends on choosing the right infrastructure. Yeah. Yeah. So there's this interesting proof of concept done by this. I forgot the names, a European open source consortium. And the basic theme of this article is sort of exploring a response to the question of, "So I've heard Python is slow. Is it?" And I think it depends. So what they've done is they've created a multi-core, talked about how that can be hard to take advantage of, but here it is, multi-core Python HTTP server that is much faster than Go. So you've heard a lot of people say, "Well, we're gonna go to Go because its parallelism is so much better than Python and it's fast." so we can, you know, do things fast. And of course, there's that big trade off with sort of the functionality and speed to market or speed to idea completion and so on. But this thing is like, hey, and we can even go as fast or faster. So they compare it against actually to go web servers. And it's faster than both of them. So what it is, is these guys have gone and said, we've talked about this idea before they said, we want to go look at all the various C based, not Python C based sort of co-routine libraries that will let us write like low level HTTP servers and it turns out that there were not that many good options and the best option that they found was still slower than go.

17:17 So they're like, well, maybe this isn't going to work.

17:20 But then it turned out they found this thing called LWAN, L-W-A-N, which is a C library and they used Cython to create a Cython based web server that wraps it up and exposes it to Python.

17:33 Cool, right?

17:34 So basically, it's not really ready to ship or anything.

17:37 It just validates the concept of creating a high-performance thing with Cython.

17:42 And I think that's a big part of the article.

17:43 So it says, "Here's some interesting things about Cython.

17:46 It's both an optimizing static compiler and a hybrid language that gives you the ability to write Python code that can call back and forth with C and C++ really easy.

17:57 It has static type declarations that make Python code faster because it can do like It doesn't have to treat everything like a reference type.

18:05 It can put integers as integers on the stack and whatnot.

18:08 The other thing is it releases the GIL by just having a keyword in Cython.

18:13 You can say, "This part actually don't need the GIL.

18:16 I'm doing this in C. Leave me alone." Isn't it interesting how that actually hits so many of the things that Anthony brought up?

18:22 The typing, optimizations, and the GIL stuff is all right there.

18:28 it generates super efficient C code that is then compiled into a Python module.

18:33 So it's like really great for wrapping up C libraries and exposing them to Python.

18:37 - I hope they continue on with work on this.

18:40 - Yeah, it looks pretty awesome.

18:41 Yeah, anyway, I think if you're interested in sort of checking out the performance, definitely have a look.

18:47 It's pretty cool.

18:48 So are you gonna cover something on testing in this episode?

18:50 - Oh yeah, it's my turn again.

18:53 - It's your turn for a theme.

18:54 - Where am I at?

18:55 I'm trying to get on here.

18:56 Oh, yes, I am very excited about some news that came out last week.

19:01 So I've been playing with--

19:03 I started PyCharm, using PyCharm a while ago.

19:07 You've been using it off and on.

19:09 I think you use lots of stuff now.

19:10 Yeah, yeah.

19:11 For quite a while, yeah.

19:12 The pytest support has been getting better and better in the last year or so.

19:18 And the PyCharm released 2018.2, I think it was last week.

19:24 And it totally beefs up support for pytest fixtures.

19:29 And that's, I think, I just wanted to mention that because anybody that's using both PyCharm and pytest definitely needs to update to 2018.2 because a few things that used to not work but do now, if you have a fixture that you're, so a fixture, if anybody's not familiar, a fixture is just a little piece of code, a little function that you declare as a fixture, that you put it as the parameter to your test, and it gets run before your test gets run at various levels.

19:58 That's a simplification, but that works.

20:00 But it shows up as a parameter, but you don't actually have to use it in your function.

20:06 It just tells pytest to run that code.

20:09 Well, PyCharm used to flag that as a variable that isn't referenced.

20:13 So, it gives you a warning.

20:15 And you also couldn't do code completion with it if it was an object that had methods on it.

20:21 And you also couldn't use it to look up where is that fixture defined?

20:25 Because it's often defined in a different file, in a conf test file or something.

20:29 But now you can, all those things now work.

20:31 So, big thank you to the PyCharm team for getting that out so quickly.

20:35 And yeah, I just wanted to let people know about that.

20:39 - Yeah, that's really awesome.

20:40 PyCharm just keeps getting better and better.

20:42 All right, so speaking of getting better, let's go back to Python performance in an indirect way.

20:48 - You got like this bone that you won't let go of, man.

20:51 No, it's good.

20:52 So this one actually hits on two themes that I think we've touched a lot on.

20:57 One is this performance kick that I'm on today, but the other is packaging.

21:01 Like we've talked about how it's not super easy to package up Python.

21:04 So for example, Go compiles to a single executable binary with zero dependencies.

21:09 You take that, you give it to somebody, they run it.

21:12 That's not how it works for complicated apps that have package dependencies and stuff in Python.

21:17 You can't just easily go, "Here, double click this." You're like, yeah, good luck.

21:22 Hold on first pip install requirements.

21:24 Now what else?

21:25 Oh, you got the wrong version of Python.

21:26 Hold on.

21:27 So the whole packaging thing, you know, there are some attempts to deal with it, like CX freeze and stuff like that.

21:33 And it's somewhat working.

21:35 But here's a interesting thing from Facebook called Azar.

21:41 So Azar is an executable archive.

21:45 And basically what it is, is you can package up some bunch of code into a single executable file that you can then mount as like a separate file system that's read only like, like a CD or something, I guess.

21:59 And so you can mount this and then execute it.

22:01 But because it came as a whole, like block, basically, it's sort of a native file system that you know, you can read files that are next to your Python files, and you just package up your dependencies and run it.

22:15 Yeah, so it's this read only file system, which when you mount it, it looks like a regular directory to user space, the one drawback, like I was when I saw this, I'm like, Oh, my gosh, is this it?

22:25 Is this the thing that is going to make it that we can just go here, double click this in Python.

22:30 It turns out there's a minor little step here, this requires a one time installation of a system level device driver for the file system.

22:38 So maybe not so much just double click.

22:41 But if you're willing to install this, maybe as your organization you install it or on your servers for whatever reason, you're willing to install this thing called Squash FS, then stars become these things you can just pass around and run.

22:55 So that's pretty cool.

22:56 So that's a good caveat.

22:58 But think of Facebook, their goal is to make it super easy to deploy these applications onto servers and run them.

23:05 So they must pre-configure their servers with Squash FS and then just make this part of their deploy mechanism. So there's basically two primary use cases for these SARs. One is simply collecting a number of files for automatic atomic mounting somewhere in the file system. Cool. And said you can use this thing called SAR exec helper. It becomes a self-contained package of executable code and its data. So an example might be a Python app that archives all of its source code and its native libraries and configuration and all that kind of stuff.

23:40 I still think that's pretty cool.

23:41 Yeah, it's kind of a focused use but still pretty cool.

23:45 Yeah, but then it's a rabbit hole now I got to go and read Squire about squash and what that is.

23:50 Yes, it's true.

23:51 So actually on this, it seems like it's generally, it could be a general mechanism for, I don't know, Ruby or JavaScript or Node or something like that.

24:02 But they particularly call out Python on the GitHub page from the Facebook incubator.

24:07 And it says some of the advantages for using it for Python are it looks like regular files on disk to Python.

24:15 So that means you can just run CPython and it doesn't know any better.

24:18 Same thing, it looks like regular files to use.

24:20 You don't need to use like weird package import, package resource tricks or anything like that to pass stuff around.

24:27 compression, it doesn't require of unpacking SO files, apparently, which like if you try to use one of the PEX mechanisms, that was a problem. So more or less it just like CPython just works basically. Your idea of like okay so there's this dependency but if you're using it within an organization that totally makes sense, it's fine. And I yeah that sort of use case or within your own servers or whatever, yeah there's a lot of use cases where I think this would be very useful for people. Yeah, I mean this like those places you described there's that's where a lot of Python is used. So it also has some interesting performance benefits which is coming back to that. It's because the way squash FS works you're actually reading off a disk a smaller set of binaries because it's compressed a smaller set of data so there's less disk activity and it's still really fast so the startup time can actually be faster for your app than and even native Python, which is pretty cool.

25:26 And once it started, once it's pretty cool.

25:28 There's some statistics in there that show it's either as fast or sometimes like the second time you've interacted with that, that SAR, because the file system actually decompresses and caches that stuff in memory, it can actually run a little bit quicker.

25:42 So there's a lot of interesting things around performance there.

25:45 And finally, these file system thing, these SARs, right?

25:49 They're read-only, which means the integrity of your app is guaranteed as opposed to say a virtual environment or like a folder where people could mess with it or change the Python system like it's read-only so what you gave them is what they're running. That's a cool thing too.

26:02 Yeah there's a lot of neat stuff here. I don't think it fits my use case but maybe some listeners it'll work well for them. Definitely. Yeah a lot of people seemed excited about it on the Twitter. On the Twitters. Exactly. All right well that's it for all of our items. You got some extras you want to throw out there?

26:15 Oh just somebody mentioned to me on Twitter, speaking of Twitters, that NumPy 1.15 or 1.15.0 was just released recently, and they completely overhauled their testing to use pytest, yay.

26:30 - Yay, another win for pytest, that's awesome.

26:33 - Yeah, and then you've got a couple lists of some videos that are out.

26:37 - Yeah, I have a couple of events, but I have two in the future and two in the past.

26:41 Maybe, depending on when you listen to this, actually all of them in the past, but right now, two in the future and two in the past.

26:46 So SciPy 2018, the Data Science Python World Conference, the videos for that are now out on YouTube.

26:54 So if you couldn't make it to SciPy and you wanna catch a bunch of the presentations there, here's a link to the videos.

27:00 Also, PyOhio, we have a lot of these regional Python conferences, and somehow PyOhio has actually gained quite a bit of momentum, which is interesting with PyCon being there last year and next.

27:12 Anyway, the videos for that are also out.

27:14 So a bunch of good YouTube watching coming up here over the weekend.

27:17 Yeah, I've already started a couple of these.

27:21 And then in the future, we've got...

27:23 In the future, PyCon Canada is coming.

27:26 So the call for papers on that is open for about a month.

27:30 And in the future, PyBay 2018, so the regional San Francisco Python conference is happening in a couple of weeks.

27:41 And I would love to go to that, but I can't.

27:42 Yeah, I still haven't decided.

27:44 - Are you thinking about going?

27:44 - I was thinking about going to Pi Bay, but there's a lot of stuff going on in the fall, so we'll see.

27:49 - Yeah, I have daughters going to college right around then, so it'd probably be better if I helped them do that instead of just go hang out in San Francisco.

27:55 All right, so the last thing is I just wanna let people know that I have another course out, building data-driven web apps with Pyramid and SQLAlchemy.

28:04 It's super fun, nine hours of awesomeness.

28:07 So there's a link in there, check that out as well, people are interested.

28:10 - Oh, that looks fun.

28:11 - Yeah, it's definitely a good one.

28:12 I covered some things that I've wanted to cover for a while like ORM migrations and managing stuff over time with databases and stuff.

28:19 Pretty cool.

28:20 All right.

28:21 Well, Brian, thanks as always.

28:22 It's been fun.

28:23 Thank you.

28:24 Yep.

28:25 Bye.

28:26 Thank you for listening to Python Bytes.

28:27 Follow the show on Twitter via @pythonbytes.

28:30 That's Python Bytes as in B-Y-T-E-S.

28:33 And get the full show notes at pythonbytes.fm.

28:36 If you have a news item you want featured, just visit pythonbytes.fm and send it our way.

28:41 the lookout for sharing something cool.

28:43 On behalf of myself and Brian Okken, this is Michael Kennedy.

28:47 Thank you for listening and sharing this podcast with your friends and colleagues.

Back to show page