OOP noob question: Mixin properties

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu Dec 13 19:53:04 EST 2012


On Thu, 13 Dec 2012 16:20:51 -0800, Micky Hulse wrote:

> Learning things here...
> 
> In my mixin class (a version I wrote for Django) I had this (indentation
> removed for better list readability):

Indentation is important. Please don't remove it. I've added it back in 
below:

> class JSONResponseMixin(object):
>     def __init__(self):
>         self._cache = False
>         self._cache_timeout = 86400
>         self._cache_key = None
>     @property
>     def cache(self):
>         return self._cache
>     @cache.setter
>     def cache(self, value):
>         self._cache = value
>     @cache.deleter
>     def cache(self):
>         del self._cache
[...]

The use of @property here doesn't give you any benefit (except, maybe, if 
you are paid by the line of code). Better to just expose the "cache" 
attribute directly, and likewise for the others. If, in the future, you 
need to turn them into computed properties, you can easily do so. But for 
now:

class JSONResponseMixin(object):
    def __init__(self):
        self.cache = False
        self.cache_timeout = 86400
        self.cache_key = None

and you're done.


[...]
> ...which works! But, then I looked here:
> 
> <https://github.com/django/django/blob/master/django/views/generic/
edit.py#L198>
> 
> Simply doing this:

> class JSONResponseMixin(object):
>     cache = False
>     cache_timeout = 86400 # 24 hours.
>     cache_key = None

> ...works just as good! Not to mention, it's less verbose.

Yes. But keep in mind that there is a slight difference between what you 
have here and what I suggested above.

In my code above, the cache attributes are defined on a per-instance 
basis. Each instance will have its own set of three cache* attributes. In 
the version directly above, there is a single set of cache* attributes 
shared by all JSONResponseMixin instances.

Will this make any practical difference? Probably not. Most importantly, 
if you write:

instance = Api()  # class includes JSONResponseMixin
instance.cache = True

then the assignment to an attribute will create a non-shared per-instance 
attribute which overrides the class-level shared attribute. This is 
almost certainly the behaviour that you want. So this is a good example 
of using class attributes to implement shared default state.

There is one common place where the use of shared class attributes can 
lead to surprising results: if the class attribute is a mutable object 
such as a list or a dict, it may not behave the way you expect. That is 
because only *assignment* creates a new per-instance attribute:

instance.attribute = "spam spam spam"  # overrides the class attribute


while modifying the attribute in place does not:

instance.shared_list.append("spam spam spam") 
    # every instance of the class will now see the change


Apart from that Gotcha, the use of shared class attributes to implement 
default behaviour is perfectly acceptable.


> I guess I wanted to run my ideas by the Python gurus just to make sure I
> was not doing crazy stuff here...

Seems fine to me.



> The above seems better than taking the more functional approach:
> 
> return self.render_to_response(data, cache, cache_timeout, cache_key)
> 
> Then again, maybe the more functional approach is better for its
> simplicity?

I actually prefer a functional response, up to the point where I'm 
passing so many arguments to functions that I can't keep track of what's 
what. Then I refactor into a class.


> Hopefully I'm not boring ya'll to death. :D

No worries :-)



-- 
Steven



More information about the Python-list mailing list