-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
array.array should remain immutable: add Py_TPFLAGS_IMMUTABLETYPE flag #88074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi Victor, Sorry for making this a deferred blocker. I recall that we had a brief discussion somewhere about an accidental change to the array.array type -- this is now a heap type (Py_TPFLAGS_HEAPTYPE is set), and as a consequence it is no longer immutable. In 3.9 this is an error: >>> import array
>>> array.array.foo = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'array.array'
>>> But in 3.10a7 it passes: >>> array.array.foo = 1
>>> array.array.foo
1
>>> I would like this type (and other types that have been or will be converted to heap types) to remain immutable. How can we do that? I think we may need a new flag bit meaning "built-in type, immutable". This bit should not be inherited of course. What do you think? (Feel free to close if this is a duplicate -- I couldn't find where we discussed this previously, sorry.) |
Hi, Guido. I am interested in what have been broken in fact? or it's a defined behavior?
Aggreed. Adding a new |
FWIW, I was the one who converted arraymodule to use heap types in 75bf107 (GH-23124) in January. Sorry 'bout the accidental change of behaviour.
+1. See attached PoC patch. As far as I understand, type flags must be explicitly inherited in Objects/typeobject.c, no? Should all previous static types be immutable, or are there exceptions? |
Adding an extra flag seems like the sensible thing to do for 3.10 Longer term, we should decouple immutability from whether something is a heap type. Erland, could you turn your patch into PR? |
Certainly, but despite the issue title, this issue mentions all types that have been converted to heap types. Should we apply the new immutable flag to _all_ heap types in the stdlib? Guido:
|
Guido:
*Many* static types have been converted to heap types in Python 3.9 and Python 3.10. Is there a rule to decide which types should be mutable or not? All types implemented in Python are mutable, unless the very few which use slots. By the way, some cases can be inherited or not. Do we care about that? Example: $ python3
Python 3.9.2 (default, Feb 20 2021, 00:00:00)
>>> def f(): pass
...
>>> class MyFuncType(type(f)): pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type 'function' is not an acceptable base type |
Let me propose a flag name: Py_TPFLAGS_IMMUTABLE. Py_TPFLAGS_IMMUTABLE implies that setattr(type, name, value) fails. A type has a dictionary, but it cannot be modified with type.__dict__[name] = value, since type.__dict__ returns a read-only mapping proxy. Currently, the limitation of not being able to modify a built-in type is arbitrary, it's not a technical limitation. It is implemented in type_setattro() which is the default implementation of tp_setattro: PyType_Type.tp_setattro = type_setattro. Not having Py_TPFLAGS_HEAPTYPE flag implies "immutable". I suggest to add a new Py_TPFLAGS_IMMUTABLE flag and modify PyType_Ready() to use the flag if Py_TPFLAGS_HEAPTYPE is not set: if (!(flags & Py_TPFLAGS_HEAPTYPE)) {
flags |= Py_TPFLAGS_IMMUTABLE;
} You can try with this change: diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 254d12cc97..4bd02d40c1 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3875,13 +3875,6 @@ static int
type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
{
int res;
- if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
- PyErr_Format(
- PyExc_TypeError,
- "can't set attributes of built-in/extension type '%s'",
- type->tp_name);
- return -1;
- }
if (PyUnicode_Check(name)) {
if (PyUnicode_CheckExact(name)) {
if (PyUnicode_READY(name) == -1) It becomes possible to modify built-in types: $ ./python
Python 3.10.0a7+ (heads/master-dirty:cdad2724e6, Apr 22 2021, 14:44:44) [GCC 10.2.1 20201125 (Red Hat 10.2.1-9)] on linux
>>> str.x=1
>>> str.x
1
>>> del str.x
>>> del str.__repr__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __repr__ |
Maybe name it Py_TPFLAGS_IMMUTABLETYPE? Just IMMUTABLE can mean that instances of the type are immutable, but we want to make a type itself immutable. |
Is there a full list of types converted to heap types? There may be other issues related to differences between heap and static types.
|
"TPFLAGS" stands for "type flags". So IMO adding "TYPE" suffix is redundant. If tomorrow, we want to add a flag for immutable instance, we can add a new Py_TPFLAGS_IMMUTABLE_INSTANCE flag. On the other side, TYPE suffix is consistent with other flags :-) /* Set if the type object is dynamically allocated */
#define Py_TPFLAGS_HEAPTYPE (1UL << 9)
/* Set if the type allows subclassing */
#define Py_TPFLAGS_BASETYPE (1UL << 10) |
Victor, I've updated PR 25520 so PyType_Ready always sets Py_TPFLAGS_IMMUTABLE for static types. |
Is there going to be a separate PR to actually *set* the IMMUTABLE flag on all built-in type objects? There are many of those. |
Guido, I’ll prepare a separate PR for all converted built-in types, and you can decide what to do with it when consensus is reached :) There are approx. ~90 of these types. Not all of them are added to their respective module dicts though, but I would add the flag nonetheless, as it simplifies creating and reviewing the patch, and it does not have any performance impact. |
BTW, slightly related, AFAICT:
|
I've prepared a diff using grep & sed (see attached patch). Let me know if you want it converted to a PR. I've excluded the test and the _xx extension modules. |
Does the test suite pass for apply-to-all.diff? Also, quite a hornet's nest you've uncovered (about __class__ assignment). Would that be fixed better with the IMMUTABLE flag? |
No, multiple tests fail. First test_distutils fails, then during the re-run, test_multiprocessing_forkserver, test_multiprocessing_spawn, and test_pdb fail.
Yes, indeed. Apropos, I see that bpo-24991 is still open.
That would be a change of functionality, given that we apply the immutable flag to all built-in types. Currently __class__ assignment is allowed for heap types, and it has been so for many years. I feel reluctant to add apply-to-all.diff to the PR right now. |
The more I read about these issues, the more I feel reluctant to apply PR 25520. Why change the behaviour/traits of heap types without a proper discussion (or PEP)? Applying this PR feels like an arbitrary change. All the related issues and mailing list discussions seems to end without consensus, and fundamental questions (what is an immutable type? which types should be immutable? why is it a problem that a type is mutable?) seems to be avoided all the time. And, as Victor points out in msg391596 (and which has also been pointed out by others in other discussions):
FWIW #1, in pypy3, datetime.timedelta.foo = 1 is ok; in python3 (3.7-3.10) it's not. FWIW #2, in Python 3.9, functools.partialmethod is implemented in Python, and functools.partial is implemented in C as a static type:
$ python3.9
>>> import functools
>>> functools.partial.foo = 1 # is this a problem?
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'functools.partial'
>>> functools.partialmethod.foo = 1 # is this a problem? If I'm only bringing noise into the discussion, feel free to remove me from the nosy list. |
Notice this issue is marked as "deferred blocker" it won't block this beta release (beta 2) but it will block the next. |
PR 26351 adds the Py_TPFLAGS_IMMUTABLETYPE type flag to all types converted to heap type during 3.10 development. |
In inherit_slots() in Objects/typeobject.c, Py_TPFLAGS_HAVE_VECTORCALL inheritance depends on if the base type is a heap type or not. This aligns with PEP-590[1]: Heap types never inherit the vectorcall protocol because that AFAICS, inherit_slots() should now use Py_TPFLAGS_IMMUTABLETYPE to decide if Py_TPFLAGS_HAVE_VECTORCALL can be inherited, and the PEP should be updated. |
+1-- |
Is anything left here? |
Should the immutable flag also be applied to the heap types converted in and before Python 3.9 before closing this issue? |
I would say yes. Do you have a compiled list of what's left? |
These types now lack the Py_TPFLAGS_IMMUTABLETYPE flag: ## Types converted in Python 3.9
## Types converted before Python 3.9
|
Yeah, I think these should be changed, but lest give a bit of time for other core devs to mention what they think |
I don't think that Python 3.9 should be changed. It's too late. IMO this issue is not important enough to introduce a new type flag in a minor Python 3.9.x bugfix release. I suggest to close this issue. |
In Python 3.11, 68 heap types have the Py_TPFLAGS_IMMUTABLETYPE flag:
|
3.9 has now entered security-only mode; suggesting to close this issue. |
Right, I closed the issue. |
FYI a question related to this new flag has arisen: https://discuss.python.org/t/immutable-types-with-mutable-bases/17764 |
Py_TPFLAGS_IMMUTABLETYPE
can now inherit the vectorcall protocol #27001Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: