WEBVTT

00:00:00.001 --> 00:00:02.620
This episode, we have an optimization twofer.

00:00:02.620 --> 00:00:07.540
We begin by looking at optimizing a subset of Python code for machine learning

00:00:07.540 --> 00:00:12.740
using the LLVM compiler with a project called PyLLVM.

00:00:12.740 --> 00:00:16.840
It takes plain Python code, compiles it to optimize machine instructions,

00:00:16.840 --> 00:00:19.920
and distributes it across a cluster to do machine learning.

00:00:19.920 --> 00:00:23.860
In the second half, we'll look at a fabulous new way to work with MongoDB

00:00:23.860 --> 00:00:25.980
for Python writing data scientists.

00:00:26.240 --> 00:00:31.340
The project is called Bison NumPy and provides direct connections

00:00:31.340 --> 00:00:33.300
between MongoDB and NumPy.

00:00:33.300 --> 00:00:37.140
It's 10 times faster than working with PyMongo directly

00:00:37.140 --> 00:00:39.320
if you plan to end up in NumPy anyway.

00:00:39.320 --> 00:00:43.660
You're about to meet the woman behind both of these projects, Anna Herlihy.

00:00:43.660 --> 00:00:49.500
This is Talk Python To Me, episode 103, recorded February 6, 2017.

00:00:52.480 --> 00:01:17.560
Welcome to Talk Python To Me, a weekly podcast on Python,

00:01:17.560 --> 00:01:20.620
the language, the libraries, the ecosystem, and the personalities.

00:01:21.260 --> 00:01:22.760
This is your host, Michael Kennedy.

00:01:22.760 --> 00:01:24.760
Follow me on Twitter where I'm @mkennedy.

00:01:24.760 --> 00:01:28.640
Keep up with the show and listen to past episodes at talkpython.fm

00:01:28.640 --> 00:01:31.180
and follow the show on Twitter via at Talk Python.

00:01:31.180 --> 00:01:36.380
This episode is brought to you by Talk Python Training and Hired.

00:01:36.380 --> 00:01:39.720
Be sure to check out what we both have to offer during our segments.

00:01:39.720 --> 00:01:40.740
It helps support the show.

00:01:40.740 --> 00:01:43.640
Anna, welcome to Talk Python.

00:01:43.640 --> 00:01:44.520
Thank you.

00:01:44.520 --> 00:01:45.780
Thank you for having me.

00:01:45.780 --> 00:01:46.940
Yeah, it's great to have you here.

00:01:46.940 --> 00:01:49.660
We got a couple of really cool things to talk about.

00:01:49.660 --> 00:01:55.460
We're going to talk about PyLLVM, which is a really cool project that you worked on.

00:01:55.460 --> 00:01:59.800
And we're also going to talk about a super high-performance sort of data science-y thing

00:01:59.800 --> 00:02:02.180
with MongoDB that you're also working on right now.

00:02:02.180 --> 00:02:04.800
So I'm looking forward to talking about both of those.

00:02:04.800 --> 00:02:06.980
But before we get into those, what's your story?

00:02:06.980 --> 00:02:08.240
How did you get into programming in Python?

00:02:08.640 --> 00:02:12.560
I had my first real experience with programming.

00:02:12.560 --> 00:02:18.380
I mean, I had done some basic when I was pretty small, but I thought it was not very fun.

00:02:18.380 --> 00:02:20.880
So I kind of put that off for another five or six years.

00:02:20.880 --> 00:02:24.820
But in university, I was kind of all over the place.

00:02:24.820 --> 00:02:27.080
I didn't know if I wanted to be a writer or an engineer.

00:02:27.440 --> 00:02:31.280
But I figured that computers are probably going to be relevant.

00:02:31.280 --> 00:02:38.460
So I took a CS class because I didn't know the difference between computer science and computer

00:02:38.460 --> 00:02:38.900
literacy.

00:02:38.900 --> 00:02:43.260
But it ended up actually working out in my favor because I really, really enjoyed it.

00:02:43.260 --> 00:02:47.800
And I ended up prioritizing my CS classes over all my other classes.

00:02:47.800 --> 00:02:50.100
And then I kind of figured, maybe I should just do this full time.

00:02:50.100 --> 00:02:51.380
That's really cool.

00:02:51.460 --> 00:02:52.680
What was that first CS class?

00:02:52.680 --> 00:02:59.900
That was Andy Van Dam's intro to computer science, which for anybody who went to Brown will recognize

00:02:59.900 --> 00:03:02.000
that it's quite a popular course.

00:03:02.000 --> 00:03:05.700
I mean, when I took it, it was maybe 150 people.

00:03:05.700 --> 00:03:09.380
But I think just last year, they had 250 or something.

00:03:09.380 --> 00:03:11.120
It's really, really blowing up.

00:03:11.120 --> 00:03:11.920
Oh, that's excellent.

00:03:11.920 --> 00:03:13.420
What language did you study?

00:03:13.420 --> 00:03:13.940
Do you remember?

00:03:13.940 --> 00:03:15.860
Yeah, it was a Java course.

00:03:15.860 --> 00:03:17.200
We did a lot of swings.

00:03:17.200 --> 00:03:18.520
Okay, excellent.

00:03:18.520 --> 00:03:19.400
Yeah.

00:03:19.500 --> 00:03:22.100
And so you decided, hey, I kind of like this programming stuff.

00:03:22.100 --> 00:03:23.700
Let's just do that as a job, right?

00:03:23.700 --> 00:03:29.320
And how did you go from learning Java and CS 101 to working in Python?

00:03:29.320 --> 00:03:34.860
So I really liked programming languages in general.

00:03:34.860 --> 00:03:38.100
So I wrote some compilers, some interpreters.

00:03:38.100 --> 00:03:44.120
My area in computer science was mostly looking at different programming languages.

00:03:44.260 --> 00:03:48.280
And I found that Python was kind of the most elegant, in my opinion.

00:03:48.280 --> 00:03:53.000
And I didn't feel bogged down by really complicated syntax.

00:03:53.000 --> 00:03:54.580
But I also felt very powerful.

00:03:54.580 --> 00:04:02.180
So I kind of felt that as opposed to being pure Python, I wanted to do more like Python and C stuff,

00:04:02.180 --> 00:04:07.540
sort of more from the very beginning to how it gets down to the computer code at the end.

00:04:07.660 --> 00:04:07.920
I see.

00:04:07.920 --> 00:04:11.740
So you really were interested in like the internals and stuff and whatnot, huh?

00:04:11.740 --> 00:04:12.080
Yes.

00:04:12.080 --> 00:04:12.440
Okay.

00:04:12.440 --> 00:04:13.520
Cool.

00:04:13.520 --> 00:04:18.740
And so the first project we're going to talk about, which I want to ask you a question quick before then,

00:04:18.740 --> 00:04:23.380
but PyLLVM, that was part of your university work, right?

00:04:23.380 --> 00:04:24.400
Yes, exactly.

00:04:24.400 --> 00:04:31.820
The project that it was based on Tupleware is a research project with some of the database professors at Brown.

00:04:32.260 --> 00:04:36.040
And my project was a senior thesis that was built sort of on top of that.

00:04:36.040 --> 00:04:36.340
Okay.

00:04:36.340 --> 00:04:36.700
Wow.

00:04:36.700 --> 00:04:37.280
Very cool.

00:04:37.280 --> 00:04:37.700
Very cool.

00:04:37.700 --> 00:04:38.400
Okay.

00:04:38.400 --> 00:04:40.460
So today you work at MongoDB, right?

00:04:40.460 --> 00:04:40.840
Yes.

00:04:40.840 --> 00:04:41.200
Yeah.

00:04:41.200 --> 00:04:41.840
What do you do there?

00:04:41.840 --> 00:04:47.580
So I actually recently switched over from spending the majority of my time on Python work.

00:04:47.580 --> 00:04:54.280
So like PyMongo, Mongo Connector, other sort of Python related projects to MongoDB Compass,

00:04:55.000 --> 00:04:58.820
which is basically a user interface for MongoDB.

00:04:58.820 --> 00:05:04.500
It's actually written in JavaScript, which was something new for me since I had done a pretty

00:05:04.500 --> 00:05:06.780
excellent job of avoiding JavaScript until now.

00:05:06.780 --> 00:05:08.440
It finally got you, huh?

00:05:08.440 --> 00:05:09.380
Yeah, I know.

00:05:09.380 --> 00:05:10.080
It was inevitable.

00:05:10.080 --> 00:05:11.580
But now I really like it.

00:05:11.580 --> 00:05:16.980
I can't say that I am converted and I still think that Python is the community and the language

00:05:16.980 --> 00:05:17.860
that I like best.

00:05:17.860 --> 00:05:19.940
I'm probably preaching to the choir here.

00:05:20.180 --> 00:05:26.420
But I now split my time between working on Compass and working on this BSON NumPy package,

00:05:26.420 --> 00:05:28.140
which I hope we'll get to talk about later.

00:05:28.140 --> 00:05:30.400
Yeah, we'll definitely talk about it because it's very cool.

00:05:30.400 --> 00:05:31.700
Okay.

00:05:31.700 --> 00:05:32.180
Excellent.

00:05:32.180 --> 00:05:35.080
So it sounds like you have a lot of cool projects going on.

00:05:35.080 --> 00:05:39.380
Let's talk about the project that you did first with this LLVM thing.

00:05:39.380 --> 00:05:45.560
So I suspect a number of people out there know what LLVM is, but the audience is diverse.

00:05:45.560 --> 00:05:46.900
There's people from all sorts of places.

00:05:46.900 --> 00:05:49.880
Let's start with just talking about what is LLVM?

00:05:49.880 --> 00:05:50.380
Cool.

00:05:50.380 --> 00:05:54.640
So despite the name, it doesn't really have too much to do with virtual machines.

00:05:54.640 --> 00:05:58.360
It's basically just a collection of compiler technologies.

00:05:58.360 --> 00:06:01.340
So the LLVM project itself is huge.

