Issue46550
This issue tracker has been migrated to GitHub,
and is currently read-only.
For more information,
see the GitHub FAQs in the Python's Developer Guide.
Created on 2022-01-27 14:55 by IanLee1521, last changed 2022-04-11 14:59 by admin. This issue is now closed.
Messages (9) | |||
---|---|---|---|
msg411886 - (view) | Author: Ian Lee (IanLee1521) * | Date: 2022-01-27 14:55 | |
Hi there - I admit that I don't really understand the internals here, so maybe there is a good reason for this, but I thought it was weird when I just ran across it. If I create a new class `A`, and set it's `__slots`: ```python ➜ ~ docker run -it python:3.10 Python 3.10.2 (main, Jan 26 2022, 20:07:09) [GCC 10.2.1 20210110] on linux Type "help", "copyright", "credits" or "license" for more information. >>> class A(object): ... __slots__ = ["foo"] ... >>> A.__slots__ ['foo'] ``` If I then go to add a new attribute to extend it on the class, that works: ```python >>> A.__slots__ += ["bar"] >>> A.__slots__ ['foo', 'bar'] ``` But then if I create an instance of that class, and try to update `__slots__` on that instnace, I get an AttributeError that `__slots__` is read-only, and yet it still is updating the `__slots__` variable: ```python >>> a = A() >>> a.__slots__ ['foo', 'bar'] >>> a.__slots__ += ["baz"] Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object attribute '__slots__' is read-only >>> a.__slots__ ['foo', 'bar', 'baz'] >>> A.__slots__ ['foo', 'bar', 'baz'] ``` Maybe there is a good reason for this, but I was definitely surprised that I would get a "this attribute is read-only" error, and yet still see that attribute updated. I first found this in python 3.8.5, but I also tested using docker to generate the above example using docker python:3.10 which gave me python 3.10.2. Cheers! |
|||
msg411889 - (view) | Author: Nikita Sobolev (sobolevn) * | Date: 2022-01-27 15:26 | |
It does not happen on `3.11` (main): ``` Python 3.11.0a4+ (heads/main-dirty:ef3ef6fa43, Jan 20 2022, 20:48:25) [Clang 11.0.0 (clang-1100.0.33.16)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> class A: ... __slots__ = ('x',) ... >>> A.__slots__ += ('y',) >>> A.__slots__ ('x', 'y') >>> a = A() >>> a.__slots__ ('x', 'y') >>> a.__slots__ += ('z',) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object attribute '__slots__' is read-only >>> a.__slots__ ('x', 'y') ``` It also does not happen on `3.9`: ``` Python 3.9.9 (main, Dec 21 2021, 11:35:28) [Clang 11.0.0 (clang-1100.0.33.16)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> class A: ... __slots__ = ('x',) ... >>> a = A() >>> a.__slots__ += ('y',) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object attribute '__slots__' is read-only >>> a.__slots__ ('x',) ``` |
|||
msg411890 - (view) | Author: Ian Lee (IanLee1521) * | Date: 2022-01-27 15:33 | |
@sobolevn - Hmm, interesting.. I tested in python 3.9 which I had available, and I can reproduce your result, but I think it's different because you are using a tuple. If I use a list then I see my same reported behavior in 3.9: ```python Python 3.9.10 (main, Jan 26 2022, 20:56:53) [GCC 10.2.1 20210110] on linux Type "help", "copyright", "credits" or "license" for more information. >>> class A: ... __slots__ = ('x',) ... >>> a = A() >>> a.__slots__ ('x',) >>> a.__slots__ += ('y',) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object attribute '__slots__' is read-only >>> a.__slots__ ('x',) >>> >>> >>> >>> class B: ... __slots__ = ['x'] ... >>> b = B() >>> b.__slots__ ['x'] >>> b.__slots__ += ['y'] Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'B' object attribute '__slots__' is read-only >>> b.__slots__ ['x', 'y'] ``` |
|||
msg411891 - (view) | Author: Eryk Sun (eryksun) * | Date: 2022-01-27 15:48 | |
Please read about augmented assignment [1]. In the REPL, use help("+="). An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once. An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated once. Also, when possible, the actual operation is performed in-place, meaning that rather than creating a new object and assigning that to the target, the old object is modified instead. Unlike normal assignments, augmented assignments evaluate the left- hand side before evaluating the right-hand side. For example, a[i] += f(x) first looks-up a[i], then it evaluates f(x) and performs the addition, and lastly, it writes the result back to a[i]. --- [1] https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements |
|||
msg411984 - (view) | Author: Ronald Oussoren (ronaldoussoren) * | Date: 2022-01-28 10:47 | |
Python's is behaving as expected here (but see below): the slots definition tells the interpreter which attribute names can be set on an instance and "__slots__" is not one of those attributes in your code. "a.__slots__ += ..." will try to set the "a.__slots__" attribute (see Eryk's message for documentation on this) and that results in the exception you are seeing. What surprised me is that A.__slots__ is mutable at all, the value of that attribute during class construction affects the layout of instances and that layout won't change when you change A.__slots__ later on. That is: class A: __slots__ = ['a'] a = A() a.a = ... # OK a.b = ... # raises AttributeError A.__slots__ = ['b'] a.a = ... # still OK a.b = ... # still raises AttributeError I don't know if this should be considered a bug or that this is intended behaviour. |
|||
msg412001 - (view) | Author: Ian Lee (IanLee1521) * | Date: 2022-01-28 15:10 | |
@ronaldoussoren - right, I agree that I think that raising the AttributeErrors is the right thing. The part that feels like a bug to me is that the exception is saying it is read only and yet it is not being treated it that way (even though as you point out, the end result doesn't "work"). Maybe this is something about the augmented assignment that I'm just not grokking... I read the blurb @eryksun posted several times, but not seeming to see what is going on. |
|||
msg412009 - (view) | Author: Eryk Sun (eryksun) * | Date: 2022-01-28 16:54 | |
If the target object of an augmented assignment doesn't support the in-place binary operation, the normal binary operation is used instead. Thus an augmented assignment is implemented to always assign the result back to the target. For an attribute, that's similar to `x.a += 1` -> `x.a = x.a + 1`. For example: >>> dis.dis('x.a += 1') 0 RESUME 0 1 2 LOAD_NAME 0 (x) 4 DUP_TOP 6 LOAD_ATTR 1 (a) 8 LOAD_CONST 0 (1) 10 BINARY_OP 13 (+=) 12 ROT_TWO 14 STORE_ATTR 1 (a) 16 LOAD_CONST 1 (None) 18 RETURN_VALUE Note the STORE_ATTR instruction in the above bytecode. As to __slots__, I think that class construction should store it as a tuple, unless maybe I'm overlooking some use case. |
|||
msg412371 - (view) | Author: Ronald Oussoren (ronaldoussoren) * | Date: 2022-02-02 16:09 | |
W.r.t. augmented assignments, this is a good blog post about how they work under the hood: https://snarky.ca/unravelling-augmented-arithmetic-assignment/ |
|||
msg412394 - (view) | Author: Ronald Oussoren (ronaldoussoren) * | Date: 2022-02-02 20:09 | |
There's a use case for using a dict for __slots__, pydoc will use a dict value for the slots as a mapping from attribute name to attribute documentation. Because of that it is not really worthwhile to transform the value of __slots__ during class definition. |
History | |||
---|---|---|---|
Date | User | Action | Args |
2022-04-11 14:59:55 | admin | set | github: 90708 |
2022-02-02 20:09:27 | ronaldoussoren | set | status: open -> closed resolution: not a bug messages: + msg412394 stage: resolved |
2022-02-02 16:09:04 | ronaldoussoren | set | messages: + msg412371 |
2022-01-28 16:54:05 | eryksun | set | messages: + msg412009 |
2022-01-28 15:10:19 | IanLee1521 | set | messages: + msg412001 |
2022-01-28 10:47:46 | ronaldoussoren | set | versions:
+ Python 3.9, Python 3.11, - Python 3.8 nosy: + ronaldoussoren messages: + msg411984 components: + Interpreter Core, - Library (Lib) |
2022-01-27 15:48:04 | eryksun | set | nosy:
+ eryksun messages: + msg411891 |
2022-01-27 15:33:20 | IanLee1521 | set | messages: + msg411890 |
2022-01-27 15:26:28 | sobolevn | set | nosy:
+ sobolevn messages: + msg411889 |
2022-01-27 14:55:32 | IanLee1521 | create |