Files
RustPython/extra_tests/snippets/builtin_object.py
youknowdot bc495a8b30 Call descriptor __get__ attributes without pre-binding (#9043)
Extract the Python 3.14-compatible descriptor slot dispatch from #8954. Call the raw type attribute with descriptor, instance and owner, preserving ownership and recursion handling. Extend the existing Python snippet for staticmethod, classmethod, callable-object and ordinary __get__ replacements.

Assisted-by: OpenAI Codex:model identifier unavailable
2026-10-10 11:56:48 +00:00

196 lines
5.4 KiB
Python

class MyObject:
pass
assert not MyObject() == MyObject()
assert MyObject() != MyObject()
myobj = MyObject()
assert myobj == myobj
assert not myobj != myobj
object.__subclasshook__(1) == NotImplemented
assert MyObject().__eq__(MyObject()) == NotImplemented
assert MyObject().__ne__(MyObject()) == NotImplemented
assert MyObject().__lt__(MyObject()) == NotImplemented
assert MyObject().__le__(MyObject()) == NotImplemented
assert MyObject().__gt__(MyObject()) == NotImplemented
assert MyObject().__ge__(MyObject()) == NotImplemented
obj = MyObject()
assert obj.__eq__(obj) is True
assert obj.__ne__(obj) is False
assert not hasattr(obj, "a")
obj.__dict__ = {"a": 1}
assert obj.a == 1
del obj.__dict__
d = obj.__dict__
assert isinstance(d, dict)
assert len(d) == 0
try:
obj.a
assert False, "AttributeError expected"
except AttributeError:
pass
# Value inside the formatter goes through a different path of resolution.
# Check that it still works all the same
d = {
0: "ab",
}
assert "ab ab" == "{k[0]} {vv}".format(k=d, vv=d[0])
from testutils import assert_raises
class AttributeHook:
existing = "class"
@property
def absent(self):
raise AttributeError("descriptor miss")
@property
def invalid(self):
raise ValueError("descriptor error")
def __getattr__(self, name):
if name == "refused":
raise AttributeError("hook refusal")
return "hook:" + name
class InheritedAttributeHook(AttributeHook):
pass
hooked = InheritedAttributeHook()
hooked.instance = "instance"
assert hooked.instance == "instance"
assert hooked.existing == "class"
assert hooked.missing == "hook:missing"
assert hooked.absent == "hook:absent"
with assert_raises(ValueError):
hooked.invalid
with assert_raises(AttributeError) as caught:
hooked.refused
assert caught.exception.__context__ is None
assert not hasattr(hooked, "refused")
assert getattr(hooked, "refused", "default") == "default"
# Changing __getattribute__ must invalidate the generic lookup path.
InheritedAttributeHook.__getattribute__ = lambda self, name: "custom:" + name
assert hooked.existing == "custom:existing"
InheritedAttributeHook.__getattribute__ = object.__getattribute__
assert hooked.existing == "class"
assert hooked.missing == "hook:missing"
del InheritedAttributeHook.__getattribute__
assert hooked.existing == "class"
class SlotAttributeHook:
__slots__ = ("value",)
def __getattr__(self, name):
return "unset"
slotted_hook = SlotAttributeHook()
assert slotted_hook.value == "unset"
slotted_hook.value = "set"
assert slotted_hook.value == "set"
# Retain the raw hook across callbacks, but delay its descriptor binding.
for lookup_kind in ("property", "getattribute"):
for change in ("replace", "delete"):
hook_events = []
class ChangingHookDescriptor:
def __get__(self, obj, owner):
hook_events.append("bind")
return lambda name: "original:" + name
class ChangingAttributeHook:
__getattr__ = ChangingHookDescriptor()
def changing_lookup(self, name):
hook_events.append("lookup")
if change == "replace":
ChangingAttributeHook.__getattr__ = lambda self, name: "replacement"
else:
del ChangingAttributeHook.__getattr__
raise AttributeError("lookup miss")
if lookup_kind == "property":
ChangingAttributeHook.value = property(
lambda self: changing_lookup(self, "value")
)
else:
ChangingAttributeHook.__getattribute__ = changing_lookup
assert ChangingAttributeHook().value == "original:value"
assert hook_events == ["lookup", "bind"]
class AddedAttributeHook:
def __getattribute__(self, name):
AddedAttributeHook.__getattr__ = lambda self, name: "added"
raise AttributeError("initial miss")
with assert_raises(AttributeError):
AddedAttributeHook().value
assert AddedAttributeHook().value == "added"
class RaisingAttributeHook(AttributeHook):
def __getattr__(self, name):
raise ValueError("hook error")
with assert_raises(ValueError) as caught:
RaisingAttributeHook().absent
assert caught.exception.__context__ is None
try:
raise RuntimeError("outer error")
except RuntimeError as outer:
with assert_raises(ValueError) as caught:
RaisingAttributeHook().absent
assert caught.exception.__context__ is outer
# The descriptor slot calls the raw __get__ attribute without binding it first.
class RawGetDescriptor:
__get__ = staticmethod(lambda *args: args)
class RawGetOwner:
value = RawGetDescriptor()
raw_descriptor = vars(RawGetOwner)["value"]
raw_instance = RawGetOwner()
assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner)
assert RawGetOwner.value == (raw_descriptor, None, RawGetOwner)
class CallableGet:
def __get__(self, instance, owner):
raise AssertionError("__get__ must not be bound before the slot call")
def __call__(self, *args):
return args
RawGetDescriptor.__get__ = CallableGet()
assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner)
RawGetDescriptor.__get__ = classmethod(lambda *args: args)
with assert_raises(TypeError):
raw_instance.value
RawGetDescriptor.__get__ = lambda self, instance, owner: (self, instance, owner)
assert raw_instance.value == (raw_descriptor, raw_instance, RawGetOwner)