00:06:01.340 --> 00:06:07.080
But what I worked with is LLVM IR, which stands for intermediate representation.

00:06:07.080 --> 00:06:15.280
And LLVM IR is basically a way of representing code that's about halfway between a top level

00:06:15.280 --> 00:06:15.780
language.

00:06:15.780 --> 00:06:19.340
So for example, Python and some machine code.

00:06:19.340 --> 00:06:21.160
So something that you wouldn't really want to read.

00:06:21.160 --> 00:06:25.160
You could probably look at the assembler and know what was going on, but it's not something

00:06:25.160 --> 00:06:26.440
you would ever really want to write.

00:06:26.440 --> 00:06:30.900
But runs much, much faster than something that a human would be able to write.

00:06:30.900 --> 00:06:31.180
Sure.

00:06:31.180 --> 00:06:32.220
So does...

00:06:32.220 --> 00:06:34.020
I don't know all that much about it, actually, that myself.

00:06:34.200 --> 00:06:42.200
So do you compile the source language into this LLVM IR and then do a further compilation

00:06:42.200 --> 00:06:43.460
towards some final target?

00:06:43.460 --> 00:06:44.940
Yes, exactly.

00:06:44.940 --> 00:06:50.460
And that's actually what makes it so powerful, is that a lot of the time you have compilers

00:06:50.460 --> 00:06:51.660
that have to be very specialized.

00:06:52.060 --> 00:06:54.940
So they take a language and they take a platform.

00:06:54.940 --> 00:07:01.860
And that is what the compiler does, is it compiles Python for a particular architecture.

00:07:01.860 --> 00:07:07.940
What LLVM IR does, which is not unique to LLVM, it's true for all intermediate representations

00:07:07.940 --> 00:07:14.660
or most of them, is that it's a way of making things both language and platform agnostic.

00:07:14.660 --> 00:07:23.180
So you can take any popular language you want from Java, R, Python, and you can compile it

00:07:23.180 --> 00:07:24.580
down to LLVM IR.

00:07:24.580 --> 00:07:30.680
Then you can take that LLVM IR and you can ship it anywhere and it can be compiled down to most

00:07:30.680 --> 00:07:31.300
platforms.

00:07:31.300 --> 00:07:36.100
So it doesn't even matter what the original language was and it doesn't matter what platform

00:07:36.100 --> 00:07:37.280
you're eventually going to run it on.

00:07:37.280 --> 00:07:39.260
It makes the code really...

00:07:39.260 --> 00:07:43.260
It makes it very cross-platform and it makes it fast.

00:07:43.260 --> 00:07:44.380
Yeah, sure.

00:07:44.480 --> 00:07:49.600
So basically, if you can get something to compile down to LLVM IR, you can get it to

00:07:49.600 --> 00:07:52.280
run quickly on many, many places, right?

00:07:52.280 --> 00:07:59.680
Because there's existing infrastructure to turn LLVM IR into executable code on all sorts

00:07:59.680 --> 00:08:00.460
of platforms, right?

00:08:00.460 --> 00:08:01.040
Exactly.

00:08:01.040 --> 00:08:02.340
And it's super powerful.

00:08:02.340 --> 00:08:09.180
The optimizations that the LLVM IR compilers do make it so that it doesn't matter if you wrote

00:08:09.180 --> 00:08:11.900
your code in C or if you wrote your code in Python.

00:08:12.180 --> 00:08:15.780
I'm of the opinion that it's much easier to write your code in Python than it is to write

00:08:15.780 --> 00:08:16.200
it in C.

00:08:16.200 --> 00:08:21.540
And so it's pretty nice because you get the benefits of maybe a more difficult syntax with

00:08:21.540 --> 00:08:23.700
pretty much whatever you're comfortable in.

00:08:23.700 --> 00:08:24.880
Yeah, that's really cool.

00:08:24.880 --> 00:08:29.360
What's the weirdest thing that you can execute this stuff on?

00:08:29.360 --> 00:08:30.180
What's the weirdest platform?

00:08:31.020 --> 00:08:31.940
That's a good question.

00:08:31.940 --> 00:08:33.140
Actually, I don't know.

00:08:33.140 --> 00:08:39.300
But I think it's pretty easy to if you just look up the LLVM IR or the LLVM compiler project,

00:08:39.300 --> 00:08:44.580
I think it's a really good way to get to know compilers in general.

00:08:44.580 --> 00:08:51.200
So there are a lot of proof of concept compilers that use LLVM because the infrastructure is so

00:08:51.200 --> 00:08:51.460
good.

00:08:51.580 --> 00:08:57.780
So I'm sure somebody has written something for an extremely obscure platform just to show

00:08:57.780 --> 00:08:58.640
that they could do it.

00:08:58.640 --> 00:08:59.380
Yeah, I'm sure.

00:08:59.380 --> 00:09:05.120
Do you think that it makes a lot of sense for people to create from scratch compilers these

00:09:05.120 --> 00:09:05.360
days?

00:09:05.360 --> 00:09:08.040
Or should most people just be building on LLVM?

00:09:08.040 --> 00:09:12.620
I think it depends on what the goal of your project is.

00:09:13.060 --> 00:09:19.120
So because this was a project that was done in the world of academia, I pretty much just

00:09:19.120 --> 00:09:21.580
picked whatever seemed like would be the most fun.

00:09:21.580 --> 00:09:28.520
And I know that's not necessarily true for a lot of people who have needs and users and they

00:09:28.520 --> 00:09:29.320
have to work quickly.

00:09:29.320 --> 00:09:35.940
And so I think it just depends on if you want something to work faster or if you want to learn

00:09:35.940 --> 00:09:37.120
a lot while you're doing it.

00:09:37.120 --> 00:09:37.360
Sure.

00:09:37.360 --> 00:09:37.600
Yeah.

00:09:37.600 --> 00:09:41.860
And I guess also like you only had, it was like a senior project sort of thing, right?

00:09:41.880 --> 00:09:45.440
So you couldn't start from scratch and do a ton of work.

00:09:45.440 --> 00:09:48.140
You had a time frame and a little amount of time and energy, right?

00:09:48.140 --> 00:09:49.120
So I guess that's a-

00:09:49.120 --> 00:09:49.120
Exactly.

00:09:49.120 --> 00:09:49.860
I had to graduate.

00:09:49.860 --> 00:09:50.600
Exactly.

00:09:50.600 --> 00:09:52.540
So that's a pretty good testament to LLVM.

00:09:52.540 --> 00:09:53.340
Cool.

00:09:53.340 --> 00:09:58.920
And I know it's like used for Swift and some other things on the Apple platform and it's

00:09:58.920 --> 00:09:59.240
pretty cool.

00:09:59.240 --> 00:10:00.200
Okay.

00:10:00.200 --> 00:10:05.680
So you also talked about a project to do with machine learning called Tupleware.

00:10:05.680 --> 00:10:07.540
And this is a project at Brown, right?

00:10:07.540 --> 00:10:08.820
Yes, it is.

00:10:08.820 --> 00:10:18.920
So Tupleware's tagline is that it is a distributed analytical platform that leverages LLVM IR to

00:10:18.920 --> 00:10:23.220
be totally platform agnostic and totally language agnostic.

00:10:23.880 --> 00:10:31.840
So the way that PY LLVM fits into Tupleware, it was basically a proof of concept that you

00:10:31.840 --> 00:10:38.040
could take Python code and you could compile it down to LLVM IR and then you could ship

00:10:38.040 --> 00:10:44.360
it to your clusters and then have them be automatic and have your code be automatically run on a

00:10:44.360 --> 00:10:45.320
very large dataset.

00:10:45.540 --> 00:10:46.020
Okay, cool.

00:10:46.020 --> 00:10:52.760
So this is like a distributed machine learning type of system and your project PY LLVM basically

00:10:52.760 --> 00:10:57.420
made it possible to feed Python instead of say C++ code or something like that to it, right?

00:10:57.420 --> 00:10:57.940
Exactly.

00:10:57.940 --> 00:11:04.200
And I chose Python because I didn't really want to write an R compiler, but also because at

00:11:04.200 --> 00:11:10.900
the time I was working on this, which is about three, three and a half years ago now, the language

00:11:10.900 --> 00:11:14.900
that most scientists were using for programming was still MATLAB.

00:11:14.900 --> 00:11:21.940
I actually had a summer job where I converted MATLAB code to scientific Python code and it was

00:11:21.940 --> 00:11:26.260
becoming more and more common that people who were not programmers would be programming in Python

00:11:26.260 --> 00:11:29.820
instead of just inheriting these old MATLAB scripts that they would reuse.

00:11:29.820 --> 00:11:32.860
So that's why I've picked Python.

00:11:32.860 --> 00:11:33.980
Yeah, that's cool.

00:11:33.980 --> 00:11:35.220
I've definitely seen that as well.

00:11:35.220 --> 00:11:40.880
When I was in school, MATLAB was definitely the thing and people would have all these scripts

00:11:40.880 --> 00:11:47.360
and one of my first programming jobs was to take a bunch of MATLAB code and turn it into like

00:11:47.360 --> 00:11:54.140
graphical visualizations on Silicon Graphics supercomputers and I had to convert the MATLAB code to C++

00:11:54.140 --> 00:11:57.900
but if it was today, I very well might have done that in Python instead, right?

00:11:57.900 --> 00:11:59.560
Yeah, that's funny.

00:11:59.560 --> 00:12:00.560
Sounds like the same job.

00:12:00.560 --> 00:12:01.600
Yeah, exactly.

00:12:01.600 --> 00:12:02.100
Exactly.

00:12:02.100 --> 00:12:03.380
Awesome.

00:12:03.380 --> 00:12:08.440
And so you said PY LLVM started from an abandoned open source project.

00:12:08.440 --> 00:12:09.780
What's the story there?

00:12:09.780 --> 00:12:15.180
Basically, once I had pinned down what exactly I wanted to do for my thesis,

