We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Easier Classes: Python Classes Without All The Cruft

00:00

Formale Metadaten

Titel
Easier Classes: Python Classes Without All The Cruft
Serientitel
Anzahl der Teile
50
Autor
Mitwirkende
Lizenz
CC-Namensnennung - Weitergabe unter gleichen Bedingungen 3.0 Unported:
Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben.
Identifikatoren
Herausgeber
Erscheinungsjahr
Sprache

Inhaltliche Metadaten

Fachgebiet
Genre
Abstract
When bundling up data, sometimes tuples and dictionaries don’t quite cut it. Python’s classes are powerful tools for data storage and manipulation, but it can take quite a bit of boilerplate code to make a well-behaved Python class. In this talk we’re going to discuss how a well-behaved class should work and take a look at a number of helper libraries for creating well-behaved classes. We’ll first see how to make classes with proper string representations, comparability, iterability, and immutability. Then we’ll dive into helper tools built-in to the standard library and available in third-party libraries and briefly discuss which of these tools makes sense to use with Django’s classes. We’ll look at namedtuple, NamedTuple (not a typo), attrs, and the new Python 3.7 dataclasses. Most of the libraries discussed in this talk are only available in Python 3, so if you’re not using Python 3, hopefully this talk will encourage you to upgrade.
ZeichenketteNichtlinearer OperatorFolientastaturPunktElektronische PublikationGeradeHash-AlgorithmusAttributierte GrammatikTextbausteinPunktGeradeIterationAttributierte GrammatikHyperbelverfahrenBrennen <Datenverarbeitung>CodeBitArithmetisches MittelBimodulSchlüsselverwaltungSchnittmengeMultiplikationsoperatorZeichenketteTypentheorieTupelNichtlinearer OperatorSoftwareentwicklerRechter WinkelHash-AlgorithmusDienst <Informatik>WellenpaketOverloading <Informatik>EinsHilfesystemPaarvergleichDifferenteOrdnung <Mathematik>Delisches ProblemDesign by ContractInterpretiererExtrapolationInternetworkingMultiplikationObjekt <Kategorie>VariableParametersystemDimensionsanalyseSelbstrepräsentationn-TupelDifferenzkernXML
PunktAttributierte GrammatikElektronische PublikationBimodulKrümmungsmaßTupelFontHash-AlgorithmusAttributierte GrammatikSelbstrepräsentationDickeGeradeArithmetisches MittelFehlermeldungObjekt <Kategorie>Punktn-TupelFunktionalPaarvergleichTupelBitTypentheorieCASE <Informatik>NummerungQuick-SortIterationCodeVersionsverwaltungZeichenketteMultiplikationVariableComputeranimation
PunktKrümmungsmaßOperations ResearchFaktor <Algebra>VektorraumZeichenketteMatrizenrechnungKategorie <Mathematik>Mailing-ListeTextbausteinCodeFreewareMultiplikationsoperatorDatensatzFunktionalObjekt <Kategorie>Metropolitan area networkHalbordnungDefaultE-MailChatten <Kommunikation>RechenschieberSchlüsselverwaltungSoundverarbeitungMomentenproblemOrdnung <Mathematik>BitZeichenketteMatrizenrechnungGeradeGüte der AnpassungQuaderData DictionaryMailing-ListeProdukt <Mathematik>TextbausteinCodeDifferenz <Mathematik>SystemaufrufTermMinkowski-MetrikInstallation <Informatik>SelbstrepräsentationIterationComputeranimation
COMService providerDatentypXMLComputeranimation
Transkript: Englisch(automatisch erzeugt)
All right, so we are going to be time crunched, so we're gonna get started.
I'm not really gonna introduce myself because I don't have time. I will say that I teach Python. So I run a Python skill-building service called Python Morsels. I have stickers, if you want one, find me afterward, and I teach on-site with companies. I actually do Python and Django training as well. So that is me. We're going to talk about classes.
I am very tempted to say that I'd like to talk about Pythonic classes, but that word, Pythonic, is a little bit controversial or loaded, and it's definitely a little bit overused. So instead, I wanna say that we're talking about friendly classes. Our goal is to make classes that are friendlier
for other developers to use. So friendly classes have a nice string representation, and they can be compared to each other in ways that make sense. More than that, even, they can overload operators. So you can use plus, minus, any type of operators that make sense on your class.
Maybe iterability, hashability, there's a lot of stuff we could implement in our class to make it friendlier for other developers to use. So the first example we're gonna look at, we're gonna look at two examples here. First one's a month class. We want this class to accept attributes, which means we're gonna need an initializer. We also want this class to have a helpful string representation,
which means we have to have a dunder-repper method. Dunder, by the way, stands for double underscore, and these are essentially special methods that allow us to communicate with Python. This is a contract we have with the Python interpreter. If we want our class to also be comparable using, for example, equality and inequality,
we're going to need dunder-eq on our class. Now, we don't need dunder-ne, we did in Python 2. Python 3, you get that for free if you've got a dunder-eq. If we want to go even further, though, and not just have comparable classes, but classes that are sortable or orderable, we're gonna need to implement less than,
which means we need a dunder-lt method. We also need greater than, though, that's dunder-gt, and there's more than just less than and greater than, there's less than and equal to, that's dunder-le, and greater than or equal to is dunder-ge. So after all of this, our class is going to be pretty big, and it's going to have a lot of code duplication.
So all of these comparison methods, all these dunder methods here, are pretty much the same method. The only real difference between these methods is the operator that they all use. So there is a helper in the Python standard library that will make this class a little bit shorter,
make it a little bit easier for us to create this class. It's called total ordering. It lives in the func tools module. It allows us to make dunder-eq and dunder-lt, and it figures out the other ones from these. It kind of extrapolates from those to figure out how to make those others for us. So this is much shorter than the class that we just had but there's still essentially a lot of boilerplate code.
In fact, I would argue this entire class is just boilerplate code. We don't really need to implement all this ourselves. We could just copy paste it from somewhere on the internet. So we're gonna take a look at how to make this class a little bit better. Before we do that, I wanna look at our second code example. So we made a month class here. I also wanna make a point class.
So our point's gonna be a little bit different from month. We want a three-dimensional point that will also have an initializer because we want to accept three arguments for our three dimensions there. It will also be comparable. Also, it also has a nice string representation and it's comparable. So all three of those we had in our month but we don't want it to be orderable necessarily.
So we don't want our point to be orderable. We do, though, want to be able to do this. So this is tuple unpacking or multiple assignment. We wanna be able to unpack our point object into X, Y, and Z variables. To do this, we need to make our class iterable.
To make our class iterable, we need it under iter method. So we can actually make our own classes work kind of like this, kinda like tuples do in Python. If we wanted to, we could also make our class immutable. What immutable means is that if we try to assign to an attribute on our class,
say p.x equals four, we get an error. So we're setting our class in stone once we've created it. We can't actually change those attributes once we've made our class. For this, this one's a little weird. We need a dunder set adder, which you pretty much never see. And once we've made a dunder set adder, we might decide, sort of like if you give a mouse a cookie,
we need our class to also be hashable. So we want our class to work in dictionary keys. We want it to work in sets. For that, though, we need a dunder hash method. So if we want a well-behaved point class, something that works as you would expect it to, if we were making a dream class ourselves, this might be what it would look like. Now, the code for this isn't actually that long,
at least for each method. It's only a couple lines of code. This is still entirely boilerplate code, though. So some of this might be new to you, especially if you're new to Python. For example, that yields there. That's a generator. You can look up generators and iterators. Google that on your own later. Dunder set adder's kind of weird. Hash is kind of weird.
A lot of these concepts are a little bit odd. This is basically copy-paste code time here. We can take this from the internet, copy-paste it, and we've made a class ourselves that does what we want, which means this is all boilerplate code. This is code that someone else could have created for us. So there is, fortunately, a way to do this.
Again, using a tool in the standard library, it can, that can implement a lot of this for us. It is called NameTuple, and it's built into Python 3. So NameTuple lives in the typing module. To create a NameTuple class, you inherit from NameTuple,
and then you use type hints to define the attributes that you want on your class. What this does for us is it actually makes a class that has an initializer automatically, and it has a friendly string representation, which is what we're looking for. And this class, as a bonus, which is what we're looking for, is comparable,
and in fact, it's even immutable, meaning if we try to assign to an attribute on our class, we'll get an error. By the way, if you're a little bit confused right now, you're going, what is this capital N, capital T, NameTuple? I've seen the lowercase version. That existed in Python 2. That's in the collections module. It still exists in Python 3.
It's a little bit more awkward, though. If you're in Python 3 land, this is what you should use. So out-of-the-box NameTuple pretty much gives us what we're looking for in our point class, at least. But I don't think you should use NameTuple to implement this point. This looks cool, but it's not actually what we want. The reason for that are there are some quirks.
There are some downsides to NameTuple. So NameTupals inherit from tuples, meaning they inherit all of the functionality that tuples have within them. So they can be ordered, which is a little bit weird for our point. We don't actually want our points to be orderable. We want them to be comparable. Less than on two points doesn't make so much sense, though, maybe.
They can also be added to each other, which is actually kind of nifty. It seems like that would be a helpful feature of our points. But when we add two points together, we don't really get the thing we were looking for. We get a tuple that, well, we get two tuples added together, because NameTupals are, in fact, tuples. When you add two tuples together, this is what happens.
The same thing happens with multiplication. You can multiply tuples by numbers. You just get a tuple back. So NameTupals are great, but they're not always the thing you're looking for. They also have a length. Maybe that makes sense for a point. It's sort of weird that it has a length of three, though. NameTupals are wonderful, but they're not always the thing that you should use.
When you're using them, you wanna be careful. You wanna be careful to make sure the thing you're looking for is actually a tuple. Now, we could actually get all of these features that we wanted without these features here. We could remove these features by implementing a lot of methods that just raise exceptions. This is really awkward, though. Finding and removing features that you don't want
is often harder than adding the features that you do want, and that's the case with NameTupals. So don't use NameTuple unless the thing you're actually trying to make should act like a tuple. If you're not trying to make a tuple, don't use NameTuple. So instead of NameTuple, we could use adders.
This is an alternative. This, unfortunately, is a third-party library. It's not built in the Python, which means to get it, we have to pip install it, which isn't a big problem. We can pip install adders. After this, we import adder. We don't import adders. We import adder. So we pip install adders. We import adder. To use it, we use attr.s,
which is a little bit cute, but also a little bit weird. To decorate this class, we set auto attribs equal to true so that we can use type hints, which is just like our NameTuple used there. And after this, we got pretty much the same thing that we get from NameTuple. We've got a class that has a constructor,
an initializer automatically. It has a nice string representation. And it's comparable. You can ask to adder objects here whether they're equal to each other. Unfortunately, we can also use less than and greater than, which is a little bit weird for point. We want to figure out how to disable this feature here. We don't want them to be orderable.
So if we want our point object to work the same way we had it before, when we made it manually, we could do this with this code here. So we're still using adders here, but we've set frozen to true. So our class can't be modified. It's immutable now. And we've made a dunder iter that we made manually to make our class actually iterable,
kind of like our NameTuple was. And we've disabled comparisons. Unfortunately, because we disabled comparisons, we didn't make our own custom dunder EQ and we need to make our own custom dunder hash, which at this point is starting to look like maybe we should have just implemented this class on our own. So the adders library, it's more powerful than NameTupels and it's more flexible than NameTupels,
but it can take a little bit of playing with to get it to do exactly what you're looking for for the simple cases. So adders, in addition, unfortunately it is a third-party library. We have to pip install it to use it. There is actually, though, something that's included with Python that works like adders.
It is data classes. So data classes are essentially a simplified version of the adders library that's built into the Python standard library. To use data classes, you'll import data class from data classes and you'll decorate your class the same way that you can with adders. The syntax that this relies on here
is, again, still type hinting. So like adders, like NameTupel, we've got type hints there, which makes our class also have an initializer, has a nice string representation, and it can be compared using equals. It gives us an error, though, which is what we're looking for when we use less than.
So it can't be ordered. We'd have to turn that on. We'd have to do that ourselves if we want our point object to be ordered. So this point object, it looks close to what we're looking for, but it isn't quite what we want yet. If we're trying to use our point with multiple assignment, for example, we'll get an error, because our point class here is not iterable.
So we can't unpack it into x, y, and z variables. If we want to try to assign to the x attribute on our class, it works, which is actually a problem in our case, because we wanted an error here. We wanted our class to be immutable. So we're expecting to get an error here, and we don't.
We need to make our point class iterable and immutable. To do that with data classes, we can implement a dunder iter method. So this is not a data class-specific thing. We're just doing the same thing we were doing before and making a dunder iter method to make our class iterable, meaning we can use multiple assignment. So this code is the same code we wrote before.
That's self.x, self.y, self.z. We could have copy-pasted this. We could instead copy-paste this. So data classes have an as tuple helper that allows us to get a tuple that contains the objects, or rather the attributes, that are on our data class object instead of specifying those manually, which isn't a huge helper, but it is kind of nifty.
That means if you copy-paste this code, it's gonna work in all data classes. So our point class now is iterable, meaning that we can use multiple assignment, which is great. This is just what we're looking for. It isn't yet immutable, meaning we can assign to attributes and we don't get an error still.
So to make it immutable, we can set frozen to true. So this is very similar to adders. So when we decorate our class with frozen as true there, we now can't assign to attributes in our class. This is exactly what we're looking for. So our class is now iterable, it's immutable,
it's comparable, it has a nice string representation. It's not orderable, which is what we're looking for. It doesn't have a length and it doesn't have all those other weird features that named tuples had that we didn't want. So using data classes, we have made a friendly class in less than 10 lines of Python code.
I think this is pretty cool. Data classes is doing a lot of work for us here that we could have done ourselves, but it's just doing a little bit of that work that we wouldn't have to do on our own. Now I said that data classes is built in to the Python standard library. What I meant by that is that it's built into Python 3.7.
Who is using Python 3.7 in production? Your production server, okay. That's a good number of you. That's about 20%, I think, maybe, rounding up. Notice I'm not raising my hand. I'm not using Python 3.7 in production at the moment. This fortunately is also available as a third-party library, so you can actually pip install data classes
to prepare yourself for that day that you upgrade to Python 3.7. In Python 3, you can. In Python 2, you cannot pip install data classes. If you need to support Python 2, I would recommend using adders. It supports Python 2 and Python 3, even though it's a little bit more awkward for some cases. And if you do need more features than data classes provide,
adders is a little bit more feature-rich in some ways, a little bit more complex to use for the simple cases, more feature-rich in general, though. And data classes, by the way, was partially inspired by adders. That's kind of the timeline that happened here. So the thing that I wanna talk about next, I don't mean to be an infomercial for data classes,
but it's probably an infomercial for data classes. So without any other customization, data classes give us a good string representation. They allow our objects to be compared to each other. We get an initializer, and that's pretty much what we get out of the box. If we wanna turn on other features, we can do so.
So for example, we just saw that we can use frozen equals true to make our data classes immutable. That also, by the way, makes our data classes hashable. So if you wanted to use them as keys in dictionaries or in sets, that's a nice side effect. And it makes our data classes iterable. I mean, if we wanted to use something besides,
sorry, not iterable, iterable's this one. This would make our data classes iterable here so that we could use it in multiple assignment, but we can also, as you see there with a tuple, actually make these iterable in any other way. We could use a list constructor, for example. So something I haven't shown you here, we were talking about that month class earlier.
I kind of dropped that example. I wanna go back to that for a moment because that required orderability. So we can actually set order to true when we're decorating our data class to get lexicographical ordering, which sounds like a really fancy word, but all it really means is the way that tuples, lists,
and all the other orderable structures in Python by default are ordered. So they go the first thing first and then everything after that. So if we want orderability on our class, that's something we can turn on with data classes as well. One more thing I'd like to discuss that is not at all data class specific, kind of like the fact that dunder iter is not actually data class specific,
is dunder methods, dunder methods in general. Almost everything we've been doing in this talk is actually just dunder methods, regardless of whether I've been writing dunder methods. So one of the secrets, really the main secret, I think, to making a friendly class in Python is embracing dunder methods. This is a class here that implements dunder iter,
dunder add, and dunder sub. Dunder add and dunder sub, what they give us are addition and subtraction. The thing that that dunder iter method gives us is, well, really nothing in our case outside of the class, maybe, depending on how we use it, but it makes that dunder add and the dunder sub, the implementation, a little bit easier.
So sometimes you will hear people call dunder methods magic methods. The reason I don't usually use this term is because magic methods are not magical. They're not magical and they're not scary. So I want you to feel comfortable embracing dunder methods in Python. Dunder methods can make friendlier classes.
All right, so before we wrap up, I think we're actually doing okay on time, which is surprising to me. I would like you to consider whether you even need classes. So this is a class that represents a matrix. This class accepts a string that represents a matrix.
So it's got new line characters and spaces, and that represents a matrix here, rows and columns in a matrix. When we construct this thing, the object we get back, that matrix object, has a rows attribute that gives us the rows in our matrix
and a columns attribute that gives us the columns in our matrix, the transpose of this matrix. This class does not need to exist. I say that because you could take this class and replace it with two functions. You could make a matrix from string function, and you could make a transpose function.
So that matrix from string function, when we call it, we call it just the same way we constructed our matrix object before. The thing it gives us back, though, is the same thing as our rows attribute gave us back on that matrix object. The transpose function transposes our matrix, which gives us the columns back. So we've just taken what was a class
and turned it into two functions. You do not always need classes. Friendly classes are great, but sometimes the friendliest classes just don't exist at all. Friendly functions are also a nice thing in Python. So when making classes, I'd like you to first consider whether you even need a class at all, because often you don't.
There is a whole talk on this subject called Stop Writing Classes. It's a bit of an old talk. It is Python 2, but it's entirely applicable, rather, to Python 3. If you do decide, after watching that talk, that you need a class, I'd like you to make a friendly class.
So friendly classes should feel as natural to use as the built-in objects within Python. Lists, tuples, all the things that are built into Python, your class should feel as friendly and as natural as those. Creating friendly classes can involve a lot of boilerplate code, because really it involves writing a lot of dunder methods oftentimes.
Data classes essentially help us write those dunder methods ourselves. They give us those things for free without us having to go out of the way to write them. Unfortunately, data classes are only in Python 3.7. You can pip install them. If you're stuck on Python 2 right now, you can use adders instead.
Or if you need more features than data classes actually provide, you can use adders. I'm actually doing really great on time, so we might even have time for questions here. I would love to help you improve your Python skills. That's the thing that I do for a living. Feel free to find me in the hallway afterward if you'd like to chat about improving your Python skills
or your team's Python skills, or always feel free to send me an email. If folks have questions, I'd actually prefer to do them in the hallway afterward just so we can kind of huddle around and chat about everything we've just seen here. By the way, the slides that I have, there's a whole bunch of extra things in the slides.
You can look into those on your own. I'll tweet them afterward. Thank you.