I hope you're using Python 3 these days, because one of its powerful new features is type annotations.
These let you build and maintain large-scale Python projects with much more ease and confidence.
This episode, you'll meet Lucas Lenga, who helped migrate some very large Python projects.
We'll discuss how Python uses the concept of gradual typing to slowly expand the sections
of your Python code that are type-checked. This is Talk Python to Me, episode 151,
recorded January 31, 2018.
Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the
ecosystem, and the personalities. This is your host, Michael Kennedy. Follow me on Twitter,
where I'm @mkennedy. Keep up with the show and listen to past episodes at talkpython.fm,
and follow the show on Twitter via at talkpython. Lucas, welcome to Talk Python.
Hello.
It's great to be here with you again. We were just recently at PyCascades,
a nice little conference where you get to pretty much meet everyone who's there.
Oh, yeah. For me, it was a very personal trip since I used to live in Vancouver for quite a while.
Western culture doesn't really allow grown men to cry publicly, but shedding a tear all the time,
seeing all the places I used to visit and used to frequent. So yeah, it was a great event. I enjoyed it a lot.
Yeah. Vancouver's a wonderful city, right?
It is just the perfect size. It's not as big and scary as New York City, but it's perfectly urban,
like with proper public transport, unlike San Francisco. So yeah, it's one of my favorite
places on earth.
Is that where you are now?
Yes. I am now in the Bay Area. So yeah, I miss Vancouver terribly.
Yeah. The Northwest is a nice place indeed. Before we get into our topic of the day,
the type annotations and all the amazing stuff and your story of how you sort of applied this on a
large code base, let's just get started with your story. How'd you get into programming in Python?
Programming is sort of a memory that I have from pretty early childhood, since I remember the day
when my dad like brought me a Commodore 64. It was supposed to be like a surprise Christmas present,
but like the package was big enough and like he just wasn't able to just hide it properly. So I knew
I'm going to get it. Like that was wow. Like I very vividly remember the moment when I sort of realized
I'm getting a computer and even if you want to play games at that point, like you had to type in a bunch
of commands. So it was very welcoming to just try out doing something more. And I was pretty,
pretty young at this point. I must've been like six or seven. So most of what I did was just retyping
programs that were published in like computer magazines at that point. But that sort of instilled
in me this realization that this is not some dark magic that like normal people cannot do. Like to
the contrary, I felt like if only I had enough time, I could type like, you know, make any program like the
war games movies or the artificial intelligence or whatever, like, you know, that's right, the whopper core.
Yes, exactly. Kids are very, I don't know, imaginative that way. It's gotten me quite a while to actually get into real programming. Like I went through Pascal, I went through Java and whatnot, you know, during college.
And in autumn 2004, I was studying computing science at the university in Poland, and I had trouble with some courses I took. There was a particularly hard line for me, which was linear algebra.
And like, I was, in fact, like, you know, scared that I would be just let go, like I would not be able to pass exams that were just coming.
So a friend showed me some scripts he wrote in Ruby, in some linear algebra library to check like whether the results of the exercises that we're doing were, were correct, right? Like, so that sort of helped you solving homework assignments.
So I badly needed some reassurance that so I got excited about this. But for some reason, for some random reason, the Ruby installer just refused to work on my Windows XP box, like it just crashed midway, couldn't install Ruby.
So as a test was scheduled, like, you know, the very next day or so, like I started looking like, you know, pretty sort of nervously for alternatives.
And I literally typed Ruby alternative in Google. And that's how I found Python. And this instilled cleanly, I quickly found a functional like lin algebra library.
That was, I think, that was, I think, before NumPy, because what I was using at this point was numerical. So either way, like, I got hooked. Like, that was such a departure from Java and Pascal that I that I've known before.
Oh, yeah, that's this really interesting story. Just like, all right, forget it. Ruby's not working. What else? I got to take a test. And then, you know, you're like, wait a minute, this is pretty cool. This stuff. I'm pretty sure that must have been before NumPy.
Yeah, that's cool. Yeah. So one of the things that I know you didn't say that you were learning math for programming per se, but a lot of times people feel like, if you are a programmer, you have to know a lot of math.
They like, it's really interesting how little programming or how much little math we do as programmers, even though they're very similar, right?
Yeah. So that depends really on like, your particular niche and programming that you're into. Like, I was always coming from this background of like, building Legos, essentially, right?
So composing smaller pieces into bigger, like tools, right? So yes, like, I'm not one of those guys who come up with new, exciting algorithms that are like, are way more efficient than something else that was done before.
I'm mostly a reuser of things that were invented by other people. But in this sense, the complexity is something that like, I enjoy more than people that, you know, sort of program on, like in a notebook, essentially, right?
It's a very different way of programming. And I'm with you, I like sort of thinking of the big architecture, how all the services and database stuff fit together. It's really fun. So speaking of which, you work on a moderately large project right now, right? What do you do day to day?
I work for Facebook. When I started working there, like, almost four and a half years ago, what I did was I was a production engineer, which is essentially like a person managing complexity and making sure that like all the software written by, I don't want to say naive, but like the sort of feature focused software engineers actually runs at the scale that we needed to.
So I started with the cache infrastructure, then I moved to sort of automated remediation of alarms that we're getting and whatnot. But all of this was using Python. So with my core development background, I was always just like, you know, putting my nose into other people's problems to sort of try to help them like out with, you know, Python issues, especially if you know, you heard that Python sucks for some reason, you know, like,
try to not take it personally. But if like, somebody really is commenting negatively on something that you personally worked on, then you want to know why, right? So I was slowly sort of making this my job. And two years ago, we actually got a team formed around this idea. And now I lead this team, like as a tech lead, it's called Python Foundation. And it's essentially managing the runtime for both Facebook and Instagram.
That is such a cool thing. That sounds like such a fun job.
What we're doing is we're trying to actually make Python, which is one of the most popular languages at Facebook, probably in the top three to like, really feel like a first class language. So to do this, you cannot really stick to a ancient version of it, right? That's going to be actually end of life in two years. So one of the core sort of, I don't know, like, missions behind this team is to move the entire company off of Python, too, that we would like.
Sort of means we would like to move everything to Python three, but also like, if people move to other tools that sprung in the meantime, that work better, like we're fine with this, like, but we really want to get rid of Python two, as a thing, we managed to help Instagram move to Python three, which I think is a pretty big deal. It's over a million lines of code.
And we did this in the year of the biggest growth for Instagram, both user-wise and feature-wise.
This is when stories were launched, when ranked feed was launched.
And we didn't have any incidents.
We didn't go down while doing it.
So I think, if anything, that should be a very big reassurance to other people struggling with this now
that it is, in fact, possible to do.
And it is worthwhile.
Is this the same basic story that was told at the keynote at PyCon 2017 around Instagram upgrading?
Yes, I was actually in the team that worked on this.
So when the keynote was prepared, I was one of the reviewers of the keynote when Lisa and Hui were practicing runs for it.
So yeah, I've known the keynote almost by heart before it went live at PyCon.
And yeah, it was also a pretty personal story for us because we spent a pretty significant time and effort on it.
But it was totally worth it.
Oh, it was such an inspiring story.
And when I think of other companies and other people saying, well, my project's too complicated to move to Python 3.
We can't possibly do this.
It's too much effort.
I look at what they did on a single branch, switching Django versions majorly and switching Python versions.
That was just incredible.
It was awesome.
Yeah.
Pretty much, you just need to know what you want to do and how.
And I think the key to success there was the process that we took.
So pretty much, if you just have an idea to move to Python 3 and you just start randomly stabbing at it, you are more likely to fail.
But if you actually figure out, how are we going to do this?
Then it's a tractable problem.
You can absolutely pull it off.
And now with enough projects actually going through this transition, there is a lot of resources.
I like the keynote that you mentioned that other people can just sort of address and listen to, watch, to see what were the processes that actually worked out for a big project.
Yeah.
Could you maybe just give people the really high-level steps they went through?
So things like kept it on Python 2 in production, but started doing the testing in 2 and 3.
I think the steps were really nice.
Do you remember them?
We had to realize that we need to make all the code work on Python 2 and Python 3 at the same time.
Right?
So we embraced 6 as a library to actually write polyglot code inside Distillery, which is the big Instagram backend repository.
Once we had this, we pretty much had to start testing.
And to start testing, we whitelisted a small amount of unit tests that we knew are passing on Python 3.
And gradually, we were just extending this whitelist when those modules that were being unit tested were made compatible with both Python 2 and Python 3.
We're gradually just extending this whitelist.
What happened at some point is that this whitelist was big enough that we could switch to a blacklist.
So there were just some struggling, tricky places that we needed to address.
What I missed is, yeah, before we even started, we had to upgrade to a newer Django version because the one that we were using was written long before anybody thought of Python 3 compatibility.
So that was like a pre-requirement.
Fortunately, there was a version that supported Python 3.
And most of the other dependencies that we had were also already ported by the time we started doing our internal transition.
So pretty much the unit tests were important.
Once those unit tests were in good enough shape, we started pretty much running a Python 3 version on developer boxes.
So Instagram and I think most of Facebook doesn't really work on the laptops that you're getting.
Everybody is working on their assigned developer server that they have somewhere.
Mine is, for example, in North Carolina.
So pretty far from where I am.
But it doesn't really matter.
You just work on it.
It's sort of your computer.
You're working on a terminal anyway.
It doesn't matter where that console actually is.
So we switched people to just run on Python 3.
We did notify them that this is happening.
But for them, it should be.
It should be a no-op.
It should just work.
Obviously, it didn't for all of the cases.
But that's the thing.
If you treat Python 3 incompatibility as a bug that needs to approach to actually fixing it is quite different
than when you're just seeing it as an intractable problem that is unlikely to ever work.
Then you just complain and you sort of throw your hands up in the air and say, you know, I don't know what to do.
So when we said, like, hey, any bug that you see, we just need to fix it.
By then, we had a pretty extensive wiki page on, like, typical issues, how to solve those.
So once we were comfortable running the entire app on developer boxes on Python 3, we started shadow testing.
So with this sort of fleet that we have in production for Instagram, obviously, you can do A-B tests.
You can release stuff in one cluster of machines and not the other and whatnot.
And that was important.
But even if you have a smaller scale deployment, I think, like, you should never just release on a single server.
So you have some sort of load balancing.
You have some sort of way of, you know, releasing gradually.
So we just started minimally releasing Python 3 and seeing what happens, right?
So we saw some, like, tragic performance regressions.
And later we found out that either it was some library that was very poorly ported or something stupid that we did.
Or actually, like, actual problems.
Like, you know, because Python 3 behaves differently and we have to switch to do something a bit different.
And in the end, we cut down the memory usage of our Python processes by one third and cut down the CPU usage by 12%.
So for just switching a Python version, I think, like, in our scale, like, that was a worthwhile investment right there.
That's a really cool story.
And I think it definitely serves as a cool roadmap for people going forward.
And it's only going to get better, right?
Like, these new web frameworks and API frameworks are, there's a lot of powerful Python 3 only ones.
I'm thinking API star and some of the async enabled web frameworks that are only accessible to these newer platforms.
Actually, Instagram is now, like, looking at, like, marrying Django with async.io in ways where we can utilize too.
Like, this is not an easy problem to do because Django is just built around the idea of a single process for a request.
Whereas async.io is totally the opposite, using coroutines to concurrently serve many requests from.
But in the end, I think this is going to be a transition that is sort of radical because async.io is viral.
In the sense that to actually use it, you have to pretty much just give up using any blocking APIs.
Like, if you want to have native async.io, you have to switch to using coroutines and non-blocking APIs everywhere.
But the alternative is really just switching to Go or Rust or whatever else.
And this is what many teams are pondering, right?
Like, we actually want to have better performance.
Python doesn't give it to us.
So let's just switch to a totally different infra, to a totally different language that has a different set of compromises that, you know, they might not even fully realize before they start using a language fully.
So I think, you know, instead of burning all the bridges, we can burn some of the bridges and switch to async.io to actually enable performance that we haven't really seen much in Python.
And that was obviously twisted, but like it was a pretty sort of separate community for the longest time.
And I hope async.io is going to be more mainstream than that.
For sure.
And a lot of the libraries that are just out there, the packages, if, you know, it's very likely they standardize on async and await.
And so they would just plug into these existing things, right?
Like, it's definitely exciting.
And final question on this before we get to the official topic.
This is really, really interesting.
You mentioned being radical.
Do you happen to work with Jason Fried as well?
Is he on your team?
He gave such a great talk called Rules for Radicals, Changing the Culture of Python at Facebook.
He was actually one of the people that made me sort of stand up and try to make the Python situation better at Facebook.
He's been at the company two years longer than me.
So, like, he was always one of those guys that we were just working with, like, as a grassroots movement before I had a team that does this full time.
Which is why when my director asked me, like, who would you see on your team?
Like, Jason Fried was, like, the first name that I gave him.
Like, that's the right person to do this job.
He's just enough, like, of a mixture of, you know, being rational and, like, a fan of the language.
So I think, like, you have to be invested.
But at the same time, you have to recognize the limitations, right?
Behind the tech.
He just, like, hits the right sweet spot.
Yeah, that's cool.
Yeah.
So the reason I bring that up is I think his talk, which I'll link to, The Rules for Radicals, about changing the mindset to wanting Python 3, to making it sort of the default behavior within a large organization.
And then the keynote from Instagram is the concrete steps that you take to actually make that happen.
I think put those two together in any organization that's on Python 2 could pretty much find a roadmap there.
Yeah, we hope so.
Yeah, yeah, I do as well.
All right.
So speaking of Python 3, one of the really cool features, what was this, 3.5 when it came out, the type annotations?
Uh-huh.
All right.
So this is PEP484, right?
Tell people what, for those who don't know, what it is.
Starting with Python 3.0, we had a feature, a syntactic feature to apply annotations to function arguments and return values.
It was always envisioned by Guido to be fundament to build static typing for Python.
However, at the time, it was very unclear what that meant.
So he pretty much left this as an exercise to the reader to come up with a sensible syntax and a type checker for Python.
And there were a few toy attempts at this, but fundamentally nothing caught on.
And there was no big investment there.
Yeah, like you could do like a docstring type thing.
I mean, there's a couple of ways.
You could do like a type colon, and it works on some tooling and not others, right?
At this point, the annotation syntax was Python 3 only.
So yes, like people wanting to sort of formalize argument types for any reason were using docstrings or whatnot.
But we actually discovered that in this case, the docstrings were pretty much sort of best effort.
They were mostly meant for human readers and not machines to check.
So very often they were out of date or incomplete or didn't even quite parse because the syntax was correct.
And like all of this caused there to be some sort of, I don't know, like the adoption was very low of this idea.
So PyCharm had its own syntax for this.
Sphinx, the documentation generator, had some sort of syntax that it accepted.
Doxygen and other systems had its own again.
But none of these really settled on using the annotations that Python 3 had.
What didn't help is also like before Python 3.5, like the adoption of Python 3 was super low, right?
So nobody actually was using Python 3.
So nobody was thinking about using function annotations.
That pretty much meant your program is now Python 3 only.
And in this time, that was like around 2013, 2014.
Like that was a very radical idea.
Like most people would just not be ready for this yet.
So we were thinking about this and I, in particular, after joining Facebook, saw how much this changed the culture of like PHP to hack.
And how type annotations really made the code base so much better at Facebook.
We even extended this to JavaScript with Flow.
So for me, like having this syntactic feature that is not utilized in any way was just a call to action, right?
So when I found out that actually Guido is interested in pursuing this, I reached out to him and I drafted like Pep484.
And then we started working on this like more heavily, actually basing on work by Yuka Leto Stalo, who wrote a prototype Python interpreter that then became my Python type checker.
Yeah.
And until this day, that's like, that's the type checker that we're using for this.
So that's like this sort of ancient history in the project.
Like I visited Guido, like, you know, working on Dropbox at the time.
He visited the Facebook campus.
It's, that was like, I guess, four years ago or more now.
Yeah.
So we started actually filling this gap.
This portion of Talk Python to me is brought to you by Linode.
Are you looking for bulletproof hosting that's fast, simple, and incredibly affordable?
Look past that bookstore and check out Linode at talkpython.fm/Linode.
That's L-I-N-O-D-E.
Plans start at just $5 a month for a dedicated server with a gig of RAM.
They have 10 data centers across the globe.
So no matter where you are, there's a data center near you.
Whether you want to run your Python web app, host a private Git server, or file server,
you'll get native SSDs on all the machines, a newly upgraded 200 gigabit network,
24-7 friendly support, even on holidays, and a seven-day money-back guarantee.
Do you need a little help with your infrastructure?
They even offer professional services to help you get started with architecture,
migrations, and more.
Get a dedicated server for free for the next four months.
Just visit talkpython.fm/Linode.
The main goal behind this was always to provide annotations to people, right?
So that this sort of semi-formal docstring syntax or whatnot, we wanted to put it in the right
place, and annotations is just the right place for it.
Yeah, it definitely is.
One of the things that I don't like about the docstring style is if you have four arguments,
the docstring becomes eight lines.
It's got the name and then the type.
It's just, it gets, it gets really long.
And if you've got like a function that is three lines long and you put this huge docstring in
it just so you can see the type, it's like you've almost made it less readable.
It's a real big trade-off at that point anyway, to put that extra stuff in there.
Whereas if it's just a little bit, you know, a colon int, colon stir type of thing at the end
of your variables, it's much more compact.
I agree with you.
So that's one concern.
But like the bigger concern is really just like most comments in your codebase are going
to be wrong.
And like you're going to be essentially lies after some time.
So it doesn't actually take very long to those docstring based types to get out of date with
just like small changes to your codebase, small diffs that like people need to fix an issue
or introduce a small feature.
So for us, the human factor was very important, but also without the help of technology to tell
us that, hey, this annotation is out of date now, we would know that like those annotations
are bound to get, you know, useless after some point.
Yeah.
The worst than useless because you would trust them maybe if they're wrong.
They're misleading, right?
Yes.
That's very true.
Yeah.
So I think maybe one place we should start the conversation is like, what's the real benefit
of these type annotations on, you know, sufficiently small projects?
Maybe you don't need them.
I do find them to be really helpful at certain parts of my code just to help editors and things
like that.
But you had this really nice example of a function.
It was called like process all.
It took an items and it just said for I in items, item dot children dot process calls a function
line.
Like, even though there's only three lines, it's Pepe compliant.
It's completely, it's nearly impossible to make sense of what it's doing, right?
Yes.
So like Python programmers really like to have concise code, right?
And like that concise code pretty often uses very generic names for variables and methods.
Like when you do, when your method is called process and then you grab for it in your big project,
you're going to find that there's maybe 58 of them.
And actually figuring out what is exactly called argument that you passed that is just called
items actually is like you can sometimes get it, you know, from context.
You can sort of assume what it is, but you can't never be sure.
Yeah.
You might do throw like a print type of items, print type of I and just you're like, all right,
what the heck?
I just going to do some print statements.
Like this is out of control.
What is this thing?
Right?
Yes, totally.
So those very generic functions tend to be misleading, right?
Because like different people are reading this code and you are a different person six months from now.
So even if you wrote this function, you might be misled by what you wrote some time ago.
So this is one of the fundamental problems with having a very dynamic language.
People sometimes say that it doesn't matter for a small project, but even a small project just gets out of your head after some time.
Right?
And when you're coming back to it, like fixing a pull request, like, you know, that somebody gave you as a puppy, right?
As a gift.
So like you need to actually put all this information back in your head.
And when you have to follow how the types actually work in your project, you have less space to actually review the change.
So fundamentally, like function annotations, type annotations are just a way to cut this short so that you don't really have to keep the entire program in your head to make informed decisions about what you're doing with your function.
That's interesting.
It's like a form of distributed cognition.
Like more of the thinking is like stored in on the page and leaves more space for algorithms and consequences.
Yeah.
Pretty cool.
So maybe let's talk about first where these appear, like where they're useful.
So they're, they're useful in editors.
They're useful in continuous integration.
They're useful in upgrading.
Like what are some of the tools around all of this?
Like, for example, if I want to do, do a check to make sure my code is hanging together, you mentioned mypy.
That's pretty much the primary tool, right?
This is not the only tool, but it's the sort of, I don't know, like all but the official type checker for Python by now.
It's a Python organization on GitHub.
It has the most, I guess, manpower behind it.
It's the most mature.
So pretty much everything standardizes around it, but it's not the only one.
So the point of PEP 484 was not to sort of create a small walled garden of a single technology.
It was more of a standardized syntax so that any piece of technology that wants to do typing can share.
And we share it with, for example, PyCharm, which is the most advanced IDE that we have for Python.
It does implement its own form of type checking that is using exactly this syntax, is sharing the annotations for third parties and the standard library that we keep in the typesheet project.
It is kept separate from mypy just for this reason so that other projects can use it.
There's a project by Google which generates types by inferring what your code base actually does, which is called PyType.
And again, that uses exactly the same syntax that we formed with PEP 484.
So there's a number of projects that sort of revolve around typing.
But as far as type checking goes, mypy is the go-to type checker that we have for Python.
That's interesting.
I didn't realize it was so baked into PyCharm through TypeShed and stuff like that.
So we'll talk about TypeShed.
That's pretty cool.
Yeah, so I find that it's useful for adding to your program to add another level of check at certain levels, perhaps, right?
As you cross, say, to a data layer in and out of a data layer, for example.
That's pretty helpful.
The continuous integration is really important.
Yeah.
Do you know what editors support it?
I know PyCharm does, but I don't know what other editors take this into an account.
mypy as a type checker was formed around the idea that it's an almost full-fledged Python interpreter.
So it analyzes the entire program.
And for the longest time, it actually had to spend the time to analyze everything, which takes time.
It is also written in Python.
So it is not the most efficient thing that we could actually come up with.
But it was very important that we can move it very fast because we were only really learning about all the edge cases of the Python type system when we were working on PEP484 and later on.
So it is not the greatest technology to use within an editor, right?
Which is why PyCharm really implements its own thing.
If you are using an editor, you really want to type command space to tell you what available methods you have, right?
And you don't want to wait 30 seconds for an answer there.
You really need something right away.
Same for just telling you whether you have any type errors, right?
You would like the curly red line to appear right away as you're typing something wrong and not after three minutes.
So that way, mypy was only being gradually made compatible with this use case.
I don't think it's there yet, but there are features being implemented towards this goal.
There was an incremental mode introduced at some point where modules that were analyzed for type information were kept in a database,
like essentially a bunch of JSON files so that we didn't have to analyze them again if they didn't change.
Now, like there's a mode introduced to mypy where it's going to live as a demon that is running on your process.
So you don't even have to restart Python, which on its own, like, you know, just starting up the entire mypy type checker takes around a second.
So just cutting down on this is already a win.
And then reading all the types.
Could it do like a continuous analysis and just watch all the files and just like in the background, analyze it and periodically report its output or something like this?
That would be the point.
Like how that exactly works.
I'm not sure yet.
Like we are not, in fact, using this feature yet.
We've only recently adopted incremental mode, which was pretty experimental and at times unstable.
So mypy is a living project, right?
It's being actively developed by a group of, I think, four full-time developers at Dropbox and a bunch of volunteers around the project from outside of Dropbox.
So this feature is pretty much like very new.
I do hope it's going to work like you're describing, since this is exactly what then enables a language server protocol in, say, Atom or Visual Studio Code to actually, like, talk to the type checker and get typing information right away.
What we have today is there is a flakied plugin that I wrote that is a sort of basic version of mypy, which is let me just tell how am I doing file.
What does that mean?
Like, if you're doing full-time analysis and you're importing stuff from different files, you're going to know their types.
You're going to be able to, like, tell whether you're using an API right or wrong, regardless of the file it's in.
But that requires this full-time analysis that takes sometimes minutes, right?
Yeah.
So instead, we can run mypy in a special mode that just says, assume every import is fine, like, whatever I'm importing, I'm using it correctly.
But just look at my functions in the file that I'm editing right now.
And it turns out that this can be done around a second, like, as long as mypy's process is starting, pretty much.
And because of TypeShed, which is our collection of types for the standard library and a bunch of third parties, we can still provide very meaningful information about how you're misusing some built-in type or some built-in library, right?
So, for example, like, the example I always give is newbies very often confuse sorted and the sort method on a list, right?
Like, they would think that the sort method also returns something, but it doesn't.
This very simple plugin for Flacate will already tell you that, hey, you meant to actually use sorted and not use that sort because that sort's in place and it doesn't return anything.
Yeah.
And those are the kinds of things that can be really helpful to get picked up there.
So let's take a moment and talk just about the syntax real briefly.
So there's a real simple version.
Like I say, variable, then colon, then the type.
So I could say ID colon int or name colon stir, right?
And that's totally straightforward.
But as soon as it gets a little more interesting, you actually have to bring in the typing module.
So like if I want and maybe I'm going to return a user or it might be empty.
It might be none because there's no user at that ID, right?
So you might have an optional user.
You might have a list of optional strings.
Like these are pretty interesting.
So do you want to talk a bit about the typing module?
Obviously, go beyond just simple classes.
So as annotations, you can use any built-in type, any user-defined class.
But beyond this, you start having like complex types.
Like you mentioned optionals, which is this actually is usually an int, but maybe it's none.
Maybe the user just didn't provide it at all.
Or maybe it's bytes and maybe it's a string, right?
So you want essentially what we call a union of multiple types.
You can have other things like I want a list, but I want to specifically tell you that this is a list that holds just strings.
So these are collections with generics.
And the built-in collections in Python don't support generics because the runtime doesn't really work like this, right?
Compared to statically typed languages, Python really implements classes as just factories of objects.
Those objects just have attributes on them.
And as long as you are calling the right attributes, like calling the right methods, everything is fine.
And the runtime doesn't really care what particular type an object has.
So if you want to actually have this as a feature of the type system, then, well, we had to create our own versions of the built-in collections.
That includes ABCs.
That includes things like all the things in collections, like order dictionaries, like name tuples.
Everything that essentially you can instantiate in the standard library, there is a generic variant of it.
So for this reason, we have the typing module that you import those complex types from.
There's many other complex types, like any, which essentially tells the type checker that I don't really know what this is.
Obviously, any is a name that describes sort of your state of knowledge.
It doesn't really say that any type is going to be fine, right?
It just says that as far as I know, like whatever is passed should be okay.
That's pretty much a way to silence the type checker.
So this special any type is also in the typing module.
This portion of Talk Pythonry is brought to you by us.
As many of you know, I have a growing set of courses to help you go from Python beginner to novice to Python expert.
And there are many more courses in the works.
So please consider Talk Python training for you and your team's training needs.
If you're just getting started, I've built a course to teach you Python the way professional developers learn by building applications.
Check out my Python jumpstart by building 10 apps at talkpython.fm/course.
Are you looking to start adding services to your app?
Try my brand new consuming HTTP services in Python.
You'll learn to work with RESTful HTTP services as well as SOAP, JSON, and XML data formats.
Do you want to launch an online business?
Well, Matt McKay and I built an entrepreneur's playbook with Python for Entrepreneurs.
This 16-hour course will teach you everything you need to launch your web-based business with Python.
And finally, there's a couple of new course announcements coming really soon.
So if you don't already have an account, be sure to create one at training.talkpython.fm to get notified.
And for all of you who have bought my courses, thank you so much.
It really, really helps support the show.
So there's a number of features there.
So whenever you need a situation like a union, like an optional type, like generics or whatnot, you would use this.
Generics are special because sometimes you really want to say, for example,
I don't care.
I'm taking it as an argument and I'm returning the same type.
Yeah, that one was surprising to me.
That one was surprising to me because I hadn't seen that before.
Like I could, I know you could have like a concrete generic, like a list of strings would sort of specify like it is a list and its internal type is this.
But to say it takes a list of T and it returns a T like that.
That was a pretty unexpected thing I saw coming out of the typing module.
That's cool.
That's pretty much like a very basic version of sort of templating for Python.
But it's fundamentally very often used, right?
So like it's very often that you would have a function that operates on a collection.
And I don't know, like returns the first truthy value of it or whatnot.
And like just typing this would be impossible without a type variable.
So this is where they come in useful.
There's a number of other more advanced features.
So they're like all documented in the on docs python.org.
But essentially the necessity for the typing module comes from the fact that there's more to types than just simple classes.
One of the things that also surprised me when I first started using these, it was because I was getting an error when I had a method that I said return.
Like let's say a user and I was returning none when the user wasn't found or if the ID was improperly specified.
And I was getting an error saying you can't return none when you say you return a user.
And then I realized eventually you have to do optional user if you're going to have none.
And most languages don't distinguish between a pointer type and whether it's nullable or not.
Maybe some of them do for value types.
The one that I do know that does that is Swift.
So what was the thinking around this concept of just actually making it explicit that you have to say it's not only this type, but we guarantee it's not or at least we proclaim it.
It's not none.
It actually points to a real object.
I personally knew about two languages that approach this problem like from the opposite ends.
So Java, for example, doesn't type check for null and like null pointer exceptions are sort of the bane of existence of a Java programmer, right?
Because this word compiler is not really helpful.
You need to figure it out on your own.
And the opposite thing was hack, which is the typed PHP version used at Facebook, actually has this concept.
And it turns out that that is the most popular class of errors found by the type checker, where the user of a function doesn't expect it can ever return null, but for some reason it does.
So it was very natural to me to introduce this for Python, especially that with the logging information we gather from running Instagram and other systems in Python at Facebook, we knew that like attribute error, none type doesn't have some attribute is a very, very popular exception, right?
That stems from the fact that sometimes, I don't know, an API call doesn't work or some helpful function tries to just not raise an exception.
Actually, raising meaningful exceptions is the Pythonic way to do this, right?
Like if you are unable to fetch a user, like raising a lookup error, like is the more natural thing.
Like it's going to read better when somebody is faced with this sort of problem.
It is what all the internals of Python do itself, right?
So like we have dictionaries doing exactly this and so on and so on.
So this is sort of what the typing gently nudges you to do because putting any sort of type union, including optional, which is essentially a union of your type and a none,
as a return value actually makes using your function so much more difficult, right?
Any user of your function now has to check whether the return value of your function was none or not.
And this is pretty painful pretty fast, especially if the situation in which your function can return none is very unlikely.
People are going to complain very loudly that you make me check for the stupid non-value.
I know that it will never be non-introduction, right?
It can never be non-only with the mock-up database or whatever.
So that actually makes you think maybe I should not change the API so that none doesn't appear there at all.
And I think like sort of ironically, the very verbose nature of the optional type sort of makes you think twice.
Yeah, it definitely does make it long.
And if it's for parameters that you have many of them, it gets even worse.
Yeah, yeah, that's interesting.
So you talked about finding errors in production and stuff.
And at your presentation, you spoke about Instagram and the sort of success story that you guys had in terms of actual runtime errors.
And maybe they're unexpected.
Yes.
The unexpected results you got.
Could you maybe cover that real quick?
When you're adopting types, you want to sort of see the value that they give you, right?
You want to recognize whether it was worth the time.
So first of all, like we as the authors of PEP484 believe that putting type information, even if you're not doing anything else with it, it's already worthwhile because it's a form of documentation.
But with additional type checking, like you want to see that actually there is like a change in the number of errors that you see in production.
Right.
You'd like to see runtime errors become CI, like continuous integration errors instead or even before then, right?
Yes.
So you're looking for some sort of metrics that you can look at to like prove that this entire effort makes sense.
So the simplest thing that you can do, obviously, is just like track the adoption, right?
Like what is your adoption?
So we obviously did that.
And now Instagram is close to 30% typed functions.
So pretty much at this point where we already see a lot of value, this is not something that you see from day one.
If you just type a bunch of functions, you're going to maybe find a bit of problems in those particular functions.
But for the typical Instagram developer, for the typical engineer, they will not really see how that changes their life.
But as soon as like you're, I don't know, north of 10% of functions, random people start noticing type errors that the type checker tells them about before they shipped something.
So a metric that I was very interested in is how is this going to affect the, I don't know, average number of attribute errors and type errors that happen in production, right?
And how many, like, are we going to see fewer exceptions?
Right.
And those two that you named, those would be the types you run into when you assume there's one type, but it's actually the other.
You thought it was a list, but it's a dictionary or something like that.
Yes.
So types, in a special case of the attribute error, is mostly the non-type, right?
When you try to do something with a non-type that it's not prepared to do.
So we wanted to see whether there's going to be fewer exceptions after adopting typing.
And sadly, like, this correlation was just not seen.
Like, we couldn't really detect that this is very easy to, like, prove that, oh, typing helped us with lowering the floor of exceptions at runtime.
But what I didn't personally notice, and Carl Meyer, who is pretty much spearheading the typing effort at Instagram, he noticed that, yes, it's not about the sort of floor of exceptions that pretty much describe mostly very unlikely scenarios that happen for an unlucky user of Instagram.
It's more about shipping a bad change.
And now that number is almost 10 times lower.
So it's 10 times less likely that you're going to ship some bad diff to production that introduces a type error than it was before.
And this is a metric that was sort of hard for me to notice from just looking at graphs in a linear fashion.
But in fact, yeah, that pretty much proves that this actually impacts Q-ality in exactly the right way.
Oh, yeah, that's a really interesting one.
Those are the releases that you're like, oh, no, it's crashing.
And you're just, like, freaked out.
And you're scrambling to roll it back.
And those are the worst kinds of errors.
Not the ones that happen in one in a million, but the one where it happens one for one.
Yep.
Yeah, pretty interesting.
Pretty interesting.
So let's talk for a moment about this concept of gradual typing.
I mean, for example, you said you guys are really successful and you've got 30% of million lines of code.
The functions there have typing or something to that effect.
And I find this in my code as well.
Like, I love type annotations, but I don't annotate everything.
There's, like, a core set of functionality.
Like, this is really what I want to annotate.
This is really important that this is clear.
But this other part, it can kind of just derive the benefits from having the other stuff really stable.
So you want to talk about the rules of, like, gradual typing and, like, maybe how the order actually affects what is caught and what is not?
That's surprising.
There is actually a separate PEP that describes how gradual typing works.
That is, I guess, 483 that describes this.
So the reason for this is that in a language like Python, where we are using runtime objects without looking at their types to validate whether they're correct or not,
putting this feature out essentially means you're going to start with large projects that have never even thought about this feature.
So the concept of gradually exposing your code base to static typing was not something that we wanted to do.
It's something that we had to do.
There was just no other way around it.
Fortunately, many other languages like JavaScript with TypeScript or, like, Flow or Hack or whatnot, like, went exactly through the same path.
I think the reason is because the primary driving factor must be if you want to bring in, like, let's say TypeScript.
If you want to bring in the other JavaScript libraries, if you forced everything to be 100% typed, you would close off the entire ecosystem.
And same thing with Python, right?
Like, you want to use all the packages on PyPI and other pieces that are going to lag behind, right?
So you can't say everything has to be typed or this just doesn't work.
Yes.
But even if that were your dream, if you wanted to actually type the world, you have to start somewhere, right?
And if you cannot reap the benefits until everything is fully typed, then pretty much, like, the feature is useless for the longest time.
And I think, like, people would get discouraged way quicker than they would see anything worthwhile from it.
So gradual typing essentially is this notion that you can slowly annotate function by function.
And by doing this, you're just increasing the footprint of typing and increase the sort of usability, usefulness of the project.
So the ordering there is important in one important, like, way.
So I would advise everybody to look at how their function call graph looks like in their program and start annotating from the functions that are mostly used, right?
Like, are very deep in the stack.
Like, everybody calls them, right?
The reason for it is that once a function like this is annotated, all users of it can be validated whether they are using this function correctly or not.
If you didn't annotate this very central function and went on and annotated a bunch of leaf functions, then you might not know whether they are correct or not.
And the reason why not is that as long as a function is not annotated, the type checker necessarily has to assume that anything is fine, right?
Like, any argument typed past is okay.
The function can also return any type from it.
So pretty much that means it's going to stay quiet regardless of what you're doing.
So if you are annotating your core function first, you're going to get the benefit of being warned about invalid usage way faster than if you would actually wait with this core functionality to the very end.
It gets even worse.
If you do this, then that might pretty much cause errors to appear on some functions that you didn't touch, right?
Like, you annotated a core function and suddenly you see 40 new errors from the type checker on functions that you don't even know about.
But these are the functions that were using what you just annotated and they were using it wrong.
So now you are faced with the problem, what am I supposed to do?
Like, am I supposed to fix all those 40 functions?
I didn't even know that we had functionality like this and now the type checker yells at me.
So the right ordering can save you a lot of time and a lot of stress with actually making the adoption smoother, right?
It's a good point.
You have a really nice graphic in your talk, which I'll put in the show notes, of course.
And it looks a little bit like the game called Whack-A-Mole.
You hit a thing, it pops up another play.
You know what I mean?
You fix one and then two more errors pop up.
You fix those.
One goes away, another pops up.
And it's sort of like as you add this type checking, the pieces that were just ignored before are now actually getting validated.
So it can be a little bit funky like that.
So like you said, I think starting at the right levels, the important functions, and then sort of slowly build your way out is pretty nice.
Yeah.
So there's ways to automate parts of this.
So the PyType project that I mentioned can infer type annotations from just looking at your project.
It is pretty Python 2-centric still, so it might not work on like the latest Python 3.6 features or whatnot.
Like that sort of your mileage may vary.
Always patches accepted.
But it can actually go a long way to create this initial body of annotations for your big project.
It does some sort of magic that you might or might not agree with, like figuring that, oh, you are using an append method.
Within your entire program, the only type that has an append method is a list.
So I guess what you're using here is a list.
All sorts of things like this.
So like this is what inference is all about.
But it's actually a very worthwhile project that sort of boosts adoption of types in new projects.
What you can also do is you can maybe gather those types at runtime.
At some point, I thought it was a crazy idea that I would just like slow everything down and it would never work on unit tests because you're mocking stuff.
So types are different.
You would also like have issues with types being returned as those massive unions of 50 things and whatnot.
So I had this pretty apocalyptic view of this, like that that would never work.
And usually when somebody says that it's impossible, somebody else that doesn't know this is going to just go ahead and implement this.
This is exactly what happened on Instagram.
Like we had Matt Page and Carl Meyer working on this project.
It's open source now.
It's called a monkey type that does exactly this.
It hooks to your program, records the types of arguments to functions.
It records the return types as well and then generates the typing stubs from what it gathered.
And you can apply those types back to your code base.
So that way you can pretty much just remove a lot of the work, like the initial work that has to be done.
And even though I envision garbage collected away, in fact, it turns out that most people don't actually use Python in crazy dynamic ways all the time because that's also very unreadable.
Right.
And Python is all about being runnable pseudocode.
It has to be readable.
So the types are, for the most part, very sane.
You can use them and pretty directly just apply them back and you're done.
We had a very big spike in typing adoption at the time where we started using monkey type since it's actually producing very high quality types.
Sometimes it's funny.
So sometimes it will tell you that this optional argument that has a default value of none has a type of none, which essentially means that you have some very special optional argument and nobody ever uses it.
In the entire code base, nobody ever actually populates this optional argument.
So you might as well just remove it.
All right.
So you could just get rid of it.
That's right.
How funny.
Yeah, there's a bunch of crazy ideas and those all do sound pretty interesting.
Another one has to do with like actual performance optimization.
Like actually going, no, this is a list.
And so we're going to do some kind of shortcut or something to that effect.
Yeah.
So originally, not only me, but I think Guido as well.
We thought that this is a dead end.
We're not going to be able to do anything useful there.
The reason for it was twofold.
Like, first of all, we saw that Python, its runtime doesn't actually utilize typing information at all.
It just tries to find attributes on your objects and does things with them.
And actually, the most performant Python runtime that we have, PyPy, is all about dynamically finding what are you doing.
And it's being able to find this in ways that are way more precise than type information that you put in will ever be.
Right.
Because very often, the types that you're going to describe is that I want to have an iterable of string.
I don't care what iterable that is.
It should be an iterable.
So that is not very useful for PyPy.
Right.
What would be useful is, yes, it's an iterable, but I run types or tuples.
Right.
So that way, like, it can actually put guards and jit things away and it becomes way faster.
So we were very negative, like, in terms of seeing value in this.
But this is exactly what Cython does.
Right.
And Cython can sometimes accelerate your function by, like, 20, 50 times, like, by knowing that, oh, this is only ever a string or this is only ever an int.
So I can maybe not even box it in a Py object and just do C-level computation that way.
So combining this information with an ahead-of-time compilation step is what is very interesting.
And I talked with Yuka Lechtostalo, the original author of mypy, about this idea during the last PyCon.
And he has a project that is sort of spearheading this for Python.
So I really do hope that by this PyCon, like, we'll probably hear from Dropbox that, hey, this actually works out.
This actually accelerates Python in this sort of automatic way.
So I don't know if you remember.
That would be awesome.
I don't know if you remember, but originally PyPy started out as, like, this crazy import that you just put in your project, like, import cycle.
And suddenly, like, your code became way faster.
You didn't know why.
So we might actually be back into this world where you don't maybe even have to perform any imports in some future Python version.
But we are actually going to attempt to do some ahead-of-time compilation for you.
And type information is going to be useful there.
That would actually be really, really interesting.
So I'm looking forward to that.
All right.
We're getting sort of short on our time.
So I want to just cover one more really quick thing.
And maybe just leave it there for the type annotations.
It's really awesome work.
And the more I use them, the more I like them.
But you have one other interesting piece of news to do with just Python more in general.
And you, right?
Yes.
So you were just chosen as the release manager for Python 3.8 and 3.9.
And 3.7 is coming really soon, right?
So you're on deck.
And you'll be up really quickly, right?
Yes.
So pretty much the development of Python 3.8 just started just yesterday.
So yes, it's going to be developed for the next 18 months, pretty much.
And Python 3.7 is in beta stage now.
What it means is we don't add new features to it anymore.
We're going to pretty much harden it now, like find all the possible bugs and problems with
whatever we implemented at this stage.
Release for betas.
Then release, hopefully, very nice release candidate that we can then bless as the gold
version.
If not, then there's going to be another release candidate.
And at some point, we're going to release Python 3.7.
It sounds like this is very close now.
But in fact, that is going to happen late June.
So the beta stage actually takes quite a bit of time.
But yeah, like this is how a mature project like Python operates.
So like with Python 3.8.
The beta stage and the later release candidates or whatever are going to happen after PyCon in
2019.
So this is going to be quite a while from now.
Unless we change how we do things, which I might sort of influence a bit.
Like this is the timeline for the Python project.
That sort of stability is good for the average programmer, right?
Because the average programmer doesn't want to have backwards incompatible changes all the
time.
Like he's not interested in some subtle new features all the time.
Like being able to run code that you wrote 10 years ago is a very important feature.
And I don't think like Python did the greatest job at this with the Python 2 and Python 3 dichotomy.
Like with a lot of smaller changes that end up being incompatibilities.
I'm always amazed how like Java was able to pull this off.
I'm still able to just perfectly fine like compile projects that I wrote in college.
And they still like work perfectly fine like all these years later on a different platform,
on a different Java version.
It's still just okay.
So we do hope that like from now on, there's not going to be a very far off Python 4 that breaks compatibility
in crazy ways again.
So we pretty much learned from this experience that, hey, we don't want to do this to people anymore.
That doesn't work for anybody, including the stress that it actually builds on core developers.
So yeah, I'm pretty happy like that I'm going to be for 3.8 and 3.9.
If I know my luck, Python 3.9 is going to be the last Python 3 version.
So again, like it's going to become like the new Python 2.7.
And I'm going to release it like for the next 15 years.
So yeah, this is like into retirement.
You're going to be working on 3.9.
Yeah, so that might happen, but hopefully not.
Hopefully it's going to be a gig that is going to end like eight years from now.
So you have to understand like because of all the security fixes that you still release for old versions or whatnot.
It's a pretty long time commitment, but it doesn't take too much time a week.
So I do hope I'm going to have to, I'm going to be able to pretty much combine this with every other activity I'm doing.
I want to be cognizant of your time and not taking it all up.
But just really quickly, what features would you like to see in these new versions 3.8 and 3.9?
And particularly, I wrote the single dispatch like generic functions like in Python.
And ever since, I was just poked by everybody to actually go full on on multiple generic dispatch.
So I think like it's time for that.
And it would be nice for Python 3.8 to fully implement that.
What else?
Performance.
It's sort of always a second priority feature for Python.
Maybe we'll see that performance optimization that you're talking about types make its way into one of these.
Oh, it would be great if that actually shipped in 3.8.
That would be very optimistic for me to say that it will.
But there's other areas of interest there.
Like for example, speeding up startup time.
So for command line utilities, for bigger projects that have like thousands of files that are involved.
Startup time in Python is not great and could be improved.
So I would actually very much like to see progress on this.
There were a bunch of crazy ideas.
Again, like that are very likely to happen during the core sprint last September.
They didn't quite end up being like ready for Python 3.7, but it is pretty likely that they're actually going to land.
That sounds really awesome.
And I'm looking forward to your sort of overseeing that whole process.
That's great.
Let me hit you with the last two questions.
The two questions before we get out of here.
If you're going to write some Python code, what editor do you open up?
I used to be a Vim person.
Like starting from my first Python conference and back in 2008,
when I, you know, sat randomly next to a person that was like a Vim god
and like did crazy things with it that I never thought were possible with an editor.
It really looked like the code was just appearing.
Like there was no cursor that like the person was just sort of fighting with.
It was just like organically forming new ideas.
So for me, that was like, oh, this is amazing.
So I've been using Vim for more than close to a decade.
But then I found out that Vim is always this thing where you can get it maybe to 90%
of what you want with every feature.
So nothing ever works like perfectly for it.
Especially like the thing that I told you about where we have developer servers that are sometimes
very far from us.
The responsiveness and latency from running your Vim over several thousand kilometers,
that was actively like impacting my productivity.
So I looked for something that would be running locally on my box.
And Adam, which is a bit absurd for me to admit,
but yes, this editor written in JavaScript, right, is what I use now.
It has a nice set of plugins released under this umbrella called Nuclide by Facebook that like
enables remote code like development, including a remote debugger for Python.
So you can just debug a process that is running in North Carolina and you just step through it
and it shows you where you are in your file and you can set watches and do like all the
things that you would expect from it.
I have Vim bindings for this.
I'm using it sort of like a primitive, almost functional Vim that way, but all the extra
functionality makes it totally worth it for me.
That sounds really cool.
It definitely sounds like a good remote work setup instead of just like SSHing over because
that sounds tough in terms of latency.
All right.
So a notable PyPI package.
Notable PyPI package.
I think this is super underutilized and you should all use it.
It's called adders.
Hinek Schlavak wrote it.
It is actually a way of creating full featured types in Python.
So full feature classes without all the boilerplate.
So by just specifying essentially like a schema for your class saying this class is going to have
those fields.
What you're getting back is like already made init method, already made a wrapper, str, being
able to compare like those objects of this class meaningfully.
You can configure it to slots.
You can configure it to create immutable classes and whatnot and whatnot.
So it is a very powerful package that sort of feels like next generation Python.
Like it removes a lot of the boring, you know, setting arguments to self.argument name, like,
you know, in the init method and so on and so on.
And more importantly, it's always correct, right?
So by removing the boilerplate that you create manually, you make sure that it is going to be
fine every time.
So I kind of recommend this high enough.
If you wish to wait for Python 3.7, this is getting included, like a rewrite of it essentially
is getting included in the Sun library called data classes.
But others is out there now.
It is pretty mature by now.
It's been maintained by Hineg for a number of years now.
We use it extensively.
I can't recommend it high enough.
Sounds really nice.
And it's definitely a cool project.
Thanks for recommending that one.
All right.
Final call to action.
And people are excited about types and the benefits to their, you know, sort of upgrading
their code, finding all these bugs.
How do they get started?
What's the final call to action for you?
If you're afraid of types and think they don't fit in Python and they're not Pythonic, you
should think about this.
This is information that you were already putting somewhere in your documentation, maybe in your
doc strings and whatnot.
Type annotations is a piece of technology that only formalizes where you're supposed to do
this.
And on top of this, it will help you to fix bugs and find like future ones.
Like that's great.
That only makes it like more usable for you.
So in this Pythonic thing, it's still very new, but the tooling is now mature enough for
actual adoption by random non-expecting users.
So if you're afraid, just try it out.
See how that actually looks for you.
It's not going to cause your code to look like Java or Scala.
It is still very much Python.
It doesn't actually cause changes to how you code Python.
I think you should make an informed decision basically by trying it out yourself.
Yeah, it's great advice.
And I definitely second it.
Lucas, thank you for being on the show.
It was great to talk to you about all this stuff.
All right.
Happy to be here.
Thank you very much.
Yeah.
Bye.
This has been another episode of Talk Python to Me.
Today's guest was Lucas Lange.
And this episode has been brought to you by Linode and Talk Python Training.
Linode is bulletproof hosting for whatever you're building with Python.
Get four months free at talkpython.fm/Linode.
That's L-I-N-O-D-E.
Are you or a colleague trying to learn Python?
Have you tried books and videos that just left you bored by covering topics point by point?
Well, check out my online course, Python Jumpstart, by building 10 apps at talkpython.fm/course
to experience a more engaging way to learn Python.
And if you're looking for something a little more advanced, try my Write Pythonic Code course at talkpython.fm/pythonic.
Be sure to subscribe to the show.
Open your favorite podcatcher and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes, Google Play feed at /play, and direct RSS feed at /rss on talkpython.fm.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Now get out there and write some Python code.
I really appreciate it.
Bye.
you
Thank you.