 So, yeah, I'm Rogier van Nagheer. I'm a data charmer at GoDadry Driven. Don't ask me what that actually means. And I want to talk to you about protocols and why you actually need them. Or in fact, mostly, what are protocols? Now, in order to fully appreciate the usefulness of protocols to see why you actually need them, I think it's useful to go all the way from, well, what is dynamic versus static typing, type hints, then ABCs, and finally protocols. So, if you're familiar with one or more of the first concepts, bear with me. All the way in the end, I'll talk about protocols. So, let's first talk about typing in Python. So, Python is a dynamically typed language. You might know that. So, what does that mean? So, let's have a look at what dynamic typing versus static typing is. So, here on the left side, you see some Python code. So, a function, my function. And it takes three arguments, A, B, and C. And then when you call it, it returns some kind of, sorry, it does an operation on those arguments and then returns the results. And so, the return type of this function, it depends on the arguments that you put in. So, if you put in five, three, and two, you'll get an integer as output. On the other hand, if you put in 5.1, three, and two, you'll get a floating point number. These types, they are checked at runtime. And as you can see, type decorations are not required. I mean, I haven't defined any types in the definition of the function. And the only way we can, or the only way I have played around with the types is by just the values of the arguments I put in there. On the other hand, so, on the right, you'll see an example of a statically typed language. So, in this case, we have to specify the return type of the function. In this case, it's an integer. And then the function name with three arguments, again, A, B, and C. And those three arguments are all integers. It explicitly says that. So now, when you, in such a language, when you try to call this function with three integers, you'll get an integer back. And when you do this with a float as one of the arguments, you'll get an error. And these types are not checked at runtime, typically, but they are checked at compile time. This does mean that type declarations are required. As you can see, both the return types, as well as the argument types are specifically, so are specified there. They're right in the code. So there are pros and cons of both kinds of languages. With dynamic typing, you don't have to really worry so much. As long as the arguments that you put in there work with the operations that you do in the function, everything's fine. But if something's not fine, you might only find out at runtime, maybe after quite a while of running, or maybe on a machine of someone else who uses your code. And that's not so nice. While with static typing, you have to specify all those types explicitly, but the types are checked at compile time, which is nice. Of course, so there's another category of languages in here, untyped programming languages. Those are something completely else. Let's not talk about them entirely. So like I said, Python is a dynamically typed language. Dynamic typing, we also call that duck typing, because if something works like a duck and it quacks like a duck, then we just assume it is a duck. So in other words, if some object that you put into a function has the required functionality, then we should just accept it. Why wouldn't we? So let's have an example here. So suppose that we have a class duck and a duck can walk and quack, obviously, right? So we can make an instance of a duck, and then we can make the duck walk and quack. That's awesome. Now, if we make a class donkey, and a donkey can certainly walk, but a donkey cannot quack, at least I haven't heard a donkey quack, then we can make another duck of type donkey, and we can try to make this duck walk and quack. But when we try to do so, at runtime, we'll get an attribute error saying that a donkey object doesn't have an attribute that's named quack. Well, of course, it's easy to see what goes on here, but in a more complex program, you might find out really late. So that is not so nice. There's only this error, so this is only really checked at runtime. So now, if instead of a donkey, we have some kind of impostor duck, which isn't really a duck, but it can walk and it can sort of try to quack, not quite quacking, then, of course, it's fine to just make a duck of type impostor duck and make it walk and make it quack. The impostor duck behaves like a real duck, so we should just accept it as being a duck, right? I really like that about Python. You don't really have to worry about what the type is of something that you put in as an argument. You just make sure that it has the functionality or it behaves like the object that you would expect it to do. So then, as a wrap up of this first part, Python is a dynamically typed language, which, wow, and I like that you don't have to, you don't really have to specify any type declarations. That's nice because you can just quickly code something together, it gives you lots of flexibility. You can create an impostor duck that behaves like a duck but actually isn't a duck, but as a downside, you don't have any type checking except at runtime and sometimes runtime is just too late. So in order to fix that last downside, we have typings, also called optional static typing and it was introduced in Python 3.5. And usually that goes together with Mypy and Mypy is the optional static type checker for Python and so according to their description, they aim to combine the benefits of dynamic or duck typing and static typing. You might know Python, you might know Mypy, sorry. You might also know Python, at least I hope. So suppose, for an example of how to use type hints, suppose that we have a class duck and this duck can eat bread and it can swim. Now then we can have a function called feed the duck. It takes a duck as argument and here after the argument name, I've specified a type hint, namely that I expect this duck to be of type duck. And then it can have this duck eat bread. Well that should work because the duck has a method that is called eat bread. Now when we instantiate a duck, then we can just, we can call this function. We can use it on the duck and duck is fed bread. Well that's cool. Now then if we have a monkey, a monkey can eat bananas and climb the tree. If we can then try to feed a duck, use this feed the duck function on the monkey, we'll get an attribute error saying that the monkey object doesn't have any attribute called eat bread. Well that's correct, but that's at runtime. But if we would use Mypy beforehand, before runtime, it would already tell you that this argument one of feed the duck, so the only argument, is actually of type monkey and it would expect it to be a duck. So here Mypy can really help you spot any issues with your code before runtime. So that's cool. Now for a much more generic function. Let's, so again we have this class duck and the duck can eat bread. And now we have a much more generic function that can feed bread to any animal. And in this case we specify that this animal must be of type duck. That's cool. But now suppose that there's yet another animal, maybe a pig. And a pig can also eat bread. Then we would shoot, well we really have to adapt the type hint of the feed bread function to also accept a pig because otherwise mypy will warn us that a pig is not a duck, which is true. So now here I've adapted the type hint for the animal argument to be of union, a union of a duck and a pig. Basically saying we either accept ducks or pigs. Now this is all fine, this is perfect. But now suppose that this code is in some package that I download from Bypy. It's called animals. And here is a class Mace. Mace is my baby boy and he really likes to eat bread. He can also drink milk. His two favorite activities. Now if I instantiate an instance of Mace, excuse me, and then try to feed bread to Mace. At runtime that would work fine because Mace has a eat bread method. But mypy will warn us that this argument one of feed bread has an incompatible type. And Mace, while expected a union of a duck or a pig. So while the function would work on this class Mace, you cannot have mypy sort of accept this as long as you, unless you can adapt the type hint of this function yourself. If you can't, if the function is in someone else's code, then you cannot add any other class to this union in the type hint. So you're basically stuck. Of course you can have mypy ignore this issue if you really know for sure that it's fine. But well, it's not so beautiful. So as a wrap up for type hints, type declarations, they're optional. That's really nice. So you don't have to, but you can. And when you do, you get optional static type checking. So before runtime, you get a warning when something is wrong. But you cannot adapt type hints of important code. So if you make something that's compatible, well, you can't tell the other library that you made something that is compatible with them. So that's not so nice. Okay, let's move on. ABCs, abstract based classes. So what are they? Here's an example. So ABCs are based classes. So classes that you can inherit from, but they cannot be instantiated. So typically they're used to define the interface of what subclasses should look like. And so here I have a base class called animal, which has an abstract method. So not implemented method called walk. Now forgive me for assuming that all animals can walk. All animals that we'll use in this presentation can surely walk. Now we cannot instantiate an animal because well, an animal is a base class. It's abstract. We cannot use it. So if we try to instantiate an animal, we'll get, at runtime, we'll get a type error saying that we cannot do that. What we can do though is we can define a class duck that inherits from animal and that can walk. And now we can instantiate the duck and this duck is then an instance of animal. Wow, okay, so that's cool. So how is this useful? Now suppose that we want to go back to the previous example with bread eating. Then we can make a base class that's called each bread that defines that all subclasses should have a method that is called eat bread. And then we can define duck and pig, both to inherit from this eat bread base class. And now in the feed bread function, all we need to do is specify that this animal should be of type, eat bread, and an animal that we know that animal can eat bread. So now if I were to import this code from some package I found somewhere and use it with my baby boy, I would define his class to be maize that inherits from each bread. And then I can define how he can eat bread and drink milk. And I can use an instance of maize with this function. That works fine. Typically though, those base classes are not so easily exposed in packages that I tend to use. So then instead of having to import or being able to import feed bread and eat bread from animals, you'd have to find this eat bread base class somewhere deep inside the library somewhere like fromanimals.base.eats import each bread. But still, this works fine. Alternatively, if you don't really want to inherit from the base class, you can explicitly register your class as being a subclass of an abstract base class. So that's what I do here. So I define the class maize which doesn't inherit from each bread, but then I register maize as well, a subclass of each bread. So in this case, maize still is. So an instance of maize still is an instance of each bread although it didn't inherit any functionality that I may have defined in each bread. In this case, of course, I didn't define any functionality in the each bread base class. I only defined that, well, an abstract method without any implementation. So in this case, it's exactly the same as the previous example. But in some cases, you might want to register a subclass without inheriting any functionality that has been defined already. So this all works fine, right? This is pretty beautiful. But sometimes, you might still run into issues. So suppose that there is this package animals that defines the base class animal and a dog that can walk and a function that can walk an animal. And then there's yet another package that's called llamas that defines a llama. And the llama can also walk, right? Lamas can walk. Now, if in our code, we import this walkAnimal function from the animals package and then the llama object from the llamas package, then sure, at runtime, we can walk the llama. That's all right. But my pie will complain that llama is actually not a subclass of an animal, which is true because, well, they're from different packages. So why would it be? Of course, what you could do is fix that by registering llama to be a subclass of animal. But this feels kind of fishy to me because you're really changing the internals of packages you're using, right? It works. It's not super nice. So wrap up, for so far, abstract base classes. They give a lot more structure to your types. That's nice. Type hints in this way won't need updates for new subclasses. So you can add as many animals as you want, but you'll never have to update all those type hints that say animal. It's automatically included. But you may still, in some cases, have difficulties combining classes from multiple libraries. Won't happen so often, and you might be able to be OK with that. But I don't think it's so nice. And those virtual subclasses, so they're subclasses that don't inherit, that you can explicitly register. That's also nice, but you still need to explicitly register them, which I also don't like so much. So now we'll come finally to protocols, also called structural subtyping or static duck typing. Those were introduced in Python 3.8. And protocols make everything that we've encountered so far much and much more beautiful. So let's have a look how. So a protocol is a special case of abstract base class. And here I define a class, eachBread, that is a protocol that defines this method, eachBread, without any implementation. And then I define a function, feedBread, that takes any animal as argument. And the animal should be of type, eachBread, so that the function can make the animal, eachBread. Now if we define a class, so when we define a class called duck that has a method that is eatBread, it's called eatBread, then this is automatically and implicitly considered to be a subtype of eachBread, but only when typing. So this protocol automatically, so this class duck is automatically a subtype of the protocol that defines what these classes that adhere to this protocol should look like. And as duck implements all the attributes and methods that this protocol defines, in this case it's just one, it is automatically a offtype eatBread. And so now if we call feedBread on the duck, that's OK, both at runtime as well as when type checking. Now if I were to import that function from a package, from animals import feedBread and then again define a class mace, then mace is also implicitly a subtype of eachBread because it implements a method that's called eatBread. And again I can call this function feedBread on mace and that works all fine. Without having to do anything at all, all I need to do is make sure that the required functionality is there. So the class mace has a method that's called eatBread. That's all I need to do. And then everything works fine. That's pretty nice, isn't it? So actually before this, there were already some kind of protocols that, so before Python 3.8, there were some protocols that we were used to, like knowing and playing around with. So if you've already used some type hints with, for example, an iterable of a certain type or an iterator of a certain type or a size, then, well, in effect, what happened was just checking that the object, whatever it was, in the case of iterable, it should have had the dunder iter method for an iterator. These were the dunder next and dunder iter methods for the size, this dunder len. So now, since Python 3.8, these are actual protocols. But before that time, those were not really protocols, but behaved at least like those. So now, protocols were mainly designed to be used when type checking, so for mypy. So before you ever run your code, make sure that all those type hints that you use the functions according to the type hints. But you can actually use them at runtime. On the other hand, so if you just do that like this, so we have this protocol that's called eatbread, and it defines that all classes that are eatbread should have the method eatbread. And we create the duck that can eatbread. Then a duck is not an instance of eatbread. It is only at when type checking. But if you really wanted to, you can make this happen by using this runtime checkable decorator. If you add this to your protocol, then suddenly, any object that adheres to the protocol becomes an instance of that protocol at runtime. Note that this isn't completely safe because at runtime, this only checks the existence of the protocol members and their names, and it doesn't check the signatures. So if you implement a method that is called eatbread that actually takes arguments, it would still be an instance of each bread while type checking that wouldn't be the case. So it's not completely safe. But still, you can do something with this without ever having to explicitly specify that duck is of type eatbread. This just magically makes it work. So wrapping up, protocols. As I said, I think this makes all of what we've seen so much more beautiful because there's no need to inherit or register anything. There are no more difficulties combining libraries because, well, it just implicitly makes it work as long as the right methods are defined. If you want to use this at runtime, make sure to decorate your protocol with the runtime checkable decorator and be aware that it's not completely safe, although in practice, you'll probably be fine. And so this, protocols, gives us the best of both worlds. Static type checking whenever you want it and dynamically typed language like Python, but also static checking of dynamic types with protocols. So that brings me to the end. I've convinced you that you do need protocols. Thanks. So you don't have any questions? All right, you want to come up to the mic here? So I have a question. What pitfalls do you see of using prototype because there's a lot of magic in the background, yeah? I don't really think that there's a lot of magic in the background. So type checking in Python is just about comparing signatures, right? And in the case of protocols, you just compare also the existence of members and attributes of a class. And you can just simply define what any object that you expect should look like instead of being explicitly like that object. Now, like I said, when using this at runtime, maybe you should be a little bit careful. But so for type checking, I don't really see any pitfalls. Yeah, thanks for the talk, by the way. The runtime checking, does that also go to the super command? I mean, it's a sort of implicit subtype, but it won't be findable via super, right? It doesn't go to the init of the protocol class, right? No, I don't. I assume not. To be honest, I haven't worked with that at all. I see people shaking their heads, so I guess not. As you don't define any functionality in a protocol, just the existence of attributes and methods, there shouldn't be any functionality inside the initializer of the protocol anyway. Yeah, and the protocol itself, is that an abstract thing, or can you actually work with that direct? So the protocol exists at runtime. But as it hasn't, so there's no implementation of any of the methods or attributes. It wouldn't be very useful, except for maybe, if you want. But it doesn't throw this error like an ABC class, right? I've never tried to instantiate a protocol. It does throw the error. Well, thanks for the answer. One more in-person question, if anyone has another one. Thanks a bunch. Is something a protocol, if it has a property, but the property retains the attribute or not implemented there? Oh, wow. Now we're really getting into the nitty-gritty details. I don't know. I would hope so, but I don't know. Thank you. Did we have any remote questions? OK. All right. If that's it, then please, let's say thanks to our guest. And thank you. Thank you.