Reputation: 5242
I'm looking for a shorthand to add common property decorators to classes.
class Animal:
def __init__(self):
self._attributes = {}
class Dog(Animal):
@property
def color(self):
return super()._attributes.get('color', None)
@color.setter
def color(self, value):
if value is not None:
super()._attributes['color'] = value
else:
super()._attributes.pop('color', None)
class Cat(Animal):
@property
def color(self):
return super()._attributes.get('color', None)
@color.setter
def color(self, value):
if value is not None:
super()._attributes['color'] = value
else:
super()._attributes.pop('color', None)
class InvisibleMan(Animal):
pass
I'm looking for the easiest way to "package" the color property so I can assign it to Dog and Cat, but not InvisibleMan. Something like this (although in actuality there will be ~8 such properties and ~15 such classes)
class Dog(Animal):
def __init__(self):
super().__init__()
includeColorProperty(self)
Upvotes: 0
Views: 47
Reputation: 2063
Have you considered descriptors, instead of a decorator?
In a nutshell, descriptors give you fine-grained control over attribute storage. (In fact, the property
decorator builds a descriptor under the hood!) Here are some Python docs that may be helpful.
Anyway, sticking with your pattern, a descriptor that manipulates _attributes
would look something like this:
class Color:
def __get__(self, obj, objtype=None):
return obj._attributes.get('color')
def __set__(self, obj, value):
if value is None:
obj._attributes.pop('color', None)
else:
obj._attributes['color'] = value
where obj
is a reference to the Dog
instance, et al.
(Note the __get__
and __set__
methods match your getter and setter, respectively.)
Then, plug the descriptor into your classes like this:
class Animal:
def __init__(self):
self._attributes = {}
class Dog(Animal):
color = Color()
class Cat(Animal):
color = Color()
class InvisibleMan(Animal):
pass
You can see in this example the behaviors you're looking for are preserved: instances maintain their own _attributes
, and InvisibleMan
has no color
:
>>> d1, d2 = Dog(), Dog()
>>> d1.color = 'blue'
>>> d1.color, d2.color
('blue', None)
>>>
>>>
>>> x = InvisibleMan()
>>> x.color
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'InvisibleMan' object has no attribute 'color'
Personally, I also find this a bit easier to read when many properties are involved, as you mentioned is true in your case. Want to know what properties are available for a given type? They're listed out right at the top, no surprises.
Upvotes: 1
Reputation: 16242
You have about options.
Firstly, multiple inheritance:
# this is the best way to do things if lots of stuff is invisible
class HasColor:
# getter and setter go here
class Dog(Animal,HasColor):
...
OR
# This is probably the best one way to do things, if not many things are invisible
class Invisible:
@property
def color(self):
raise AttributeError("very meaningful message")
class InvisibleMan(Invisible,Animal): # THE ORDER HERE MATTERS!!
etc
Option 2 would be to override the getter and setter in invisible man:
class Dog(Animal):
...
class InvisibleMan(Animal):
@property
def color(self):
raise AttributeError("very meaningful message")
Bonus option:
If you want to turn invisibility on and off on an instance then you want to do something else. I'm not sure if you want this but:
class Animal:
cloaking_on = False
@property
def color(self):
if self.cloaking_on:
raise AttributeError(etc)
etc
Then you can have a way to set cloaking on and off and make all Cats invisible by default.
Upvotes: 0