In the previous lesson, I introduced you to multiple inheritance. In this lesson, I’ll show you different bits about class internals and how you can use them to make more powerful code. In part one of this multi-part course, I showed you how the
@.setter decorators work to let you write code that makes methods behave like attributes.
00:30 If you implement their interface, Python will give you some functionality. Many things that seem like magic are actually implemented as protocols underneath, meaning you can write your own classes that behave the same way.
You can write a class that abstracts a positive integer using the descriptor protocol. First off,
.__set_name__() is called when a descriptor is instantiated, and it’s passed the name of the variable it is being assigned to. For example, if you read a line of code that says
radius = PositiveInteger, instantiating a
PositiveInteger object and storing it in a reference called
radius, the name
radius gets passed into
The core of what you do with the descriptor protocol is get and set values.
.__get__() is the get part. This method is past the object that the descriptor is attached to and the class of the descriptor itself.
That’d be the
radius and the
Circle class in the example I just mentioned. I’m not really sure why they bother passing in the class as you can always get it through
self.__class__, but it’s part of the protocol, so it has to be in the signature.
.__dict__ is the dictionary associated with all Python objects where Python stores the objects attributes. It feels like there’s a lot going on in this line, but all it’s doing is getting the value from the associated instance object.
There are situations where this won’t work, but I’ll come back to that edge case in a later lesson. All right, you’ve got the getter. Now the setter. This signature takes the instance object associated with the value and the new value to set it to. The purpose of
PositiveInteger is to make sure that it can only store positive integer. This line enforces that.
Circles are, so yesterday. Let’s define an
Ellipse. The ellipse has two radius-like things: a width and a height. Mathematically, there are a half dozen ways to specify an ellipse, but width and height are commonly used in drawing libraries, so let’s stick with that. Instead of specifying focal points, I want both my width and my height to be positive integers, so I use our newly minted class.
This is how you register a descriptor. Inside
.__init__(), When I go to assign a value, the descriptor on the class is getting invoked, but because the descriptor’s code actually stores the contents on the object, the end result is a value associated with the object, not the class.
04:37 My brain hurts a little when I think about this. You don’t really need to understand this. When you use descriptors, you can treat it like magic. If you build a descriptor and assign it in the class, the end result is stored values with the same name on the object.
04:50 It isn’t magic though. There’s no extra special stuff going on here. From the interpreter’s point of view, it’s a class attribute that references a particular kind of instance object, which implements the descriptor, which when assigned to passes the value through the descriptor protocol to the owning instance object. Does it spoil the magic to know how the trick works?
.height. That assignment triggers
PositiveInteger because it’s registered as a descriptor, as a class attribute. This gets called twice, once for
.width and once for
.height. Now, when I access the
What you put in
.__slots__ is a tuple with the names of the attributes your class uses. Technically, it can be a list instead of a tuple, but that has extra overhead as well, and you’re never going to edit the contents. When
.__slots__ is present, the underlying
.__dict__ gets removed.
07:48 I played around with this for a while trying to figure out if I could get the two things to work together. I found some code on the internet that showed a way to make it work, but it had two problems.
08:06 Python 3 seems to be a bit more aggressive about the constraint. In theory, you could write a descriptor class that stored values in the descriptor class instead of on the instance object. As there can be multiple instance objects, you’d need to store that value in a nested dictionary with the top level keyed on the instance and the second level storing the values for that instance.
I haven’t tried this, but there’s no reason it wouldn’t work that I can think of. But if you get what I’m laying down, the short version of it is, I just said, reimplement
.__dict__, but somewhere else.
Become a Member to join the conversation.