zopefoundation / ExtensionClass

Metaclass for subclassable extension types
Other
4 stars 10 forks source link

PURE_PYTHON: Name check for setting attributes is applied to all classes, not just C classes #36

Open jamadden opened 3 years ago

jamadden commented 3 years ago

(Tested with 4.5.1, but this goes back to the very oldest code in this repo.)

The Python version of ExtensionClass applies the check for special methods to all classes: https://github.com/zopefoundation/ExtensionClass/blob/14c7e107f08b777ae9a1a1c325b3f12bdab7c17c/src/ExtensionClass/__init__.py#L210-L219

But the intent in the C version is to only apply the check to other built-in types: https://github.com/zopefoundation/ExtensionClass/blob/14c7e107f08b777ae9a1a1c325b3f12bdab7c17c/src/ExtensionClass/_ExtensionClass.c#L477-L486

This leads to a noticeable difference in PURE_PYTHON mode, such that code that works with the C extension fails in PURE_PYTHON. Compare the C version:

>>> os.environ.get("PURE_PYTHON")
None
>>> from ExtensionClass import Base
>>> class X(Base):
...     pass
...
>>> X.__eq__ = 'added by decorator'
>>>

to the Python version:

>>> os.environ['PURE_PYTHON'] = '1'
>>> from ExtensionClass import Base
>>> class X(Base):
...     pass
...
>>> X.__eq__ = 'added by decorator'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/lib/python3.10/site-packages/ExtensionClass/__init__.py", line 214, in __setattr__
    raise TypeError(
TypeError: can't set attributes of built-in/extension type '__main__.X' if the attribute name begins and ends with __ and contains only 4 _ characters

I'm not sure how to reliably detect built-in classes in Python, so I'm not sure how to fix this?

d-maurer commented 3 years ago

Jason Madden wrote at 2021-10-1 08:51 -0700:

... I'm not sure how to reliably detect built-in classes in Python, so I'm not sure how to fix this?

I have hit the same problem in branch experimental_C3_MRO and did something to work around it (I think I removed the restriction alltogether).

The problem is actually deeper: when we want that the "PURE_PYTHON" version behaves as the "C" version, then it is not important (in the Python version) whether the type at hand is actually implemented in "C"; instead, it is important whether it would be implemented in "C" for the "C" version. And that is very hard.

jamadden commented 3 years ago

instead, it is important whether it would be implemented in "C" for the "C" version.

I'm not sure I agree with that. This is all happening at runtime, and at runtime, you care about what (sub)classes you actually have, not what you theoretically might have at some other time.

I think I removed the restriction alltogether

That seems like a reasonable approach to me, especially if we just pass everything we don't specifically care about (__of__, etc) through to type.__setattr__: it can worry about whether or not some attribute should be writable and whether or not some class is a heap type or not.

d-maurer commented 3 years ago

Jason Madden wrote at 2021-10-1 11:08 -0700:

instead, it is important whether it would be implemented in "C" for the "C" version.

I'm not sure I agree with that. This is all happening at runtime, and at runtime, you care about what (sub)classes you actually have, not what you theoretically might have at some other time.

Then, when you switch between Python and "C" implementations, you will get different behaviors (because with "C" implementations, you will have more types implemented in "C").

-- Dieter