1

I was playing around with the risks of class-level variables in Python, and I thought that the risk of lists as class-level variables can be solved with tuples, for example, an empty tuple.

Take:

class BaseClass(object):
    default_sth = []

    def foo(self):
       for sth in self.default_sth:
           pass

This is risky because:

class SubClass(BaseClass):
    pass

a = SubClass()
a.default_sth.append(3)

print(BaseClass.default_sth) # [3]

Using None instead of empty tuple (tuple()?) could be instead dangerous because it fails when iterating over it.

Because of immutability, I think this is a good option, but I don't think I've seen anybody talk about this around anywhere. Do you see any flaws with this reasoning? Is it pythonic?

jleeothon
  • 2,907
  • 4
  • 19
  • 35
  • Hmm. Is it possible that you're looking for class-private members, supported in Python through [name mangling `__leading_double_underscore` names](https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references)? These are basically class level attributes that are unique to the actual class, and can't be (easily, resp. accidentally) changed by subclasses. See Raymond Hettinger's talk [Class development toolkit](https://www.youtube.com/watch?v=HTLu2DFOdTg) for a detailed explanation (name mangling starts at around 33:24). – Lukas Graf Jul 11 '14 at 20:37
  • Also see [this answer](http://stackoverflow.com/a/1301369/1599111) for a quick example on how name mangling works. – Lukas Graf Jul 11 '14 at 20:39
  • MMm that's an interesting concept. The problem with that is that I *do* want the variable to be overridable in subclasses, though, without changing the value in the original class. – jleeothon Jul 14 '14 at 14:52

3 Answers3

2

You're confusing class variables with instance variables (which is easy to do since in the absence of an initialized instance variable, the lookup finds the class variable). In this case, you have explicitly said that default_sth is a class variable, when you mean to have a default value for an instance variable. Use this instead:

class BaseClass(object):
    def __init__(self, sth=None):
        if sth is None:
            sth = []

    def foo(self):
       for sth in self.sth:
           pass

You might balk a little at having a "hard-coded" default, but you're just adding verbosity by trying to have a separate name for the default value. Just comment and document your __init__ method to indicate what sth is and what its default value should be if no other value is passed. Don't try to make the default itself configurable unless you have a good reason to.

As far as immutability goes, only the tuple is immutable, not the name that refers to it. There's nothing stopping someone from using your original code and saying

BaseClass.default_sth = []

and you're right back where you started, despite making default_sth a tuple to start with.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • That's an interesting approach. Do you know if there are any performance differences with doing it in class definition against initialization? – jleeothon Jul 14 '14 at 14:56
  • 1
    `self.foo` will always look for `foo` in the object's dictionary, and if that lookup fails, then it checks the class's dictionary. So using class variables as a default for what is really an instance variable imposes the overhead of a failed dictionary lookup on each use. – chepner Jul 14 '14 at 15:02
  • Practically speaking, though "explicit is better than implicit", so if something is meant to be an instance variable, initialize it, rather than relying on a fallback to a class variable. – chepner Jul 14 '14 at 15:11
1

I think the problem with using a tuple instead of list is that usually people are choosing to use a list because they actually want list semantics; you can't append to a tuple, sort it in place, remove items from it, etc. If you want your class-level list attribute to not be shared with sub-classes, the sub-class can just make its own copy:

from copy import deepcopy

class SubClass(BaseClass):
    default_sth = deepcopy(BaseClass.default_sth)  # now SubClass has its own copy.

Then your example will output [], which is the behavior you would prefer. If you use a tuple in BaseClass, your example will throw an AttributeError (because it tries to call append on a tuple), and the code would need to change to this to do the equivalent of an append:

a = SubClass()
a.default_sth = tuple(list(a.default_sth) + [3])

Which is kind of a pain to write and not very efficient since you're doing a bunch of copying to convert from tuple -> list -> tuple.

dano
  • 91,354
  • 19
  • 222
  • 219
  • I hadn't thought somebody might want list semantics. I'll take that into account! +1 for the `deepcopy` thing. – jleeothon Jul 14 '14 at 15:03
0

Mutable default variables are well-known fart in otherwise so beautiful Python.

E.g.

http://eli.thegreenplace.net/2009/01/16/python-insight-beware-of-mutable-default-values-for-arguments/

"Least Astonishment" and the Mutable Default Argument

http://pythonconquerstheuniverse.wordpress.com/2012/02/15/mutable-default-arguments/

Solutions include

  • Initialize everything in __init__ - don't use class-level variables unless you really mean global variables

  • Pass in None and convert it to list when accessed first time

Community
  • 1
  • 1
Mikko Ohtamaa
  • 82,057
  • 50
  • 264
  • 435