I’m learning more about how properties work in Python. One thing I’m learning is that a property objects are only evaluated in the context of the parent object they’re attached to.
After my last property trick, I now needed a way to manage groups of color tints. After thinking about it for a while, I ended up with:
gradientLeft = tintedColor(0.4)
gradientRight = tintedColor(0.2)
outlineColor = tintedColor(0.5)
textColor = tintedColor(0.67, 0.6)
defaultColors = (gradientLeft, gradientRight, outlineColor, textColor)
I thought, “I’m brilliant! I’m extending the dynamic nature of ‘property’ to defaultColors”
But unfortunately, I ended up with something ugly instead.
What I got back was:
>>> col.defaultColors (<property object at 0x009D9530>, <property object at 0x01351530>, <property object at 0x013B5378>, <property object at 0x013B53A0>)
That’s not helpful at all! If I tried to access col.defaultColors[0], I got a property object rather than an rgb tuple.
I think the problem is that when I said col.defaultColors[0], the property object didn’t know it was being accessed as a property of an object, so it didn’t unwrap itself. It looked like I was going to have to do that unwrapping myself.
Sure enough, if I look at the property object, I can see what to do:
>>> dir(col.selectedColors[0]) ['__class__', '__delattr__', '__delete__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__ ', '__set__', '__setattr__', '__str__', 'fdel', 'fget', 'fset']
Ah! So all I have to do is call fget():
>>> col.selectedColors[0].fget() Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: getSaturatedColor() takes exactly 1 argument (0 given)
Of course, because it needs a “self” argument. I have one (’col’) but its not being used in the context of the call to fget. Lets try passing it in:
>>> col.selectedColors[0].fget(col) (216.75, 255.0, 255.0)
Ah! So now I need a way to unmangle each element in the list, at the time the list is accessed.. what could I possibly use build the list then? I know, property()!
and thus we have tupleProperty. like tintedColor, we need to essentially curry some state to the property call, so we need to wrap it with a function.
def tupleProperty(*args):
def demangledTupleGetter(self):
return [val.fget(self) for val in args]
return property(demangledTupleGetter)
now, we can redefine defaultColors as appropriate:
defaultColors = tupleProperty(gradientLeft, gradientRight, outlineColor, textColor)
what’s particularly neat is that we can change the hue and the right properties will be called:
>>> col.defaultColors [(153.0, 255.0, 255.0), (204.0, 255.0, 255.0), (127.5, 255.0, 255.0), (50.489999999999995, 153.0, 153.0)] >>> col.eventHue = 0.0 >>> col.defaultColors [(255.0, 153.0, 153.0), (255.0, 204.0, 204.0), (255.0, 127.5, 127.5), (153.0, 50.489999999999995, 50.489999999999995)]
I think the only bummer here is that I had to dive into the internals of python in order to make use of fget() and untangle this duple/property dependency.