I can't believe I've gone so long without learning about descriptiors. They do make a few things I've seen make sense. @Carberra a video on generics would be appreciated
Amazing, I have never seen these in the wild. Couple of times I've used properties in convoluted ways where this would be better, config validation is a good example. I just want to be pedantic and nitpick that "value is not negative" is distinct from "value is positive" because the latter is ambiguous on whether 0 is included.
Very true, thanks for pointing that out! "Value just be greater than 0" is probably least ambiguous, rids the need for positive and negative completely.
you can have `__init__(self, type_ :Type : int, f_val : Callable|None = None)` on the descriptor class to make it more generic. i.e the descriptor can get all sorts of arguments telling it how to behave. Knowing how to use that makes even more useful.
Do you have other exemples where descriptors are used ? I saw your video on pydantic for validation and was wondering if there were better uses cases since a module already covers it
The config and the validators are the only two places I've personally used them, but source 1a in the description has quite a few examples of other implementations.
You used cached property on a function that takes only self as parameter, that means you can never change the setting, the cache would just last forever(or perhaps in some nondeterministic way expire?)
This ^^ but also, in this context, you'd actually _want_ that, especially if you were fetching AWS SSM parameters instead of environment variables, where you wouldn't want to make a request every time you accessed the descriptor.
If you've only got one attribute you need to validate you can just stick with property, doing it like in the video is better when you need to validate multiple things, or you need something more generic, as you can share logic across validators.
I've always wondered, why do they use the term "descriptor"? We're talking about getter and setter dunders but there's also delete thrown into the mix. Is it because they handle attributes?
@Carberra More complete name: Attribute Descriptor. The terminology did not originate with python. JavaScript has property descriptors for a long time, and they work in much the same way (except you don't define a class, but supply the "methods" to Objet.defineProperty(...). I don't know where the idea originated for sure, but a similar idea underlies the Common Lisp meta object protocol. (See the 1991 book "The Art of the Metaobject Protocol" by Gregor Kickzales , Jim des Riveres, and Danny Bobrow, all of whom were at Xerox PARC at the time.). This is part of the 1994 ANSI X3J13 standard (and later, an ISO standard). It may be inspired by features of Smalltalk, but I never did much with Smalltalk, so I can only suspect.)
For the age validator, for example, why not store the actual age inside the descriptor, like `AgeValidator.value`, instead of on `Person._age`? I tested it here and it seems to work. Is it an anti-pattern of some sort? Are there side effects I'm not considering?
You could very well do that. I did it the way the Python docs do, and doing it like that allows me to show off that you can access the parent object of the attribute being validated, but from what I can tell, there are no disadvantages to having the private attribute within the validator itself, unless you needed to access that attribute somewhere else within the class.
I'm bit confused what happens if there's class attribute and instance attribute with the same name, age in this case. I was under the impression that instance attribute just overwrites the class attribute but in this case it seem to "inherit" the type (AgeValidator) from class attribute.
If there's a class attribute, it also serves as an instance attribute -- I might have misspoke slightly during the explanation, but essentially, instances can access class attributes from self. I don't think you can have both at once.
Correct me if I am wrong: In the example 'self.age = age' - you actually try to resolve it - find self.age - first in instance, then in class. In this case you find it as class attribute and after that you try to assign the value to it based on its defined double underscore __set__. And I believe only if the resolution would fail, then you would actually create new object as instance attribute and assign value to it.
I can't believe I've gone so long without learning about descriptiors. They do make a few things I've seen make sense.
@Carberra a video on generics would be appreciated
Yeah same here! I only learned them in anger recently myself haha. A genetics video is planned!
Great video. The Validator example is immediately useful.
Amazing, I have never seen these in the wild. Couple of times I've used properties in convoluted ways where this would be better, config validation is a good example. I just want to be pedantic and nitpick that "value is not negative" is distinct from "value is positive" because the latter is ambiguous on whether 0 is included.
Very true, thanks for pointing that out! "Value just be greater than 0" is probably least ambiguous, rids the need for positive and negative completely.
you can have `__init__(self, type_ :Type : int, f_val : Callable|None = None)` on the descriptor class to make it more generic. i.e the descriptor can get all sorts of arguments telling it how to behave. Knowing how to use that makes even more useful.
Interesting. I hadn't thought about this before.
Thanks for the video, helped a lot 🔥
great videos!! what is your vscode theme?
Thank you! I've got a video explaining the whole lot in the description.
fyi the validator that accepts a generic type T should have a __set__ that accepts parameter value of type T not int
Ah shoot, good catch!
I was doing something similar with properties a while ago and really wish I'd known this before. Would've made it a lot more readable.
Do you have other exemples where descriptors are used ?
I saw your video on pydantic for validation and was wondering if there were better uses cases since a module already covers it
The config and the validators are the only two places I've personally used them, but source 1a in the description has quite a few examples of other implementations.
Seeing this makes me glad that I keep things simple.
This python reminds me of C#, which is odd.
C# is simple tho and the docs are better
You used cached property on a function that takes only self as parameter, that means you can never change the setting, the cache would just last forever(or perhaps in some nondeterministic way expire?)
All properties must depend only on self. They are values that can be computed from the state of an object, they are not methods.
This ^^ but also, in this context, you'd actually _want_ that, especially if you were fetching AWS SSM parameters instead of environment variables, where you wouldn't want to make a request every time you accessed the descriptor.
How much better are validators in this instance over doing setters (and @property for getters)?
If you've only got one attribute you need to validate you can just stick with property, doing it like in the video is better when you need to validate multiple things, or you need something more generic, as you can share logic across validators.
Would it be possible to use descriptors to create a `Constant` class?
You should be able to, yes! It would only work on classes though, nothing top-level.
@@Carberra tried it, but I am having some issues (so is chat GPT, so don’t worry tho. I’m not alone in this)
I've always wondered, why do they use the term "descriptor"? We're talking about getter and setter dunders but there's also delete thrown into the mix. Is it because they handle attributes?
Your guess is as good as mine! Though I'm not actually what _I'd_ call it, given the opportunity.
@@Carberra Well don't leave me wondering! What would you call it?
Soz missed a word, meant "not actually sure what".
@@Carberra :)
@Carberra
More complete name: Attribute Descriptor. The terminology did not originate with python. JavaScript has property descriptors for a long time, and they work in much the same way (except you don't define a class, but supply the "methods" to Objet.defineProperty(...). I don't know where the idea originated for sure, but a similar idea underlies the Common Lisp meta object protocol. (See the 1991 book "The Art of the Metaobject Protocol" by Gregor Kickzales , Jim des Riveres, and Danny Bobrow, all of whom were at Xerox PARC at the time.). This is part of the 1994 ANSI X3J13 standard (and later, an ISO standard). It may be inspired by features of Smalltalk, but I never did much with Smalltalk, so I can only suspect.)
For the age validator, for example, why not store the actual age inside the descriptor, like `AgeValidator.value`, instead of on `Person._age`? I tested it here and it seems to work. Is it an anti-pattern of some sort? Are there side effects I'm not considering?
You could very well do that. I did it the way the Python docs do, and doing it like that allows me to show off that you can access the parent object of the attribute being validated, but from what I can tell, there are no disadvantages to having the private attribute within the validator itself, unless you needed to access that attribute somewhere else within the class.
It smells like hours of painfull debugging
It’s not. And once done once, you can reuse time and again.
One thing that confuses me... You used `os.environ.get()` to retrieve the setting, but how did that get into the OS environment in the first place?
os.environ is a built-in dictionary that contains all the environment variables. Just import os and you have access to it.
I'm bit confused what happens if there's class attribute and instance attribute with the same name, age in this case. I was under the impression that instance attribute just overwrites the class attribute but in this case it seem to "inherit" the type (AgeValidator) from class attribute.
If there's a class attribute, it also serves as an instance attribute -- I might have misspoke slightly during the explanation, but essentially, instances can access class attributes from self. I don't think you can have both at once.
@@Carberra Thanks for the clarification
Correct me if I am wrong:
In the example 'self.age = age' - you actually try to resolve it - find self.age - first in instance, then in class. In this case you find it as class attribute and after that you try to assign the value to it based on its defined double underscore __set__. And I believe only if the resolution would fail, then you would actually create new object as instance attribute and assign value to it.
Good video thanks for sharing!
10:10 AI is right. Non-negative is not the same thing as positive. Non-negative can be zero. One might be zero years old.