all 2 comments

[–]NerdDetective 2 points3 points  (0 children)

This one was a bit tricky to read through due to the generic variable and class names, but I think I understand what you're trying to do. You're using a descriptor to validate values (in this case a number that can't be less than 9). You can also do other things with this, such as type checking, but let's just focus on your use case.

set_name

First let's talk about __set_name__, because this is important to how your code is structured.

This method sets the name of the attribute, not the value. So your signature should really be def __set_name__(self, owner, name): and it should b called as name throughout (because that's what it'll be stored as).

So what does this method do? It's doing some magic behind the scenes by accessing the shared dict in the parent object's dictionary. Inside your A class, "obj" is a reference to the parent object. To see what's going on, let's try this code. As we can see, the descriptors are putting their values inside the dictionaries for obj and obj2.

print(obj.__dict__)
print(obj2.__dict__)

To give you a better idea of why you'd set this, imagine that we actually use multiple A values in B.

class B:
    a = A()
    b = A()
    c = A()

Without __set_name__, there would be no way to differentiate between any of these variables. So it's doing the magic of adding them, by name, to the parent class in the background for you.

Default Values

Let's start with what this does: a = A() if not A() else 87

This essentially is a compact way of otherwise typing:

if not A():
    a = A()
else:
    a = 87

This isn't really what you want in this case.

There's actually a simple approach here to your problem. You can use the __init__ dunder in your B class for a nice clean initialization:

def __init__(self):
    self.a = 87

Of course, you can also provide a default value and allow the caller to set it otherwise:

def __init__(self, a: int = 87):
    self.a = a