00:12:15.180 --> 00:12:20.160
I went on a search spree where I tried to figure out if somebody else had done it for me,

00:12:20.160 --> 00:12:21.700
which would have been very convenient.

00:12:21.700 --> 00:12:32.900
But at the time, I couldn't find any Python 2 LLVM IR compilers that were already up and running or already published.

00:12:32.900 --> 00:12:36.680
So what I did is I found this GitHub.

00:12:36.680 --> 00:12:37.840
Actually, it wasn't even GitHub.

00:12:38.020 --> 00:12:39.500
It was a Google code, right?

00:12:39.500 --> 00:12:46.060
Yeah, it was a Google code project, which had been documented almost zero.

00:12:46.060 --> 00:12:52.200
There were some slides in Japanese that I found, which I tried to feed through Google Translate, which did not work.

00:12:52.800 --> 00:12:55.180
So it was basically just some code.

00:12:55.180 --> 00:13:04.560
And it ended up being almost exactly what I needed because it was a pretty simple compiler outline without any of it really implemented.

00:13:04.560 --> 00:13:06.100
But the structural stuff was there.

00:13:06.280 --> 00:13:11.660
So a lot of the scoping and a lot of the variable tracking had been started but not finished.

00:13:11.880 --> 00:13:21.440
So it was kind of the perfect project to pick up because a lot of the design decisions that would have resulted in a lot of cost versus value arguments.

00:13:21.440 --> 00:13:28.740
So, you know, we could implement it this way, but it would take a lot of time and we might not actually get that much out of it was sort of already decided for me.

00:13:28.860 --> 00:13:38.620
So I had a pretty clear path in terms of getting the subset of Python it supported to be something that I thought a user could actually make use of.

00:13:38.620 --> 00:13:42.680
Yeah, that seems really great that, you know, that's out there and you can pick it up.

00:13:42.680 --> 00:13:49.760
And it's funny it was on Google Code because not only was the project kind of abandoned, like Google Code itself is kind of in archive mode, right?

00:13:49.760 --> 00:13:51.740
I guess it's a real testament to GitHub.

00:13:51.740 --> 00:13:55.080
I think it was in archive mode when I found it even.

00:13:55.080 --> 00:13:57.580
It's definitely in archive mode now.

00:13:57.580 --> 00:13:58.700
It's several layers.

00:13:58.700 --> 00:14:07.660
Yeah, but that's helpful that it's like, well, we already kind of put the thinking into place, but hey, I need to – they just didn't take it to completion, right?

00:14:07.660 --> 00:14:13.140
And so, like, okay, these are the final to-dos on this project or whatever until I can actually use it on Tupleware.

00:14:13.140 --> 00:14:15.760
Yeah, I mean, when I found it, it wouldn't even compile.

00:14:15.760 --> 00:14:28.440
So I have really a lot of respect for the person that wrote it because it really had everything – it was clear that this person didn't actually run it, but they had thought about it a lot.

00:14:28.440 --> 00:14:34.000
And the skeleton was there, but it hadn't actually gone through the process of becoming a tool that could be run.

00:14:34.000 --> 00:14:36.240
And that, to me, is very incredible.

00:14:36.400 --> 00:14:39.960
I mean, I run my code every 10 seconds just to make sure that it's working.

00:14:39.960 --> 00:14:40.560
Exactly.

00:14:40.560 --> 00:14:43.020
Yeah, I'm with you on that.

00:14:43.020 --> 00:14:43.860
Like, run it often.

00:14:43.860 --> 00:14:56.420
So, yeah, I guess some people – you know, probably more in the early days when compiling took longer and stuff, but some people really sit there and work through it, and then they run it.

00:14:56.420 --> 00:14:59.360
You know, hours after hours of work, that's just how I work.

00:14:59.360 --> 00:15:00.260
I run it often.

00:15:00.260 --> 00:15:02.400
Yeah, it's very impressive.

00:15:02.400 --> 00:15:03.460
Yes, indeed.

00:15:03.460 --> 00:15:05.320
It takes a subset of Python.

00:15:05.320 --> 00:15:08.900
I can't feed it like Django and have it spit that out, right?

00:15:08.900 --> 00:15:16.540
Because what happens is it takes the Python language and it turns it to LLVMIR, which then finally compiles down to machine instructions.

00:15:16.540 --> 00:15:19.940
But there's no interpreter.

00:15:19.940 --> 00:15:24.680
There's no standard library underneath there for it to run, right?

00:15:24.720 --> 00:15:26.660
So, you've got to be pretty focused on what you give it.

00:15:26.660 --> 00:15:27.020
Yes.

00:15:27.020 --> 00:15:31.240
What it does have is sort of the C standard library.

00:15:31.240 --> 00:15:43.820
So, for example, if you want to call, like, Printline, you can do some somewhat complicated gymnastics in order to get LLVM to make that external call.

00:15:44.240 --> 00:15:49.740
So, that's really nice because getting Printline to work when you're writing a compiler is so important.

00:15:49.740 --> 00:15:53.120
And when it does work, it's, like, really one of the best feelings ever.

00:15:53.120 --> 00:16:05.560
But generally, no, if you wouldn't be able to import a Python package, for example, since the goal of the project was really just to provide people an alternative to using MATLAB.

00:16:05.680 --> 00:16:10.040
So, instead of writing your machine learning algorithms in MATLAB, you can write them in Python.

00:16:10.040 --> 00:16:14.660
Or, you know, in this case specifically, it'd be instead of writing in C++, you're writing it in Python.

00:16:14.660 --> 00:16:18.620
But the nature of those algorithms is that they're quite simple.

00:16:18.620 --> 00:16:27.500
So, we didn't – I didn't spend a lot of time trying to get objects or more complicated data structures working because I didn't anticipate it would be that common.

00:16:27.500 --> 00:16:28.060
Right.

00:16:28.200 --> 00:16:33.680
So, you're working with, like, loops and join and map and stuff like that.

00:16:33.680 --> 00:16:36.980
And then you feed it basic algorithms, huh?

00:16:36.980 --> 00:16:40.440
So, like, Bayesian stuff or linear regression.

00:16:40.440 --> 00:16:42.300
Yeah, exactly.

00:16:42.300 --> 00:16:43.160
You feed those off, huh?

00:16:43.160 --> 00:16:43.420
Okay.

00:16:43.420 --> 00:16:49.240
And I'm not a machine learning – I had not really done much machine learning at that time.

00:16:49.240 --> 00:16:59.320
So, I basically just went to my ML professor and was like, give me, like, your top five most common machine learning algorithms that you would expect to want to run on large data sets.

00:16:59.320 --> 00:17:02.680
I'll make these run in Python through this thing, right?

00:17:02.680 --> 00:17:03.780
Nice.

00:17:03.780 --> 00:17:05.080
And so, yeah.

00:17:05.080 --> 00:17:06.660
So, you basically – you write the algorithms.

00:17:06.660 --> 00:17:12.700
They compile down and then finally get transformed to run basically on the C, C++ runtime, like you said.

00:17:12.700 --> 00:17:15.740
And that goes through something called Boost Python, right?

00:17:15.740 --> 00:17:16.540
What is Boost Python?

00:17:16.840 --> 00:17:25.640
So, Boost Python – Boost is a bunch of packages for C++ that provide a lot of really, really powerful capabilities.

00:17:25.640 --> 00:17:31.620
And so, Boost Python is actually just an easier way of calling the Python C API.

00:17:31.620 --> 00:17:38.740
If I had done this now, I probably just would have skipped using Boost entirely because I'm pretty comfortable with the Python C API.

00:17:38.740 --> 00:17:45.380
But at the time, it was sort of the first Python plus another language interfacing I had done before.

00:17:45.760 --> 00:17:51.760
And this was pretty simple because it was already being used by Tupperware itself.

00:17:51.760 --> 00:17:55.840
So, I didn't have to do a lot of – I didn't actually have to incorporate it into the ecosystem.

00:17:55.840 --> 00:17:56.800
It was already there.

00:17:56.800 --> 00:17:57.120
Yeah.

00:17:57.120 --> 00:17:57.780
That's cool.

00:17:57.780 --> 00:17:58.080
Yeah.

00:17:58.080 --> 00:18:02.060
And by now, you've done so much with PyMongo and all the stuff in MongoDB.

00:18:02.060 --> 00:18:02.480
Yeah.

00:18:02.480 --> 00:18:02.900
Yeah.

00:18:02.900 --> 00:18:03.260
Cool.

00:18:03.260 --> 00:18:03.700
Okay.

00:18:03.700 --> 00:18:08.500
So, give us, like, a short example of what this algorithm might look like.

00:18:08.560 --> 00:18:11.040
I mean, code over audio is really hard.

00:18:11.040 --> 00:18:14.300
But just, like, what kind of stuff would you feed off to this system?

00:18:14.300 --> 00:18:19.300
So, I would expect there to be – so, you've got to assign to your variables.

00:18:19.300 --> 00:18:25.880
You most likely have an array that you are assigning values to.

00:18:26.420 --> 00:18:31.240
You need to iterate through the array or you need to iterate through the data that you've been given.

00:18:31.240 --> 00:18:35.280
And you need to do a lot of – or at least a decent amount of error checking.

00:18:35.280 --> 00:18:42.520
So, what that means in terms of syntax is you would want to have reassignable variables.

00:18:42.520 --> 00:18:47.460
You would need to have conditionals, loops, and arithmetic.

00:18:47.980 --> 00:18:49.780
Those are basically the most important things.

00:18:49.780 --> 00:18:53.640
This portion of Talk Python To Me is brought to you by us.

00:18:53.640 --> 00:18:59.540
As many of you know, I have a growing set of courses to help you go from Python beginner to novice to Python expert.

00:18:59.540 --> 00:19:01.580
And there are many more courses in the works.

00:19:01.580 --> 00:19:05.820
So, please consider Talk Python training for you and your team's training needs.

00:19:05.820 --> 00:19:12.260
If you're just getting started, I've built a course to teach you Python the way professional developers learn, by building applications.

