How to use `__slots__` with initialization of attributes?
But this is giving error Python 3 while working fine on Python 2:
ValueError: '_fields' in __slots__ conflicts with class variable
.
While you didn't get an error in Python2 at class creation/compile time like in Py3k, if you try to actually set the value of _fields
, you get AttributeError: 'C' object attribute '_fields' is read-only
:
>>> class C(object):
... __slots__ = ('_fields')
... _fields = set()
...
>>>
>>> c = C()
>>> c._fields
set([])
>>> c._fields = 'foo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object attribute '_fields' is read-only
>>>
Also, see the fourth note in the slots documentation:
__slots__
are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by__slots__
; otherwise, the class attribute would overwrite the descriptor assignment.
Wrt your modification:
I change the code to
class B(object): __slots__ = ('_fields') def __init__(self): _fields = set()
The modified class B has a _fields
inside __init__()
, not self._fields
so it's just a local variable in init, and not accessible anywhere else in the class. Change that to:
class B(object):
__slots__ = ('_fields')
def __init__(self):
self._fields = set()
To correctly initialise _fields
, do this:
class B(object):
__slots__ = ('_fields')
def __init__(self, _fields=None):
if not _fields:
self._fields = set()
Wrt further experimentation:
In class D, __slots__
is a variable only inside __init()__
. It's not the (special) class variable D.__slots__
; or even the instance variable self.__slots__
. So it has __dict__
.
Class A has none, so also has __dict__
.
Class C has __slots__
correctly.
Related videos on Youtube
ViFI
Updated on September 15, 2022Comments
-
ViFI almost 2 years
I read through the main answers on usage of slots and it has given me an idea of how and where to use
__slots__
.Now, I am porting a code from Python 2 to Python 3 which is similar to as following:
class B(object): __slots__ = ('_fields') _fields = set()
But this is giving error Python 3 while working fine on Python 2:
ValueError: '_fields' in __slots__ conflicts with class variable
.I change the code to
class B(object): __slots__ = ('_fields') def __init__(self): _fields = set()
and it works fine. My query is, is it even the correct change ?
As i got from original code, I guess it is saying that don't use
__dict__
for saving memory or faster access or whatever reason but at the same time is also trying to specify the type of attribute_field
as set(). Can the change above be the right way to say it or it can have some side effects.
Further Experiments: I experimented further with following variants (on Python 3):
import pdb class A(object): a = set() ''' class B(object): __slots__ = ('a') a = set() ''' class C(object): __slots__ = ('a') def __init__(self): a = set() class D(object): def __init__(self): __slots__ = ('a') a = set() if __name__ == '__main__': #pdb.set_trace() x = A(); print(dir(x)) #y = B() z = C(); print(dir(z)) z1 = D(); print(dir(z1))
and it gave following output.
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a'] ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'a'] ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
We can see that only C object shows correct footprint i.e. no
__dict__
and only__slots__
. Isn't it what ideally we would want ? Any explanation on__weakref__
would also be helpful.Also on Python 2, both B and C object show same footprint. Based on that should C be the right way to put it as it is compiling on both Python 2 and 3 as well.
-
ViFI over 7 yearsThanks. But did we want to say the following in
__init__
insteadself._fields = _fields if not None else set()
-
aneroid over 7 years
if not _fields:
checks_fields
in boolean context. So if_fields
isNone
then that expression evaluates toTrue
and the lineself._fields = set()
gets executed. It could also be done asif _fields is None: self._fields = set()
. Btw, in the example in your comment,not None
is always True. You could put it asself._fields = _fields if _fields is not None else set()
or the more Pythonicself._fields = _fields if _fields else set()
. Caveat: an emptyset()
evaluates to False so use an appropriate check... -
aneroid over 7 yearsCaveat: an empty
set()
evaluates to False so use an another check ifset()
is okay butNone
is not. Also, don't intialise function or methods with mutable types as default argument values; likeset
,dict
,list
, etc. Another example here.