 Levels, levels? Is it loud enough? All right. Ahoj Praho, stracený syn se vrátil. Je mi obrovskostí, že můžu po přednáškach celým světa konečně mluvit k vám, v měm rodišti, kruh se konečně uzavřil, cítím hodně pocitů. Hi, I'm Hinek. I really care about classes. I care about them so much, that I wrote a PPI package to make writing classes easier. You might have heard of it or not, it's called errors. If you haven't heard of it, it's fine, but I also helped creating a module that is based on errors, which is called data classes, which you probably have heard of. And thanks to errors and data classes, writing classes has become boring, which is good, because writing more classes is not a drag anymore, and it doesn't affect your design decisions anymore. Before, sometimes we were like, this should be a new class, but it's too much hassle to implement all the dunder methods. So let's just add another attribute or method to the old class and make it work. And this leaves you more time for the much more interesting topic of relationships between classes. And that's really what subclassing and composition are. It's two ways in which two classes can relate to each other. A dog is an animal and a dog has a collar. Asking two programmers, which is better, is like asking two Spaniards if it's Sevilla or Sevizia. It can get very loud. I've witnessed it last night. So the more controversial one is, of course, subclassing. So quick primer. We say sub is a subclass of base class base, and subclass sub consists of everything from class base, to potentially base's own base classes, which we do not know about when we look at sub. This relationship between two classes is called an is-a relationship. So you can say sub is a base, which makes no really sense, but you can say a dog is an animal. Now base classes are also known as superclasses, but that sounds ambiguously close to subclasses, especially the weird accent like mine, so I'm going to stick to base classes. Sorry. On Python, there is no protection for base classes from the subclasses whatsoever. This is by design. When you subclass a base class, you have access to any attribute on a base class and to any method on a base class, both reading and writing. So sub can do anything it wants to base, and again, this is by design. There is no encapsulation between sub and base. So far so obvious, but it goes both ways. Like the self argument is the exact same object for both methods here. So if you don't control the base class, and the base class decides to use an attribute with the same name as you are using, well, LOL. And if you do control the base class, you have to always keep that on mind, that there's this bi-directional relationship, because you can break either by changing the other. And in Python, you can kind of prevent this by using the Dumbled underscore prefix, but nobody really uses that anymore. We've somehow decided to use the consenting adult principle, which to my taste is a little bit lewd for an engineering principle, but more importantly, you can't consent to something you don't know about. This is like asking someone to promise to not get mad before telling them what it is about. This is not how consent works with anything. And what all this means is like that the base class has no control over sub, sub has no control over base, but both can modify each other at runtime by trying to modify themselves. So we say that base classes and sub classes have a bi-directional relationship, and it sounds harder to reason about, and it actually is very much so. Admittedly, there are situations where bi-directional relationships can be really useful, it can make code shorter and everything, but I'm going to argue that it shouldn't be the default in your design decisions. Now on the other hand, composition, if a class A wants to use data or code from class B, it has to keep an instance of B as an attribute. So this is an has-a relationship. A doc has a caller. And if class A wants to access something on class B, it has to say so explicitly. And it's not controversial if I say that reading this is much clearer than the subclass example. Because the relationship of the classes, A and B, is expressed in code. The location of X is also expressed in code. You can see it, it's right there. Now the downside, of course, is that if you instantiate on A, you also have to instantiate a B. And whenever you access attributes on B, two attribute lookups instead of one. But it also means that you can easily replace B with a different class. For example, when you're testing or when you're in development and you don't want to actually send out the emails to your example customers. And often, you can also share B among the classes. For example, a thread-safe connection pool. You just need one and you can pass it in all the other classes. So this leaves us with a question why do we subclass in the first place? There's a few mechanical reasons that I will briefly touch on, but in Python we mainly subclass to share code. And there are various popular techniques and patterns, like both in a standard library and beyond, like template subclassing, dynamic dispatch. And they are so widespread that they feel indispensable at this point. But are they indispensable for writing real-world software nowadays? It sure felt like that for a very long time. And it's easy to forget today how incredibly dominant Java used to be for everything. A language doesn't even have functions. It's the best they can do are static methods. And you can see that all over the Python standard library, all the package, all the modules that were born when Java was raining. Most notably, the unit test module or the logging module, which both even inherited Java's camel case names, which we usually don't do anymore. But after decades of object-oriented programming, being a synonym for class hierarchies, something changed. We've got two extremely popular imperative programming languages that issue subclassing completely. So clearly the answer is no. You do not need subclassing to write great software. Now, rust and go prove it. Which for me personally is a great relief because I was always low-key suspicious of subclassing because it always felt like it's kind of alluding me a little bit. There's some complexity that I don't completely grok. And I always thought maybe it will come with time, with experience. Maybe if I do it often enough, it won't feel as weird. But it looks like much smarter people than I decided otherwise. What complexity do I mean? So my main problem with subclassing is that it adds another whole dimension when you're trying to reason about program flow, which I like to call the goose chase program flow. Trademark pending. Giving you example of this is a talk by itself, but I'm sure everybody of you have been there when you try to trace across class hierarchies what method is actually being called. Easy example, like quick. Which one is called? Honestly, I don't know. I keep forgetting whether the MRO, the method resolution order goes left to right or right for left. And this is easy mode because you see all the classes, all at once and it's not like long listings that are distributed across multiple files. So what about now? This is a fun mystery, but this is still easy mode because it's all there, you can see it all. But you have to know the rules to make sense out of it. This code is not self-explanatory. It is mental overhead that you have to employ when you are reading this code. And in practice this is not what you see. You see this. And C can be a class. But C can also be a huge diamond hierarchy hidden somewhere. Like you don't know. So jumping up and down hierarchies is such a common pattern in Python. It has a name and it's called template up-classing and I absolutely hate it. And I'll use a listing from my favorite Python book to show you a more realistic example and how to get rid of it. So despite everything it follows if you're interesting in building maintainable applications around databases and web service and whatever, get this book. You can read it for free online too. It's a great gift from Harry and Bob for the Python community. I mentioned almost all my talks and I still mean it. Like I wish it existed ten years ago. Like my life would have been so much better. And before you started crucifying me for talking shit about other people's work I was a technical reviewer for this book and I actually did bring it up. Quite vehementally in fact. But my threats of plash violence went unheeded. So you can see they only added as an exercise to the reader. So let's go together and see if we can do better. And what we do is we will implement a special kind of repository. A repository in a nutshell is an abstraction over storage. So instead of writing SQL or using the ORM directly in your business code you have a class that offers methods to get, modify and list objects to and from a database or whatever storage you're using. Usually using vanilla classes which are classes they don't even know that they are coming from the database. They are very lightweight. And this is cool because it isolates all database interactions into distinct classes. So which of course makes even more sense with Asing IO. And as a side effect it makes your app a lot more testable. But we don't implement a regular repository. We are going to implement a tracking repository that additionally keeps track of the objects it has seen while getting or setting in a set. Why? Don't matter. It's an example in a book. If you want to know, read the book. I recommend it. Now the interesting part we want is that we want to take a regular repository and add tracking to it. So let's see what the book is suggesting. Like this is classic template subclassing. It starts with an abstract class that cannot be instantiated by itself. You must subclass it and fill in missing parts. In this case it's an underscore add that takes a product and stores it and underscore get that returns a product base on a stuck keeping unit also known as sq. Now a class that does that that it subclasses and implements those method is called a concrete implementation of an abstract class. And this so far is not bad at all ABCs were original take on abstract data types and interfaces and I know about ZOPE interface you don't have to tell me. Now the interesting thing that makes its template subclassing is that we additionally share methods with the concrete subclasses methods that use the missing ones. So in this case we've got add product which is a public method as you can see and it calls self underscore add and adds the product to our scene set and then there's get by sq and tries to get it using underscore get and if it's if it finds it it also adds it to the scene set that's it. Now one thing to notice is that the lower abstraction level like add and get live in a subclass so depending how you look at the hierarchy it is it is higher in a class hierarchy which is weird. Now let's look at the books concrete implementation of this which uses sets for storage. Now since the data storage is transient transient we will call it a fake tracking repository and the and we subclass the abstract tracking repository. While initializing we have to call superdunder in it because the abstract base class used its own dunder in it method to initialize their own storage and then we make the class concrete by adding the missing methods. This shouldn't be very surprising. I mean it's odd that they chose to implement it on a set which I would not I would use a dictionary but whatever and it's also weird to implement private methods in a concrete class. If you look at a concrete class you are not looking at the API that you're going to use later. Now you would you would instantiate this and call the public methods from the base class and this is my first annoyance like this it's weird. The second annoyance even bigger is that we have to remember calling super superdunder superdunder in it which introduces bidirectional flow between those two classes and there's no way to express this requirement in code you have to use documentation but people don't read documentation and if they read it they forget about it documentation like this cannot be verified documentation grows stale now one reason why it gets so weird and ugly in this case is because we use kind of specialization but it goes the wrong way it's the lower level of code that we want to be able to replace and so this abstract tracking repository is actually a regular repository plus tracking and you want to be able to replace the regular repository part on the right side that is like implementing an animal class by subclassing a dog maybe you can make it work but it's going to be awkward like in this case so if you'd be adding higher level features to a lower level class that would be a regular specialization and I will talk about it later because there is a value in that but as it stands with this with this direction of our dependencies we have to use other tools so how can we make this clearer I've already hinted at it we need to invert the dependency and it's super easy if we switch to composition so what does this abstract repository actually want to do it wants to add and get products from another repository and keep track of those products so what varies here it's always the big question what varies here and it's a storage so this logic lives in a lower level repository as we've seen so let's start inverting from the bottom and extract the part that varies into an interface and I could use an abstract base class like in a book and it would look like this this is the low level abstract part it's extracted into a separate interface except that the methods now are the public API for this concrete class so there are no underscores anymore now if someone wanted to implement this one they would just usually subclass it and implement add and get and this is called nominal subtyping because you have to tell Python hey, this class is a subtype of abstract repository okay now subclassing this uppercase ABC is optional it's popular because it's shorter but you could also use a meta class for it which I personally prefer because it's more explicit and one interesting tidbit about using ABCs and actually type hints is that a class that subclasses an abstract class and does not implement all the methods that are required cannot be instantiated it will explode at runtime however the type hints are ignored they don't matter at all at runtime on the other hand if you are using types and type checking like mypy will catch both so that makes like the runtime overhead a little bit unnecessary that you have for checking those interfaces but it's the original pretype hints behavior and it allows you to define interfaces without using type hints which I understand are still a little bit controversial in a community now without going into type theory being a subtype and being a subclass does not have to be the same thing as you will see next because in the brand new Python 3.8 we've got something that is better to define interfaces and I'm gonna use it now it's called a protocol and a protocol lives in a typing module so the typing hints are not optional and you define an interface like this and to the type checker any class that has an add method and a get method with exactly these signatures is considered a subtype of repository and if you decorate the class with runtime checkable it even works with instance checks again you do not subclass this you just implement the interface Python will know this is a subtype and as long as you don't use its instance checks it's also absolutely no runtime override from this because it's only used statically when type checking now this is called structural subtyping and it's been popularized by go and in my opinion is a much more natural way to deal with types in dynamic languages like Python and my favorite feature is that it allows me to define interfaces that the classes that implement them don't even know about I can like define a little sliver of an API that I need from a third party class and then the type checker tells me where I'm using it correctly like if the class actually implements it and where I'm using only the parts that I've been promised without running the code so I would say this is a straightforward interface so let's implement it using a set like the example in the book and we are off to a good start because there's no subclassing in the class definition we just initialize our internal storage there is no super that we could forget and now we add the methods we add an add looks exactly like before does it fulfill our API protocol takes a product returns none check let's do get next also looks exactly the same takes a string returns a product or none check two now take a moment to take in a beauty and elegance of this because this class is not coupled at all to any other class all it is coupled to is a set basically right it has a single purpose and it is adding and retrieving data from a set nothing else with the abstract base class approach it would look almost the same it would look like this except now your subclass abstract repository which has the legitimate upside of being explicit about the intent of implementing an abstract repository that is something to be weighted like this has an upside alternatively you can also use abstract base classes without any subclassing at all because every abstract base class has a method called register this is still nominal to subtyping because you still tell Python that this repository is an abstract repository but you don't have to subclass and since this register method takes a class you can also use it as a class decorator so that works too so it's again still nominal subtyping but it shows that you can use abstract base classes completely without any subclassing whatsoever so there's even for this mechanics you don't need it now we have a fully functional repository let's make it tracking so this is our tracking repository it has no base class which is good it has two attributes our repository that's private because it's none of the business of the user and a set called seen as before now both add product and get by skew look almost the same as before like in a book except now we can see that add and get live on a repo attribute instead of being hidden somewhere in a class hierarchy like you can see right away where they are coming from and I find even in this trivial example it is radically easier to understand on one glance what is going on and what the relationships are this is like the smallest font I've ever used on a slide and I've removed the type hints to make it more succinct but I hope you can see that yes it's a little bit more verbose it's more lines of code but we have very clear dependencies here right those two classes are independent from each other and can be tested as such and each initializes only itself there is no runtime overhead from using the interface because we use a protocol and the static type checker will ensure that we use underscore repo correctly like we don't need to run the code to verify that and this is like my key point the structure of the code is expressed in a code not in some comments or whatever or it doesn't have to be deduced by looking at hierarchies and trying to remember the MRO I mean look how great the control flow is it's just one way right instead of jumping like hands and forth through class hierarchies and there's even a fancy word for this and that's a strategy pattern repository is a plagable strategy we inject the behavior we want to customize which makes it easy and safe to replace it by a different implementation if you are switching the storage or if you want to write tests it only has to fulfill the API protocol that we have defined and this alleviates many reasons for monkey patching and testing and to bring in a hot take because this talk is not controversial enough yet you shouldn't need monkey patching neither of modules nor of classes to be able to test the vast majority of your code only the lowest level of low level code at the very fringes of the architecture should need monkey patching to simulate errors or something like that everything else should be possible to parameterize within composition and what it means in other words is that testability is a direct function of your architecture of your software right there's no magic feature or package that will make it easy to effectively test a tangled mass or like tightly coupled software it's always going to be hard there's nothing that helps you with that learning to write testable software is learning to design software that is good for testing which is in my opinion good design by itself it is not about learning cool testing tools like they are really cool testing tools like pythes or hypothesis but it starts with the architecture not with the tools or with the tests now this small aside we get our first take away subclassing is super powerful but it requires knowledge and self-discipline from you instead of explicitly reading the relationships between the object within your code you have to interpret the hierarchy through a lens of rules rules like the MRO or how super works like one of the most upvoted questions like overflow is like what is even super for then there's like tons of rules to follow to not end up with a chaotic mess like the avoidance of diamond hierarchies risk of substitution principle the open closed principle there's a lot to keep in mind you have to have it on your mind on the other hand composition mechanically forces discipline on you sometimes to the point of clankiness but it leaves less room for errors by you and it expresses all relationships in explicit straightforward code which means that sometimes you have to type a little more it means that it gets awkward to have bi-directional relationships but it's a feature because those relationships are not free but in a sense of complexity they have a coast and they should be used thoughtfully and they shouldn't happen by accident now these problems around confusing control flows is a special case of a larger problem which is like the muddling of namespaces and namespaces are great there's even a pep for that well it's not a pep for that it is in a pep it's in pep 20 where Uncle Timmy bestowed us that namespaces are one great honking idea let's do more of those and subclassing is literally the opposite of that because you take two or more classes with perfectly good namespaces each and you unite them into one breaking encapsulation between them completely as I've said before there is no protection whatsoever so what you get is namespace pollution you lose the control over your classes API surface which is a huge deal for public APIs whether inside your company or on PyPI because whenever the base class changes which happens even in a standard library all the time even in ways that technically do not change the public API it will affect your subclass in ways that you cannot predict because you cannot tell the future like if it adds or removes attributes or methods so you will feel it and solve with your users especially if you get like named clashes so this doesn't affect you only with public APIs though so I want to show you why I've chosen the class decorator approach for others instead of subclassing and why it's also been used taking up by data classes so a quick refresher first a class decorator works like a function decorator it's a callable that gets a class and returns a class then you apply it to a class and then the class has that attribute but it's really just syntactic sugar for this and a class decorator is like precise surgery because the decorated class only gets what you explicitly put there which are usually fewer things than you get everything by default so let's compare and look at an instance API surface of a simple Django model which is straight from the tutorial that uses a subclassing based API and I'm specifically using Django as an example here because it's huge and it's less likely to step on anyone toast if everybody wrote it, nobody wrote it with it of a data class yeah, sorry so that carries the same data it has those two fields and it uses a class decorator now, data class first it's exactly two attributes from data classes and the rest is your fields which gives you a grand total of 32 attributes only two extra which is the data classes fault the rest is from Python now let's look at the Django or a model yeah, it's 91 so you get 59 extra attributes that you can't do anything about and try finding something in there this is no shadow Django by the way this is a problem that all ORMs have there's a meta class somewhere because they have to keep track of changes and synchronization and everything like that and this is why I'm not a fan of these heavy objects inside of my business code I'm not saying you're not supposed to use ORMs at all like, I don't, but that's not my point my point is that this is bad to have in business code and that's the reason why I personally love the SQL Alchemy core API together with repositories and vanilla address classes or data classes if I have to this gives me fine-grained control it gives me lightweight objects that are quick and light it gives me clear IO boundaries like, if you access the wrong attribute on an ORM you might get a network call from it it can't happen if you just return a vanilla data class and it allows me to write business code in terms of regular classes which I find much more pleasing but it is more typing more typing but again I don't get accidental IO by accessing the wrong attribute anyhow, if you find yourself in a situation like this with your own code and you want to improve the situation but you don't want to restructure your whole project and encapsulate everything private so everything starts with an underscore into a private namespace attribute that lives in a base class and this is what IRIS and data classes do too like data classes you've seen those two attributes those are pretty complex things hidden behind that but they don't pollute the whole class like this so of course you can say a Django model is too performant sensitive to instantiate an extra class with all its private members every single time or it is more convenient to have everything on self but you have to acknowledge that it makes other things harder like testing, finding, understanding and that's a discussion to be had not whether a subclassing is morally reprehensible or what the best practice is so this leads to the one big take away here like it's a trade off of course on the one side we have readability composition is explicit subclassing gives you implicit behaviors you get less information from just looking at a class definition like how deep and wide is the hierarchy where is this attribute coming from or looking at a method called attribute lookups you have no idea, you can't tell then there's control subclassing breaks encapsulation set many sub consequences some of which I've mentioned before now on the other side having everything on self is convenient until the number of everything gets really high you can't find anything in the everything anymore still a trade off also subclassing gives you brevity which sometimes can make the code more readable if it's like you can see it all on one page that can be good sometimes and then of course there's performance typically with composition you instantiate more objects you have more attribute lookups which in a real hot loop can be a problem usually it isn't but can be but that is not new readability versus performance is as old as programming if statements are faster in polymorphism tangled optimized code is faster than clean layers function calls slow code down especially in python but we still write functions to have a more understandable code and this is what software engineering is waiting trade offs against each other but for myself readability and thus maintainability waste the most I don't think that I'm alone with this like software engineering is programming integrated over time which means that you will keep coming back to the code you wrote to fix bugs to add features and readability is a core requirement for long term maintainability and that makes maintainability an extremely important property for everything except like one of scripts but there is no such thing as one of script like they all become load bearing eventually of course the importance of maintainability only grows over time the longer you didn't look at the code the harder it is for you to understand the code and especially in a brave new world of generative AIs the value of writing code will go down there's no question about that people are hitting the tap key a lot nowadays while the value of understanding your code will go up AI will make writing code a lot easier it will probably get better by serving us but AI is not going to debug your spaghetti code in a foreseeable future if anything you need clearer architectures so you can judge their very confident suggestions better for correctness and of course you can legitimately disagree by the way I weigh my tradeoffs I'll be the first to admit that my perspective is very skewed by the fact that I broke for a small company where I have a lot of responsibility and sometimes don't touch projects for years and that I maintain too many open source projects that I also don't have time to look at the whole time right but you have to always acknowledge the consequences of your decision so my goal for today is not to stop you from ever subclassing again my suggestion based on my experience is to err on the side of composition but I'm not your dad and I probably won't have to read your code so my goal for today is making you more cognizant of this tradeoff and draw your own conclusions whatever they are but you have to be able to argue for them and my ultimate hope is that we'll get more nuanced discussions in the future and just to be very very clear functions that call other functions with some arguments and return a value and then look at that value and call other functions are absolutely the best in regards to clarity it's like going from 3D chess to checkers and I've noticed that over time the number of classes that I've read went up thanks to errors and data classes but the number of methods I'm writing went down and I increasingly resort to functions especially when more than one class is involved which helps me to break dependencies between classes and that's usually very beneficiary if you want to test them so I'm going to give you an example imagine you have two classes A and B now you need a method from both do you put the method on A or do you put a method on B this by the way shows that even with composition you can end up with a tangled mess I've already given it away it's neither this isn't Java, let's write a function not only does this decouple A and B it also allows for easy testing because you're literally passing A and B into it so you can pass it a fake or a mock or whatever you want so nowadays for me it's almost always a function that's coordinating a bunch of classes and of course if you really need a class which happens sometimes you need to pass things around you just write a third one and use the magic of composition it is not as simple as a function but it has the same upsides A and B are decoupled and making this an easy decision is why I've wrote errors and help making data classes in the first place if it's too much work you would have just chosen A and B now having said all this let's tackle the elephant in a room why anyway we've enumerated many reasons for the famous composition of our inheritance principle and we've seen you don't need subclassing however Python is not go or rust and assuming we at EuroPython want to write Python that looks like Python and we don't want to fight the language at every corner to subclass sometimes even when you are not chasing the last 1% of performance or are too lazy to type a little bit more now if you follow some rules it's gonna be okay though so one reason is of course mechanics you need more control over your classes well you probably need a meta class like with the ORM that I've show you so there you go it's gonna be hard to use meta classes without subclassing it is what it is I do not use meta classes at all like in none of my projects I use a meta class so you can't live without it another example of course are exceptions where even the position in the hierarchy is actually an important and useful feature and it's the only pythonic error handling we have neither rust nor go have exceptions and some have tried to carry over their error handling to Python but the results are unlikely to go mainstream from what I've seen so and it's even okay with multiple inheritance because if it makes sense for ontological reasons why not? as long as you start building diamonds it's fine there's usually a little to no code or data sharing and yeah that's very few headaches by default and now with multi exceptions we have some really great tools so thumbs up don't be afraid now let's go to the one thing that gets closest to what one understands under subclassing and it's specialization and I'm going to channel who despite being a Rubyist is one of the best object oriented programming teachers I know the OG subclassing in small talk back in the day was only about specialization and then we lost our way and tried to do other things with it specialization is about taking a general class and make it more specific and the more specific part can be sometimes intuitive because it means that you have to take the whole thing with all its contracts and add more contracts they don't contradict the original one this is the list of substitution principle and the classic example of course is what is more specific for programming a square or a rectangle look it up the answer will surprise you so by this kind of subclassing can be worthwhile in python and I would like to demonstrate using a simple example that makes me think about this more we want to model email addresses and there's two types there's a mailbox that has a database ID the address and a password hash and a forwarder that shares ID and address with the mailbox but it has a list of forwarding targets instead of a password hash because it's a forwarder now these examples are not supposed to be executable code I'm alluding all the data class or address decorators so don't be surprised and this is a very simple case and you should absolutely leave this as it is because it's not worth deduplicating here anything you can even create a type that covers both of them and the type checker will know that an email adder only has the overlap of the two classes which is an ID and an address this is true for more complex classes to though because if you start deduplicating too early you may end up with bad abstractions you don't want that you want to see the data first you write it all out, see it, feel it get a feeling for the shape of the data and only then when you're done when you see it all in front of you then start trying to save some lines of code it's especially important when you are thinking about data which source you do not control like database tables maybe you are coming from somewhere else and the first instinct is to like try to replicate the data into your classes but it doesn't always make sense so first write it out look at it feel it because if you do it too fast you end up with abstractions that are very hard to get rid of from a code base once they are there it's a problem and copying a little bit of code hands and forth it's not that big of a deal so don't commit haphazardly start verbose but for the sake of science we are going to try to unite them anyway one way is by adding a type field and this used to be a pretty popular solution like we have an email address it has a type which is either mailbox or forwarder and then we add both and they are either none or they have a value now this is a very bad solution because it uses comments to explain contracts of those fields and static typing also will get very obnoxious because all the fields can be none so the type checker will yell at you again these designs used to be really popular especially in the times when writing more classes was a drag we just added more and more to the one class we had ending up with so called god objects alright thank god those times are passed so let's go and do the composition thing so we define a class with the basics that are shared and then we add it to the other two classes now the naming is awkward right and that's a signal that something is off usually when you cannot give a thing a name it means that you did not fully understood the thing it's like a general rule also like it has an email address this is like super weird but it works it's composition it must be good valid solution but still it's clunky right so mailbox really is an email address so let's do the dirty thing we still define an email address with basic data again but now we subclass so this looks a lot more idiomatic to me as a subclassing hater this is much better for my eyes it is as little as the word means pythonic but the tradeoffs are still valid like python enforces no rules between them so you have to know them and you have to follow them so you don't end up with a mess but as long as the methods of email adder only works on email adder and the subclasses only mind their own business and as long as you don't go to town with the depth or even the width of the hierarchy and the relationships only go in one direction you've got a very efficient and readable solution for storing data whose structure actually is hierarchical and all this brings us back to the cross-eyed rodents because now everybody who knows Go wanted to tell me how unfair I am can relax because Go has no subclassing but it has embedding which allows for this and this only the syntax is that you simply make it part of the structure and you don't give it a name now you have to be explicit when you're constructing your objects but then you just can access them like they are part of the original structure look Ma, only one dot but there's no super there are no hierarchies, there's no going sideways just limit subclassing to the part that is not terrible the part that I just showed to you you take a class and add something to it you specialize it more of a syntactic sugar to save some typing than anything else and I'm not a big fan of all the Go decisions but this is a good pragmatic one and to me this has become kind of epiphany and a heuristic how I think about subclassing in Python like can I do it in Go then I will do it if not now so what have you learned today despite everything remember you've write Python idiomatic Python has a certain shape there's no shame in that using some types for error handling so you don't have to subclass exception is not gonna improve your code focus on a shape of your data before you deduplicate your data write it all out be verbose so you can really see it and feel it and finally it's all a tradeoff there's a right or wrong only this aspect is more important to me than this one for me subclassing is like an emergency hedge to bend through third party classes to my will and a nicer way to model hierarchical data but your decision can be different and that's fine but as it's absolutely everything you have to do the work to make these decisions if all you know is templates subclassing and not the strategy pattern you're gonna use templates subclassing and think that you're being pragmatic and those composition hipsters are not at that point you are not waiting tradeoffs your horizon is too narrow but I hope I've set you on the right path today a speaking of today that's all I have for you today if you want to go deeper and this link will take you to my blog post about the same topic I still had to leave out a lot of things although I'm almost out of time so follow me on the bird and elephant side sketch your domains from viral media if you speak German Sam Hineck, děkuju obam honě thank you for the great talk I couldn't agree more frankly unfortunately schedule doesn't allow for questions come and fight me yeah, exactly please give another round of applause to Hineck