00:19:12.260 --> 00:19:17.320
Check out my Python Jumpstart by building 10 apps at talkpython.fm/course.

00:19:17.320 --> 00:19:20.460
Are you looking to start adding services to your app?

00:19:20.460 --> 00:19:23.620
Try my brand new Consuming HTTP Services in Python.

00:19:23.620 --> 00:19:29.060
You'll learn to work with RESTful HTTP services as well as SOAP, JSON, and XML data formats.

00:19:29.060 --> 00:19:30.920
Do you want to launch an online business?

00:19:30.920 --> 00:19:35.120
Well, Matt Makai and I built an Entrepreneur's Playbook with Python for Entrepreneurs.

00:19:35.120 --> 00:19:40.360
This 16-hour course will teach you everything you need to launch your web-based business with Python.

00:19:40.360 --> 00:19:43.900
And finally, there's a couple of new course announcements coming really soon.

00:19:43.900 --> 00:19:49.360
So, if you don't already have an account, be sure to create one at training.talkpython.fm to get notified.

00:19:49.360 --> 00:19:53.140
And for all of you who have bought my courses, thank you so much.

00:19:53.140 --> 00:19:55.080
It really, really helps support the show.

00:19:55.080 --> 00:20:00.360
One of the things that seems a little challenging to me is like you're building something in Python

00:20:00.360 --> 00:20:09.580
and it's being more or less just compiled into the results of C++, which is a typed system, right?

00:20:09.580 --> 00:20:14.120
So, it expects, you know, here's a four-byte integer.

00:20:14.120 --> 00:20:16.000
Here's a Boolean.

00:20:16.000 --> 00:20:17.560
Here's a string pointer, right?

00:20:17.560 --> 00:20:20.840
Whereas in Python, you don't really have that, right?

00:20:20.940 --> 00:20:24.800
So, what do you have to do to make the type system sort of fit together there?

00:20:24.800 --> 00:20:32.800
That is a problem that comes up sort of time and time again because no matter how many layers of abstraction you're talking about,

00:20:32.800 --> 00:20:36.680
ultimately, machine code is pretty strictly typed.

00:20:36.680 --> 00:20:40.840
And we don't really like working in strictly typed languages.

00:20:40.840 --> 00:20:53.740
So, there's a lot of cost-benefit analysis going on there where you could say I demand that I can reassign my variable from an array to an integer

00:20:53.740 --> 00:20:56.920
or between different types of numbers, for example.

00:20:56.920 --> 00:21:04.820
Or you could say that's going to save me a ton of work if I just tell my user who I anticipate is probably used to MATLAB.

00:21:04.820 --> 00:21:06.360
Don't do that.

00:21:06.360 --> 00:21:08.900
This is Python, but it's not that Pythonic.

00:21:08.900 --> 00:21:13.000
Like, you know, maybe down the line when this is no longer a proof of concept.

00:21:13.000 --> 00:21:22.840
Ultimately, what we ended up doing was just sticking to pretty static types because whenever you write a compiler,

00:21:22.840 --> 00:21:32.960
it's kind of a debate between how much do I want to reinvent the wheel and how much are my users going to be willing to sort of have a more limited experience

00:21:32.960 --> 00:21:37.520
for the sake of, like, my sanity or how much time I'm willing to put in on this project.

00:21:37.520 --> 00:21:39.140
Sure, that makes a lot of sense.

00:21:39.140 --> 00:21:45.800
I feel like because it's such a limited set of types, really, that you can write your algorithm in,

00:21:45.800 --> 00:21:51.640
it's not so bad to sort of restrict it and talk about the types, right?

00:21:51.640 --> 00:21:57.880
You've got, like, the fundamental numerical types, strings, lists, and a few other things that they can really work with, right?

00:21:58.140 --> 00:22:02.980
Yeah, and I definitely, that's what we're trying to sell here.

00:22:03.060 --> 00:22:11.680
But I remember having this argument where I came up with what I thought was, like, a super clever solution for how we're going to do dynamic types.

00:22:11.680 --> 00:22:16.420
And it was, like, twice as much code as the entire compiler was before that point.

00:22:16.420 --> 00:22:17.560
And I brought it to my advisor.

00:22:17.560 --> 00:22:19.300
I was like, this is such a good idea.

00:22:19.440 --> 00:22:21.400
Like, I can't wait to implement this.

00:22:21.400 --> 00:22:24.840
And he kind of looked at me and he was like, this is not what we need right now.

00:22:24.840 --> 00:22:33.080
What we need is something that is working, which is, I guess, was my first experience with, you know, writing code that actually needs to do something.

00:22:33.080 --> 00:22:34.920
And it needs to do something as soon as possible.

00:22:35.320 --> 00:22:38.240
As opposed to code that's, like, beautiful and elegant.

00:22:38.240 --> 00:22:40.580
And I've, like, prototyped it three times.

00:22:40.580 --> 00:22:41.960
And I have all the time in the world.

00:22:41.960 --> 00:22:42.380
So.

00:22:42.380 --> 00:22:42.860
Yeah.

00:22:42.860 --> 00:22:44.720
You know, shipping is a feature, right?

00:22:44.720 --> 00:22:45.720
Yeah.

00:22:45.720 --> 00:22:49.860
No one's going to use your code if you don't actually get it out there and get it working, I guess.

00:22:49.860 --> 00:22:50.320
Yeah.

00:22:50.320 --> 00:22:52.020
I mean, I still struggle with that now.

00:22:52.020 --> 00:23:00.120
I definitely like to write code that is fully baked as opposed to just, like, getting stuff out of the door.

00:23:00.120 --> 00:23:03.460
But I'm definitely getting used to that now that I'm working in JavaScript more.

00:23:04.600 --> 00:23:05.540
Yeah, it's one benefit.

00:23:05.540 --> 00:23:06.200
That's awesome.

00:23:06.200 --> 00:23:07.220
Yeah.

00:23:07.220 --> 00:23:12.320
Yeah, I guess I really appreciate getting something out there so that people can use it and give me feedback.

00:23:12.320 --> 00:23:13.220
Like, this is working.

00:23:13.220 --> 00:23:14.040
This is not working.

00:23:14.040 --> 00:23:16.280
But, yeah, you just have to have some flexibility.

00:23:16.280 --> 00:23:19.940
You can't get locked into, like, some early prototype API or something, right?

00:23:19.940 --> 00:23:20.760
Yeah, exactly.

00:23:20.760 --> 00:23:21.260
Yeah.

00:23:21.260 --> 00:23:25.600
So you said that some of the LLVM IR features made your life easier and some made it harder.

00:23:25.600 --> 00:23:26.820
How'd that work out?

00:23:26.820 --> 00:23:41.320
So probably the most involved issue that comes up between converting from Python to a intermediate representation is like what you mentioned with types and reassigning.

00:23:41.940 --> 00:23:46.400
LLVM IR is written in SSA, which means static single assignment.

00:23:46.400 --> 00:23:56.300
And what that basically means is that you have your registers, which are your smallest unit of storage, and you can only assign to them once.

00:23:56.980 --> 00:24:07.120
So if you assign a number to your register, even if you want to assign something of the same type, it is frozen for that function call.

00:24:07.700 --> 00:24:13.600
So what I needed to do to get around that was basically move everything onto the stack.

00:24:13.600 --> 00:24:23.120
And once everything is on the stack, then you need to keep track of stuff using what people call a symbol table, which is basically just a dictionary where you can look up.

00:24:23.120 --> 00:24:31.880
I have a variable named X, and it lives at this memory address, and it's been around for this long and all sorts of other metadata like that.

00:24:31.880 --> 00:24:32.160
Okay.

00:24:32.160 --> 00:24:33.820
So, yeah.

00:24:33.820 --> 00:24:34.660
Does that make sense?

00:24:34.660 --> 00:24:35.140
Yeah, yeah.

00:24:35.300 --> 00:24:36.220
That's pretty interesting.

00:24:36.220 --> 00:24:38.060
You can only assign to them once.

00:24:38.060 --> 00:24:38.380
Okay.

00:24:38.380 --> 00:24:39.760
Pretty interesting.

00:24:39.760 --> 00:24:42.520
And then memory management, how did that work, actually?

00:24:42.520 --> 00:24:48.120
Because everything was on the stack, for the most part, things basically just took care of themselves.

00:24:48.120 --> 00:24:51.320
I didn't have to write a garbage collector, thankfully.

00:24:51.320 --> 00:24:57.120
But there was one particular instance that was really awkward.

00:24:57.840 --> 00:25:08.820
So if you are making a function call and you want to return something that can't fit into a register.

00:25:08.820 --> 00:25:17.360
So say, for example, you have an array, or you have a function call that populates an array, and then you want it to return the array to the original caller.

00:25:17.820 --> 00:25:22.280
The problem there is that you can no longer save that on the stack.

00:25:22.280 --> 00:25:26.920
Because as anyone who's ever programmed in C or C++ knows, it'll go out of scope.

00:25:26.920 --> 00:25:32.340
So the solution there is either, oh, do I have to keep track of scopes now?

00:25:32.340 --> 00:25:34.300
Do I actually have to write a garbage collector?

00:25:34.460 --> 00:25:40.660
Do I have to reinvent memory management in Python myself in two months?

00:25:40.660 --> 00:25:45.240
And the answer to that was, okay, no, that doesn't make sense either.

00:25:45.240 --> 00:25:56.760
So what ended up happening is I would just move the data that you put in your array onto the heap temporarily and then pass back a pointer to it.

00:25:56.920 --> 00:26:10.860
And then either copy it into the stack again and free it, or just chalk that up to a memory leak and let the stuff that you return from a function just call it a memory leak.

00:26:10.860 --> 00:26:13.940
So that's sort of a lose-lose situation.

00:26:13.940 --> 00:26:18.120
It was definitely one of those things that if I had had more time, I would have liked to dig into that.

