python, __slots__, and "attribute is read-only"
Asked Answered
B

5

27

I want to create an object in python that has a few attributes and I want to protect myself from accidentally using the wrong attribute name. The code is as follows:

class MyClass( object ) :
    m = None # my attribute
    __slots__ = ( "m" ) # ensure that object has no _m etc

a = MyClass() # create one
a.m = "?"  # here is a PROBLEM

But after running this simple code, I get a very strange error:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    a.m = "?"
AttributeError: 'test' object attribute 'm' is read-only

Is there any wise programmer who can spare a bit of their time and enlighten me about "read-only" errors?

Belligerency answered 4/5, 2009 at 15:50 Comment(5)
This seems like a needless use case. Why are you doing this? Why aren't you using properties?Par
I want to proff myself from writing a property name with a typo, for eample if i write object.my_prperty = 1 instead of object.my_property = 1 (missed 'o'), python will not show any errors but i will have an logic error in my program. So i want to limit a properties to my predefined set, so only they can be acessed. How i can solve this with properties?Belligerency
generally in python we accept the possibility of misspellings in assignments and writes good tests that would catch superficial and more semantic errors as well. I humbly suggest that if you want to write python, you should consider doing it the python way instead of fighting the language tooth and nail. I'll add that misspellings in my python code have cost me maybe 20 minutes of time in the last 9 years.Inwrap
You could also look into a Python IDE. I usually use IDLE, where (in the shell, not in a .py file) you could do: a=MyClass(), then type a. and it would show you all the attributes. I've also used PyDev, the Python plugin for Eclipse, which does this even in a .py file. There are plenty of others out there. Google or search this site for "Python IDE" and I'm sure you'll find one you like.Alceste
The error should read AttributeError: 'MyClass' object attribute 'm' is read-onlyLettuce
S
49

When you declare instance variables using __slots__, Python creates a descriptor object as a class variable with the same name. In your case, this descriptor is overwritten by the class variable m that you are defining at the following line:

  m = None # my attribute

Here is what you need to do: Do not define a class variable called m, and initialize the instance variable m in the __init__ method.

class MyClass(object):
  __slots__ = ("m",)
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"

As a side note, tuples with single elements need a comma after the element. Both work in your code because __slots__ accepts a single string or an iterable/sequence of strings. In general, to define a tuple containing the element 1, use (1,) or 1, and not (1).

Sylvanite answered 4/5, 2009 at 16:7 Comment(3)
Do you know why if i wrote: slots = [ "m" ] m = 5 Python will overwrite descriptor object with variable and will not use descriptor objects' set() method to set value instead?Belligerency
Because m in this context is a class variable, not an instance variable. In order to utilize the descriptor's set() method, you should assign to the m of an object, not a class. If you want to initialize an instance variable of an object, do it from within the init method as I did in my code snippet.Sylvanite
The key from the docs (which btw moved here) is __slots__ are implemented at the class level by creating 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. So what happens is that (irrespective of the order of defining m and __slots__ ?) when assignment is attempted the set method is not mapped to anything anymore - nor is get but it falls back to MyClass.mLettuce
J
11

You are completely misusing __slots__. It prevents the creation of __dict__ for the instances. This only makes sense if you run into memory problems with many small objects, because getting rid of __dict__ can reduce the footprint. This is a hardcore optimization that is not needed in 99.9% of all cases.

If you need the kind of safety you described then Python really is the wrong language. Better use something strict like Java (instead of trying to write Java in Python).

If you couldn't figure out yourself why the class attributes caused these problems in your code then maybe you should think twice about introducing language hacks like this. It would probably be wiser to become more familiar with the language first.

Just for completeness, here is the documentation link for slots.

Jugular answered 4/5, 2009 at 20:5 Comment(1)
+1 for completeness. As mentioned all over the place, slots is not a bad idea, but requires careful usage. It is more than a safety check and adds much complexity to the implementation that will require to pay attention to how you use the object.Oberhausen
S
7

__slots__ works with instance variables, whereas what you have there is a class variable. This is how you should be doing it:

class MyClass( object ) :
  __slots__ = ( "m", )
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"       # No error
Strafford answered 4/5, 2009 at 16:2 Comment(0)
P
6

Consider this.

class SuperSafe( object ):
    allowed= ( "this", "that" )
    def __init__( self ):
        self.this= None
        self.that= None
    def __setattr__( self, attr, value ):
        if attr not in self.allowed:
            raise Exception( "No such attribute: %s" % (attr,) )
        super( SuperSafe, self ).__setattr__( attr, value )

A better approach is to use unit tests for this kind of checking. This is a fair amount of run-time overhead.

Par answered 4/5, 2009 at 17:6 Comment(4)
But this is more lines of code compared to slots? Why manually do check what can be automatically performed by language.Belligerency
To avoid "very strange error" messages and to avoid having to spend a lot of time debugging inner mysteries of slots. I prefer obvious and simple solutions where secret knowledge is not required to debug.Par
Not being able to add new instance vars is a side-effect of slots not the purpose. (which is a performance optimization).Violoncellist
@Violoncellist — And that, I think, is the heart of the problem.Leopoldeen
L
0
class MyClass( object ) :
    m = None # my attribute

The m here is the class attributes, rather than the instance attribute. You need to connect it with your instance by self in __init__.

Letsou answered 20/10, 2014 at 15:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.