00:26:18.120 --> 00:26:24.440
And dynamically reassigning variables to two parts of the compiler, I feel like, are unfinished.

00:26:24.440 --> 00:26:25.280
Sure.

00:26:25.280 --> 00:26:25.560
Okay.

00:26:26.260 --> 00:26:29.920
Yeah, that's a big challenge in adding your own garbage collector.

00:26:29.920 --> 00:26:31.240
That sounds like a lot of work.

00:26:31.240 --> 00:26:34.640
Sounds like it could be really fun, but it sounds like a different thesis.

00:26:34.640 --> 00:26:35.800
Exactly.

00:26:35.800 --> 00:26:37.520
It's not my problem right now.

00:26:37.520 --> 00:26:37.760
Okay.

00:26:37.760 --> 00:26:38.420
Interesting.

00:26:38.420 --> 00:26:40.540
So how was the performance?

00:26:40.540 --> 00:26:48.060
Say, like, one of the options was I could write my code in C++ and I could give it to Tupleware, or I could write it in Python and give it to Tupleware.

00:26:48.060 --> 00:26:50.320
What was the trade-off there in terms of performance?

00:26:50.320 --> 00:26:52.400
Was it huge or pretty close?

00:26:52.400 --> 00:26:58.220
So in terms of performance, I actually do have the numbers that I ran that I can dig up.

00:26:58.220 --> 00:27:07.840
But the bottom line was that the LLVM compiler itself was much faster.

00:27:08.000 --> 00:27:29.900
If you take C++ and you compile it down to LLVM IR using a compiler that I think is written in LLVM IR itself, or at least in C or C++, compared to a compiler that is written in pure Python that takes in Python code, does all the, builds the syntax tree, does the parsing, all the semantic error analysis, that kind of thing.

00:27:29.900 --> 00:27:35.720
It takes longer, but it doesn't take an order of magnitude longer.

00:27:35.720 --> 00:28:01.380
So if you're running ML algorithms on huge data sets, having the compilation of your algorithm, which only happens once, if it takes one second or if it takes five seconds, it doesn't really matter because we're talking about hours and hours of work.

00:28:01.380 --> 00:28:08.140
So it's definitely slower, but for what the project needed, it wasn't inhibitively slower.

00:28:08.140 --> 00:28:08.860
Yeah, yeah, sure.

00:28:08.860 --> 00:28:13.560
And for the execution speed, like would the algorithms run about the same or was it really different?

00:28:13.960 --> 00:28:20.480
So the algorithms itself, once they got down to LLVM IR, pretty much ran the same.

00:28:20.480 --> 00:28:22.620
There were a couple interesting cases there.

00:28:22.900 --> 00:28:40.640
So what actually happens when the code gets put into LLVM IR and is then shipped to the other half of the distributed system where it gets actually compiled, analyzed, and run, is that is all handled by LLVM and C++.

00:28:40.640 --> 00:28:42.820
There's no Python involved in that.

00:28:43.280 --> 00:28:49.420
And so the optimization passes that the LLVM IR compilers actually do are incredibly powerful.

00:28:50.240 --> 00:29:04.020
So the reason that there's not a lot of optimization happening in the compiler itself, I mean the compiler from Python to LLVM IR, is that it's pretty much going to get squashed no matter what with the LLVM passes themselves.

00:29:04.020 --> 00:29:11.920
So that was a huge benefit because I didn't really have to sweat optimizations, which is another huge part of compiler writing.

00:29:12.140 --> 00:29:12.720
Yeah, that's awesome.

00:29:12.720 --> 00:29:17.420
Just let LLVM do its analysis on the intermediate representation, yeah?

00:29:17.420 --> 00:29:20.060
Yeah, I mean it's a huge selling point of LLVM IR.

00:29:20.060 --> 00:29:25.800
But there is some interesting stuff about optimizing function calls.

00:29:25.800 --> 00:29:31.760
And basically if you have a recursive call, it becomes a lot more difficult.

00:29:31.760 --> 00:29:45.360
I basically discovered that the LLVM IR compiler is not as good at unrolling these recursive calls as it is when you give it Python code because there's sort of, if you're doing...

00:29:45.360 --> 00:29:47.920
It just basically doesn't like the recursion so much, huh?

00:29:47.920 --> 00:30:00.440
Yeah, there's some things that you have to do in Python that you don't have to do in C++ because it's closer to the end result that ends up tripping up the optimizer.

00:30:00.440 --> 00:30:06.760
And so for an algorithm that involves recursion, it will actually perform slower.

00:30:06.760 --> 00:30:07.720
Okay, interesting.

00:30:08.500 --> 00:30:21.180
Yeah, so this sounds like a really cool project if you have something super focused like Tupleware where you can take a really small subset of Python and execute it against that system, right?

00:30:21.180 --> 00:30:25.000
There's a bunch of different implementations or runtimes out there.

00:30:25.000 --> 00:30:29.980
So we've got things like IronPython and Jython that try to take a different take on Python.

00:30:29.980 --> 00:30:34.280
There's PyPy, there's Pigeon, there's Cython, and Numba.

00:30:35.180 --> 00:30:42.060
It sounds like you're much closer to something like Numba with this project than you would be, say, with Cython.

00:30:42.060 --> 00:30:52.820
Yeah, I think that a lot of the sort of the line in the sand that gets drawn between projects is everybody is trying to get Python code to run really, really fast.

00:30:52.820 --> 00:30:59.260
But the way that LLVM IR actually factors into it can vary a lot between projects.

00:30:59.260 --> 00:31:04.720
So the goal of Numba is to run your Python code super fast.

00:31:04.720 --> 00:31:10.400
LLVM IR is just one step in what is, I think, a six-step process.

00:31:10.400 --> 00:31:19.960
And there's not actually, there wasn't actually a way to extract the LLVM IR directly from Numba, which has changed now.

00:31:19.960 --> 00:31:23.160
But when I initially wrote it, they didn't have that ability.

00:31:23.660 --> 00:31:35.860
So I would have had to basically go in and pick and choose bits from their code and then move it into a separate project because there was no elegant way to really pull it out.

00:31:36.420 --> 00:31:48.140
Another huge difference is that a lot of these compilers are JITs, which work great for what they're trying to do and generally work faster.

00:31:48.140 --> 00:31:50.800
But the thing about JITs is that they're lazy.

00:31:51.580 --> 00:31:57.580
And if you have a lazy compiler, it won't actually compile anything unless it's run.

00:31:57.580 --> 00:32:02.200
But for Tupleware, we're completely unconcerned with running the code.

00:32:02.200 --> 00:32:07.480
We just want to compile it and then we want to take that compiled code and pass it off to somebody else.

00:32:07.480 --> 00:32:07.860
I see.

00:32:07.860 --> 00:32:14.340
So that's basically part of the mechanism for deploying to this distributed cluster, right?

00:32:14.340 --> 00:32:16.220
Because you've got to give it the executable code.

00:32:16.220 --> 00:32:16.660
I see.

00:32:16.660 --> 00:32:22.080
So sort of ahead of the time JIT compilation would be as close as you could get or something.

00:32:22.080 --> 00:32:26.920
I did some performance comparisons between Numba and PyLVM.

00:32:27.080 --> 00:32:33.360
But the problem there is that there's no way to actually run code using my compiler.

00:32:33.360 --> 00:32:37.520
But you have to run the code in order to compile stuff with Numba.

00:32:37.520 --> 00:32:48.760
So if you're doing benchmarking, the actual cost of the algorithm itself, it doesn't negate the data, but it makes it a pretty big asterisk.

00:32:48.760 --> 00:32:51.980
Like, by the way, we also had to run the algorithm.

00:32:51.980 --> 00:32:53.040
Yeah, sure.

00:32:53.040 --> 00:32:55.400
So it's hard to compare apples to apples.

00:32:55.400 --> 00:32:55.880
Yeah.

00:32:55.880 --> 00:32:56.520
Okay.

00:32:56.880 --> 00:33:02.540
So I guess maybe we should kind of wrap it up on PyLVM and talk about your MongoDB stuff.

00:33:02.540 --> 00:33:05.200
But what's the two quick questions?

00:33:05.200 --> 00:33:09.340
Is this a Python 2 or Python 3 project or both?

00:33:09.340 --> 00:33:13.460
This is Python 2.7 is what I wrote it in.

00:33:13.460 --> 00:33:13.980
Yeah, sure.

00:33:13.980 --> 00:33:15.200
Okay, cool.

00:33:15.200 --> 00:33:18.380
And what's the future for this project?

00:33:18.380 --> 00:33:23.560
Do you know if anyone's picking it up or, you know, people out there listening, if it sounds interesting, you can pick it up.

00:33:23.560 --> 00:33:24.300
It's on GitHub, right?

00:33:24.300 --> 00:33:25.540
Yes, it is on GitHub.

00:33:25.540 --> 00:33:30.640
I would really recommend people to look at the project and to contribute.

00:33:30.640 --> 00:33:35.300
But that is also from like a curiosity slash selfish interest.

00:33:35.300 --> 00:33:44.920
I think that if you're going to do it, there are a lot of design decisions that were made in the interest of this specific project.

00:33:45.560 --> 00:33:47.940
And so if it matches your use case, that's excellent.

00:33:47.940 --> 00:33:57.820
And so if it matches your use case, because there are so many variables, I recommend just writing one because it's probably one of my favorite projects I've ever worked on.

00:33:58.060 --> 00:34:04.820
Just because it's such a neat, like a very well defined and very satisfying problem to solve.

00:34:04.820 --> 00:34:05.080
Nice.

00:34:05.080 --> 00:34:10.640
Do you feel like you understand how a lot of these compilers and execution fits together better now?

00:34:10.640 --> 00:34:18.200
Yeah, I think it's probably the best learning project anyone could have is to actually understand what goes on under the hood of the language they use.

00:34:18.880 --> 00:34:26.040
It also made me much, much better at writing optimizations work.

00:34:26.040 --> 00:34:26.360
Okay.

00:34:26.360 --> 00:34:27.240
Yeah, very cool.

00:34:27.240 --> 00:34:28.400
All right.

00:34:28.400 --> 00:34:32.280
So let's talk about this, what you're up to these days at MongoDB.

00:34:32.280 --> 00:34:34.040
And you said you'd worked on PyMongo.

00:34:34.040 --> 00:34:37.360
And just for everyone listening, like that's the primary driver.

00:34:37.360 --> 00:34:43.440
The primary way to speak to MongoDB is to, you know, pip install PyMongo and import it.

00:34:43.440 --> 00:34:44.920
And then you just start talking.

00:34:44.920 --> 00:34:48.440
And basically the data exchange is dictionaries, right?

00:34:48.520 --> 00:34:55.380
You write a prototypical query sort of thing as a dictionary and you get back rows, which are documents in the forms of dictionaries, right?

00:34:55.380 --> 00:34:56.380
Yes, that's correct.

00:34:56.380 --> 00:34:59.520
So you don't actually have to get dictionaries anymore.

00:34:59.520 --> 00:35:04.320
For a long time, PyMongo would just automatically read your data into dictionaries.

00:35:04.320 --> 00:35:07.860
But now you can actually get raw BSON out of the driver.

00:35:07.860 --> 00:35:10.620
And that opens up a lot of doors for what you can do with it.

00:35:10.620 --> 00:35:10.820
Right.

00:35:10.820 --> 00:35:18.380
So BSON is binary JSON, which is the actual in-memory on-the-wire representation that you get talking to MongoDB.

00:35:19.140 --> 00:35:27.420
You had a cool talk about something called Monary, which is kind of getting superseded by the project that you're working on now.

00:35:27.420 --> 00:35:35.780
But you had some interesting performance numbers about getting dictionaries back or just in terms of analysis in general.

00:35:35.780 --> 00:35:37.180
Forget the database for a minute.

00:35:37.180 --> 00:35:42.320
Like working with dictionaries versus working with lists versus something like NumPy, right?

00:35:42.320 --> 00:35:42.740
Yes.

00:35:43.280 --> 00:35:59.700
So that was pretty enlightening for me as a relatively new Python programmer to realize that Python dictionaries, which I considered was kind of the most basic way of storing data in Python, was actually pretty slow.

00:36:00.040 --> 00:36:10.800
And compared to like ND arrays, which are C-style arrays that come with the NumPy package versus just something like a list, they are significantly slower.

00:36:10.800 --> 00:36:11.160
Yeah.

00:36:11.160 --> 00:36:12.020
You had some cool numbers.

00:36:12.020 --> 00:36:18.440
You said something like for a certain algorithm, working with a bunch of Python dictionaries, you could do like 12 million a second.

00:36:18.440 --> 00:36:18.880
Yes.

00:36:18.960 --> 00:36:22.980
And with lists, it was close to 10 times as much, 110 million a second.

00:36:22.980 --> 00:36:25.340
And with NumPy, you could do 500 million a second.

00:36:25.340 --> 00:36:25.840
Yes.

00:36:25.840 --> 00:36:35.600
And that's just a really simple like take a bunch of data that's in this form and just add them all together or like make pairs or something like that.

00:36:35.600 --> 00:36:36.020
Right, right.

00:36:36.020 --> 00:36:38.060
Like basically read through it or something to that effect.

00:36:38.060 --> 00:36:38.980
Exactly.

00:36:39.400 --> 00:36:39.640
Okay.

00:36:39.640 --> 00:36:49.900
And so you said, look, the way with the BSON capability notwithstanding, like basically the way that PyMongo works is you do a query, you get a bunch of dictionaries back in Python.

00:36:49.900 --> 00:36:57.000
But if you're doing data science or something computational, you probably want to work in, say, NumPy, right?

00:36:57.000 --> 00:37:00.600
But the workflow would be I make a query in my Python layer.

00:37:00.600 --> 00:37:01.400
It goes to Mongo.

00:37:01.400 --> 00:37:03.160
That comes back across the wire.

00:37:03.160 --> 00:37:08.800
It gets turned into Python dictionaries and then serialized back in down in the C layer into NumPy.

00:37:08.900 --> 00:37:10.020
And that was a problem, right?

00:37:10.020 --> 00:37:20.840
Yes, that's a huge problem because you're taking something that is pretty fast, namely MongoDB, and you have something on the other end of your line, which is also really fast, NumPy.

00:37:20.840 --> 00:37:24.360
And then you have this bottleneck, which is Python dictionaries.

00:37:24.360 --> 00:37:36.020
And it's kind of a shame that there hasn't been more stuff related to this until recently because MongoDB is an incredibly powerful database and it's very easy to use.

00:37:36.400 --> 00:37:40.980
And NumPy also has a selling point that it's extremely powerful and pretty easy to use.

00:37:40.980 --> 00:37:48.980
So you'd think that for a lot of data scientists or for people who don't love coding in MATLAB, they would want to put these two tools together.

00:37:48.980 --> 00:38:01.500
But MongoDB hasn't been used in this context super often because of the limitation that in order to get the data out, you have to put it through this kind of clunky data structure before getting it back into your super fast arrays.

00:38:01.500 --> 00:38:01.940
Right.

00:38:01.940 --> 00:38:12.220
So you said basically with some tests you did, like going through PyMongo, you could read about from MongoDB through Python into NumPy about 150,000 documents a second.

00:38:12.780 --> 00:38:24.840
And there's this other project called Monary that you were talking about when I saw this presentation where it basically says, let's stay down in the C layer the entire time.

00:38:24.840 --> 00:38:30.780
And even though we're calling it from a Python app, it sort of connects it directly to NumPy, right?

00:38:31.120 --> 00:38:39.620
Exactly. So now we are taking this raw BSON format and basically moving it directly into C style arrays.

00:38:39.620 --> 00:38:47.160
So you have two things that are in sort of natural machine order and you no longer have to take it out of that.

00:38:47.160 --> 00:38:50.540
Nice. And that's something like 10 times faster, right? At least it was.

00:38:51.000 --> 00:38:56.900
Yeah, it was 1.7 million reads per second compared to 150,000.

00:38:56.900 --> 00:38:58.380
That's a big difference. You might.

00:38:58.380 --> 00:39:07.460
That's like a difference between going and getting a cup of coffee while your algorithm runs on your data versus going to lunch or something.

00:39:07.460 --> 00:39:14.880
Yes, exactly. And so it's really exciting to me that we can leverage these two really awesome technologies.

00:39:14.880 --> 00:39:19.100
And Monary itself is actually not under active development anymore.

00:39:19.540 --> 00:39:22.360
Monary is a project that actually was community based.

00:39:22.360 --> 00:39:33.160
It was started by somebody who did not work at MongoDB, David Beach, who basically wrote it because he was sick of having to lose so much time to Python dictionaries.

00:39:33.160 --> 00:39:48.600
And so now we are writing a codec that is 100% in C that takes this raw BSON, which before, I think maybe a year or two ago, you couldn't actually get raw BSON from PyMongo, but now you can.

00:39:49.120 --> 00:39:58.960
So now you can take this raw BSON and you can have this super lightweight package that just converts it directly into NumPy and you are good to go.

00:39:58.960 --> 00:40:00.800
That's really cool. So what's this thing called now?

00:40:00.800 --> 00:40:05.220
So this is called BSON NumPy, which is a deeply uncreative name.

00:40:05.220 --> 00:40:09.020
And I am definitely open to alternatives, but it is descriptive.

00:40:10.080 --> 00:40:11.920
Basically, it is in beta.

00:40:11.920 --> 00:40:20.960
So maybe closer to alpha because we stopped working on it about two weeks ago and we released, we actually released about two weeks ago.

00:40:20.960 --> 00:40:29.360
So it's the project of both me and a Jesse Giryu Davis, who is my coworker who you spoke to twice, I think.

00:40:29.360 --> 00:40:30.920
Yeah, yeah. Jesse's a friend of the show.

00:40:30.920 --> 00:40:31.860
So hello, Jesse.

00:40:31.860 --> 00:40:33.680
And nice work on this project as well.

00:40:33.800 --> 00:40:36.520
We are both really eager to hear people's feedback.

00:40:36.520 --> 00:40:43.140
We really want MongoDB to become more useful for data scientists.

00:40:43.140 --> 00:40:46.680
And we think that this is really the tool that is going to make it happen.

00:40:46.920 --> 00:40:48.640
Yeah, it seems like a really great idea.

00:40:48.640 --> 00:40:54.080
Just we'll skip the serialization where it's slow and just flow the data straight into NumPy.

00:40:54.080 --> 00:41:08.700
This portion of Talk Python is brought to you by Hired.

00:41:08.700 --> 00:41:11.720
Hired is the platform for top Python developer jobs.

00:41:11.720 --> 00:41:16.520
Create your profile and instantly get access to 3,500 companies who will work to compete with you.

00:41:16.860 --> 00:41:19.380
Take it from one of Hired's users who recently got a job and said,

00:41:19.380 --> 00:41:24.680
I had my first offer on Thursday after going live on Monday and I ended up getting eight offers in total.

00:41:24.680 --> 00:41:28.140
I've worked with recruiters in the past, but they've always been pretty hit and miss.

00:41:28.140 --> 00:41:30.980
I tried LinkedIn, but I found Hired to be the best.

00:41:30.980 --> 00:41:33.080
I really like knowing the salary up front.

00:41:33.080 --> 00:41:35.440
Privacy was also a huge seller for me.

00:41:35.440 --> 00:41:37.120
Sounds awesome, doesn't it?

00:41:37.120 --> 00:41:39.140
Well, wait until you hear about the sign-in bonus.

00:41:39.140 --> 00:41:42.560
Everyone who accepts a job from Hired gets $1,000 sign-in bonus.

00:41:42.560 --> 00:41:45.220
And as Talk Python listeners, it gets way sweeter.

00:41:45.820 --> 00:41:50.440
Use the link Hired.com slash Talk Python To Me and Hired will double the signing bonus to $2,000.

00:41:50.440 --> 00:41:52.240
Opportunities knocking.

00:41:52.240 --> 00:41:56.000
Visit Hired.com slash Talk Python To Me and answer the door.

00:42:03.000 --> 00:42:05.640
I guess people can just go to MongoDB and find it.

00:42:05.640 --> 00:42:07.180
How do they learn more about this project?

00:42:07.180 --> 00:42:11.180
Because it's in its very, very first iteration.

00:42:11.180 --> 00:42:12.780
It's not...

00:42:12.780 --> 00:42:17.080
We released it on bsunnumpy.readthedocs.io.

00:42:17.080 --> 00:42:21.040
And you can find it on Read the Docs.

00:42:21.680 --> 00:42:22.860
Basically, we...

00:42:22.860 --> 00:42:29.700
Neither myself nor Jesse are data scientists, but we're both super familiar with Python and

00:42:29.700 --> 00:42:31.200
we're super familiar with MongoDB.

00:42:31.200 --> 00:42:37.060
But that does leave a lot of open questions that I wouldn't necessarily know how to answer.

00:42:37.060 --> 00:42:43.120
So I'm not sure what the most common data type for NumPy would be.

00:42:43.240 --> 00:42:48.660
I mean, there are all these really cool, complex data types, but for all I know, nobody ever

00:42:48.660 --> 00:42:49.200
uses them.

00:42:49.200 --> 00:42:53.620
So what I really want is to be able to reach out to the community and have people tell me

00:42:53.620 --> 00:42:54.260
what they need.

00:42:54.260 --> 00:42:59.100
Because a lot of the great features at MongoDB pretty much came out because somebody had a

00:42:59.100 --> 00:43:00.120
need and they asked for it.

00:43:00.120 --> 00:43:02.280
So we wrote them and then it became a huge selling point.

00:43:02.800 --> 00:43:07.800
So what I want is just to hear from what people who would use these technologies really want.

00:43:07.800 --> 00:43:08.400
Yeah, absolutely.

00:43:08.400 --> 00:43:09.920
So people can check it out.

00:43:09.920 --> 00:43:12.780
It's bsun-numpy on PyPI.

00:43:12.780 --> 00:43:14.940
And also I'll put the link in the show notes.

00:43:14.940 --> 00:43:20.600
So check it out and give both of you guys feedback on like, hey, this looks really cool, but it

00:43:20.600 --> 00:43:22.180
doesn't do whatever, right?

00:43:22.180 --> 00:43:23.060
Exactly.

00:43:23.060 --> 00:43:23.640
Nice.

00:43:23.640 --> 00:43:29.160
You also had in your presentation about Monary, it's still kind of the same type of question

00:43:29.160 --> 00:43:32.640
and answer type thing that you get the same kind of analysis you can do.

00:43:32.640 --> 00:43:39.360
with Bison-numpy, you had an interesting analysis you did of taxi cabs and Times Square, right?

00:43:39.360 --> 00:43:41.320
Do you want to tell people that story?

00:43:41.320 --> 00:43:41.880
Sure.

00:43:41.880 --> 00:43:49.140
So when I first joined MongoDB, I was working out of the headquarters, which is in Times Square

00:43:49.140 --> 00:43:50.020
in New York City.

00:43:50.020 --> 00:43:55.480
As a side note, right now I work out of Stockholm, Sweden, which I prefer quite a bit more to

00:43:55.480 --> 00:43:56.940
fighting my way through the crowds.

00:43:56.940 --> 00:44:02.480
But I was struck one morning when I was trying to think of sort of a nice data set to

00:44:02.480 --> 00:44:07.900
sort of show examples of how much faster Monary would be than Pymongo.

00:44:07.900 --> 00:44:14.800
I just was so sick of fighting through the crowds and fighting to get to the train station.

00:44:14.800 --> 00:44:16.420
And I kind of figured like, wow, what is...

00:44:16.420 --> 00:44:17.780
Why is there a Batman in my way?

00:44:17.780 --> 00:44:20.020
What's going on in this place?

00:44:20.020 --> 00:44:20.680
Exactly.

00:44:20.680 --> 00:44:23.900
It's like, how can I get out of here as fast as possible?

00:44:23.900 --> 00:44:25.220
Like, where can I go?

00:44:25.220 --> 00:44:26.140
How can I do it?

00:44:26.480 --> 00:44:33.660
So I also had access to the taxi data for all of New York City, which is freely available

00:44:33.660 --> 00:44:34.080
online.

00:44:34.080 --> 00:44:36.180
And it's a really interesting data set.

00:44:36.180 --> 00:44:43.180
And so I basically just took that data set and I looked at all the rides that both started

00:44:43.180 --> 00:44:44.280
and ended in Times Square.

00:44:44.280 --> 00:44:47.560
Because I kind of just wanted to know, like, where are people going?

00:44:47.560 --> 00:44:48.860
Like, what's the rush?

00:44:48.860 --> 00:44:50.200
Where are you coming from?

00:44:50.200 --> 00:44:51.440
Why are you coming here?

00:44:52.160 --> 00:44:54.620
Of all the places in New York City to be, why Times Square?

00:44:54.620 --> 00:44:55.060
Okay.

00:44:55.060 --> 00:44:55.320
Yeah.

00:44:55.320 --> 00:44:56.240
That's really cool.

00:44:56.240 --> 00:45:01.840
And you found, you had a really great bunch of visualizations that came out of MATLAB.

00:45:01.840 --> 00:45:06.160
And I'll put the link to that video up there because you have, you know, some really great

00:45:06.160 --> 00:45:10.240
maps and like bars living on flat maps and all kinds of stuff.

00:45:10.240 --> 00:45:10.960
I thought that was great.

00:45:10.960 --> 00:45:11.200
Yeah.

00:45:11.200 --> 00:45:12.520
It's like, it's MATPLOTLIB.

00:45:12.520 --> 00:45:13.040
Oh, yeah, yeah.

00:45:13.040 --> 00:45:13.300
Sorry.

00:45:13.300 --> 00:45:14.640
I keep, I don't know why I said MATLAB.

00:45:14.640 --> 00:45:16.920
MATLAB because we were talking about, yeah, yeah.

00:45:16.920 --> 00:45:18.320
MATPLOTLIB is what I had in mind.

00:45:18.320 --> 00:45:18.580
Yeah.

00:45:18.840 --> 00:45:23.800
But yeah, no, I mean, the point of those diagrams were just basically to show off how

00:45:23.800 --> 00:45:31.380
cool NumPy can be and how powerful MATPLOTLIB can be for creating really beautiful visualizations.

00:45:31.380 --> 00:45:35.400
And I know that there are a lot of different visualization tools out there.

00:45:35.400 --> 00:45:41.680
But in terms of selling Python and providing a way for data scientists to use both MongoDB

00:45:41.680 --> 00:45:44.940
and NumPy, I felt like it was a pretty good sell.

00:45:44.940 --> 00:45:45.620
Yeah, that's cool.

00:45:45.740 --> 00:45:50.060
And you also were able to leverage, I think the interesting linkage there is you were able

00:45:50.060 --> 00:45:57.760
to put a huge amount of data in Mongo and apply a geospatial index to it and then use that

00:45:57.760 --> 00:46:01.600
in your queries, but then also do the analysis with NumPy, right?

00:46:01.600 --> 00:46:02.240
Mm-hmm.

00:46:02.240 --> 00:46:02.880
Exactly.

00:46:03.060 --> 00:46:08.900
So you are taking advantage of these geo queries, which are really simple, really easy to use,

00:46:08.900 --> 00:46:10.860
but also very fast.

00:46:10.860 --> 00:46:17.240
And you are taking advantage of Python, NumPy specifically, where you have really, really

00:46:17.240 --> 00:46:20.560
fast analysis, but a lot of these algorithms are actually written for you.

00:46:20.560 --> 00:46:26.120
So you can use a lot of these scientific Python packages that have done what I consider the hard

00:46:26.120 --> 00:46:27.660
work for you completely.

00:46:27.660 --> 00:46:28.180
Yeah.

00:46:28.180 --> 00:46:28.660
Yeah.

00:46:28.660 --> 00:46:32.540
It seems like just take the tools, click them together, and you get some really great

00:46:32.540 --> 00:46:33.140
analysis.

00:46:33.140 --> 00:46:33.740
That's awesome.

00:46:33.740 --> 00:46:34.620
Yeah.

00:46:34.620 --> 00:46:40.180
So I'm looking forward to your presentation where you talk about your Bison NumPy version.

00:46:40.180 --> 00:46:41.480
Yeah.

00:46:41.480 --> 00:46:47.260
Well, I'll have to find something better, some better visualizations and some, you know, I

00:46:47.260 --> 00:46:49.720
don't have as many things to complain about now that I live in Stockholm.

00:46:49.720 --> 00:46:51.540
So I have to...

00:46:51.540 --> 00:46:51.940
Yeah.

00:46:51.940 --> 00:46:54.240
You can do something with snow or winter or something.

00:46:54.240 --> 00:46:54.660
Who knows?

00:46:54.660 --> 00:46:55.220
Yeah.

00:46:55.220 --> 00:46:55.860
The darkness.

00:46:55.860 --> 00:46:56.840
Yes, exactly.

00:46:56.840 --> 00:46:58.120
Here's the light analysis.

00:46:58.120 --> 00:46:59.300
All right.

00:46:59.300 --> 00:46:59.600
Cool.

00:46:59.600 --> 00:47:01.260
So I guess let's go ahead and leave it there.

00:47:01.260 --> 00:47:02.600
That sounds like a great project.

00:47:02.600 --> 00:47:07.420
And so if you're out there working with MongoDB and ultimately your data ends up in NumPy,

00:47:07.420 --> 00:47:10.260
you know, maybe skip the dictionaries is the message, right?

00:47:10.260 --> 00:47:10.880
If you can.

00:47:10.880 --> 00:47:11.800
Yeah, exactly.

00:47:11.800 --> 00:47:17.420
And especially for people who use scientific Python and have never considered MongoDB a viable

00:47:17.420 --> 00:47:19.760
option, I think that is going to change.

00:47:19.860 --> 00:47:25.280
And I think that we are going to be able to write programs that take advantage of all this

00:47:25.280 --> 00:47:27.500
cool work that has already been done.

00:47:27.500 --> 00:47:27.820
Yeah.

00:47:27.820 --> 00:47:33.480
And I think having it officially part of the product, the library, gives it a little more,

00:47:33.480 --> 00:47:35.860
probably gives people more confidence to build upon it.

00:47:35.860 --> 00:47:36.520
Yeah, exactly.

00:47:36.520 --> 00:47:41.600
And I am personally just so excited because this is the first project that I've really had

00:47:41.600 --> 00:47:42.680
ownership over.

00:47:42.680 --> 00:47:45.880
I contributed a lot to PyMongo, but it wasn't my project.

00:47:45.880 --> 00:47:50.440
Well, I feel very proud of what I've done for this particular project.

00:47:50.440 --> 00:47:51.680
It sounds really, really cool.

00:47:51.680 --> 00:47:52.780
So, so nice work.

00:47:52.780 --> 00:47:57.020
Let me ask you a couple of questions before we get out of here, as I always do.

00:47:57.020 --> 00:48:01.820
First one, if you're going to write some Python code, maybe not C code, if you write some Python

00:48:01.820 --> 00:48:03.380
code, what editor do you open up?

00:48:03.520 --> 00:48:04.720
So I use PyCharm.

00:48:04.720 --> 00:48:13.160
And it's funny that you mentioned C code because I got a perpetual license for JetBrains.

00:48:13.160 --> 00:48:20.740
And I love that I can write, I can write Node, I can write Ruby, I can write Python, I can write C.

00:48:20.740 --> 00:48:22.660
And all my shortcuts are the same.

00:48:22.660 --> 00:48:27.800
I was a Vim user before Jesse convinced me to switch over to PyCharm.

00:48:27.800 --> 00:48:30.040
And now I'm like completely converted.

00:48:30.040 --> 00:48:34.200
I can have all my Vim shortcuts and I can also have all the power of a really great IDE.

00:48:34.200 --> 00:48:39.920
So I really recommend the JetBrains libraries because it's really nice to not have to switch

00:48:39.920 --> 00:48:45.960
between IDEs, between like SeaLion, PyCharm, Ruby9, WebStorm.

00:48:45.960 --> 00:48:47.720
You know, it's the same environment.

00:48:47.720 --> 00:48:50.580
So I feel like I don't have to lose any time to relearning stuff.

00:48:50.720 --> 00:48:51.840
Yeah, that's really cool.

00:48:51.840 --> 00:48:54.500
Jesse's a big fan of PyCharm and so am I.

00:48:54.500 --> 00:48:59.940
Just so people who are not familiar, basically there's this IntelliJ platform, which is kind

00:48:59.940 --> 00:49:02.420
of the IDE and then they plug in the language specific stuff.

00:49:02.420 --> 00:49:05.080
So like when you say it's all the same, it's like all kind of the same base.

00:49:05.080 --> 00:49:07.200
And like you said, like it behaves the same.

00:49:07.200 --> 00:49:08.160
It's pretty sweet.

00:49:08.160 --> 00:49:08.580
Yeah.

00:49:08.580 --> 00:49:09.220
Okay, cool.

00:49:09.220 --> 00:49:14.520
And we just this week passed 100,000 packages on PyPI.

00:49:14.520 --> 00:49:16.620
So that's a big milestone.

00:49:16.620 --> 00:49:17.180
Hooray.

00:49:17.180 --> 00:49:18.360
Wow.

00:49:18.360 --> 00:49:19.600
Yeah, no kidding.

00:49:19.600 --> 00:49:21.700
And so there's a ton of them out there.

00:49:21.700 --> 00:49:24.840
Do you have one that maybe like you think people don't necessarily know about?

00:49:24.840 --> 00:49:26.100
You're like, hey, you should check this out.

00:49:26.100 --> 00:49:28.260
BSON NumPy maybe?

00:49:28.260 --> 00:49:29.180
Yes.

00:49:29.180 --> 00:49:34.900
Is it too shamelessly self-promoting to say that you should download BSON NumPy and that's

00:49:34.900 --> 00:49:36.340
the best package on PyPI?

00:49:36.340 --> 00:49:38.280
It's one of the newer ones.

00:49:38.280 --> 00:49:39.120
That's a really good question.

00:49:39.120 --> 00:49:43.940
I guess I'll just go with that because I'm very utilitarian with my packages.

00:49:44.480 --> 00:49:47.300
I pretty much just keep them to a minimum.

00:49:47.300 --> 00:49:47.920
Okay.

00:49:47.920 --> 00:49:48.420
Yeah.

00:49:48.420 --> 00:49:48.880
Very cool.

00:49:48.880 --> 00:49:49.960
All right.

00:49:49.960 --> 00:49:50.120
Yeah.

00:49:50.120 --> 00:49:50.840
It's a great package.

00:49:50.840 --> 00:49:54.200
And you know, you guys need some people to try it out and actually do real data science

00:49:54.200 --> 00:49:54.500
with it.

00:49:54.500 --> 00:49:55.620
So that's great.

00:49:55.620 --> 00:49:56.500
Exactly.

00:49:56.500 --> 00:49:57.260
All right.

00:49:57.300 --> 00:49:58.300
So final call to action.

00:49:58.300 --> 00:50:00.860
What do you want people to check out or do?

00:50:00.860 --> 00:50:05.440
I would like people to check out BSON NumPy because I think it's a really cool project.

00:50:05.440 --> 00:50:12.160
I think that if anyone is interested in a really neat piece of code, they should look through

00:50:12.160 --> 00:50:17.680
the LLVM Py work, especially if you're either new to compilers, maybe new to Python, new to

00:50:17.680 --> 00:50:18.360
LLVM IR.

00:50:18.360 --> 00:50:24.500
It's a very nice sort of pet project and it's very self-contained and it is, in my opinion,

00:50:24.500 --> 00:50:25.480
pretty well documented.

00:50:25.480 --> 00:50:28.520
There's an entire 20-page thesis I wrote about it.

00:50:28.520 --> 00:50:30.820
So if you have questions, they're probably answered.

00:50:30.820 --> 00:50:34.760
I'm also always happy to answer by like email, Twitter, anything.

00:50:34.760 --> 00:50:38.080
So those are my two things I would love to see people do.

00:50:38.080 --> 00:50:38.600
All right.

00:50:38.600 --> 00:50:39.640
That sounds great.

00:50:39.640 --> 00:50:41.420
Ana, thank you so much for being on the show.

00:50:41.420 --> 00:50:42.840
It's been really interesting to talk to you.

00:50:42.840 --> 00:50:43.380
Yeah, of course.

00:50:43.380 --> 00:50:44.900
Thank you so much again for having me.

00:50:44.900 --> 00:50:45.140
Yeah.

00:50:45.140 --> 00:50:45.640
Thanks so much.

00:50:45.640 --> 00:50:45.960
Bye.

00:50:45.960 --> 00:50:46.280
Bye.

00:50:46.280 --> 00:50:50.760
This has been another episode of Talk Python To Me.

00:50:50.760 --> 00:50:56.880
Today's guest has been Ana Herlihy and this episode has been sponsored by Hired and Talk

00:50:56.880 --> 00:50:57.760
Python Training.

00:50:57.760 --> 00:51:00.640
Are you or a colleague trying to learn Python?

00:51:00.640 --> 00:51:05.320
Have you tried books and videos that just left you bored by covering topics point by point?

00:51:05.320 --> 00:51:10.640
Well, check out my online course, Python Jumpstart by Building 10 Apps at talkpython.fm

00:51:10.680 --> 00:51:13.940
slash course to experience a more engaging way to learn Python.

00:51:13.940 --> 00:51:18.720
And if you're looking for something a little more advanced, try my Write Pythonic code course

00:51:18.720 --> 00:51:21.280
at talkpython.fm/pythonic.

00:51:21.280 --> 00:51:24.640
Hired wants to help you find your next big thing.

00:51:24.640 --> 00:51:29.380
Visit Hired.com slash Talk Python To Me to get five or more offers with salary and equity

00:51:29.380 --> 00:51:33.140
presented right up front and a special listener signing bonus of $2,000.

00:51:35.020 --> 00:51:36.720
Be sure to subscribe to the show.

00:51:36.720 --> 00:51:38.920
Open your favorite podcatcher and search for Python.

00:51:38.920 --> 00:51:40.160
We should be right at the top.

00:51:40.160 --> 00:51:46.260
You can also find the iTunes feed at /itunes, Google Play feed at /play and direct

00:51:46.260 --> 00:51:49.440
RSS feed at /rss on talkpython.fm.

00:51:49.860 --> 00:51:54.560
Our theme music is Developers, Developers, Developers by Corey Smith, who goes by Smix.

00:51:54.560 --> 00:51:59.240
Corey just recently started selling his tracks on iTunes, so I recommend you check it out at

00:51:59.240 --> 00:52:01.240
 talkpython.fm/music.

00:52:01.240 --> 00:52:06.360
You can browse his tracks he has for sale on iTunes and listen to the full length version of the theme

00:52:06.360 --> 00:52:06.600
song.

00:52:06.600 --> 00:52:08.680
This is your host, Michael Kennedy.

00:52:08.680 --> 00:52:09.960
Thanks so much for listening.

00:52:09.960 --> 00:52:11.160
I really appreciate it.

00:52:11.160 --> 00:52:13.300
Smix, let's get out of here.

00:52:13.300 --> 00:52:34.740
I'll see you next time.

00:52:34.740 --> 00:52:35.580
I'll put it in.

00:52:35.580 --> 00:53:05.560
Thank you.

