Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Doc/library/winreg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -771,8 +771,9 @@ Handle objects provide semantics for :meth:`~object.__bool__` -- thus ::
will print ``Yes`` if the handle is currently valid (has not been closed or
detached).

The object also support comparison semantics, so handle objects will compare
true if they both reference the same underlying Windows handle value.
The object also support equality comparison semantics, so handle objects will
compare equal if they both reference the same underlying Windows handle value.
Closed handle objects (those with a handle value of zero) always compare equal.

Handle objects can be converted to an integer (e.g., using the built-in
:func:`int` function), in which case the underlying Windows handle value is
Expand Down Expand Up @@ -815,3 +816,6 @@ integer handle, and also disconnect the Windows handle from the handle object.
will automatically close *key* when control leaves the :keyword:`with` block.


.. versionchanged:: next
Handle objects are now compared by their underlying Windows handle value
instead of object identity for equality comparisons.
14 changes: 6 additions & 8 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,19 @@ def evaluate(
type_params = getattr(owner, "__type_params__", None)

# Type parameters exist in their own scope, which is logically
# between the locals and the globals.
type_param_scope = {}
# between the locals and the globals. We simulate this by adding
# them to the globals.
if type_params is not None:
globals = dict(globals)
for param in type_params:
type_param_scope[param.__name__] = param

globals[param.__name__] = param
if self.__extra_names__:
locals = {**locals, **self.__extra_names__}

arg = self.__forward_arg__
if arg.isidentifier() and not keyword.iskeyword(arg):
if arg in locals:
return locals[arg]
elif arg in type_param_scope:
return type_param_scope[arg]
elif arg in globals:
return globals[arg]
elif hasattr(builtins, arg):
Expand All @@ -185,15 +183,15 @@ def evaluate(
else:
code = self.__forward_code__
try:
return eval(code, globals=globals, locals={**type_param_scope, **locals})
return eval(code, globals=globals, locals=locals)
except Exception:
if not is_forwardref_format:
raise

# All variables, in scoping order, should be checked before
# triggering __missing__ to create a _Stringifier.
new_locals = _StringifierDict(
{**builtins.__dict__, **globals, **type_param_scope, **locals},
{**builtins.__dict__, **globals, **locals},
globals=globals,
owner=owner,
is_class=self.__forward_is_class__,
Expand Down
9 changes: 0 additions & 9 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1911,15 +1911,6 @@ def test_fwdref_invalid_syntax(self):
with self.assertRaises(SyntaxError):
fr.evaluate()

def test_re_evaluate_generics(self):
global alias
class C:
x: alias[int]

evaluated = get_annotations(C, format=Format.FORWARDREF)["x"].evaluate(format=Format.FORWARDREF)
alias = list
self.assertEqual(evaluated.evaluate(), list[int])


class TestAnnotationLib(unittest.TestCase):
def test__all__(self):
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_winreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,33 @@ def _test_named_args(self, key, sub_key):
access=KEY_ALL_ACCESS) as okey:
self.assertTrue(okey.handle != 0)

def test_hkey_comparison(self):
"""Test HKEY comparison by handle value rather than object identity."""
key1 = OpenKey(HKEY_CURRENT_USER, None)
key2 = OpenKey(HKEY_CURRENT_USER, None)
key3 = OpenKey(HKEY_LOCAL_MACHINE, None)

self.addCleanup(CloseKey, key1)
self.addCleanup(CloseKey, key2)
self.addCleanup(CloseKey, key3)

self.assertEqual(key1.handle, key2.handle)
self.assertTrue(key1 == key2)
self.assertFalse(key1 != key2)

self.assertTrue(key1 != key3)
self.assertFalse(key1 == key3)

# Closed keys should be equal (all have handle=0)
CloseKey(key1)
CloseKey(key2)
CloseKey(key3)

self.assertEqual(key1.handle, 0)
self.assertEqual(key2.handle, 0)
self.assertEqual(key3.handle, 0)
self.assertEqual(key2, key3)


class LocalWinregTests(BaseWinregTests):

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Now :class:`!winreg.HKEYType` objects are compared by their underlying Windows
registry handle value instead of their object identity.
34 changes: 30 additions & 4 deletions PC/winreg.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,38 @@ PyHKEY_strFunc(PyObject *ob)
return PyUnicode_FromFormat("<PyHKEY:%p>", pyhkey->hkey);
}

static int
PyHKEY_compareFunc(PyObject *ob1, PyObject *ob2)
static PyObject *
PyHKEY_richcompare(PyObject *ob1, PyObject *ob2, int op)
{
/* Both objects must be PyHKEY objects from the same module */
if (Py_TYPE(ob1) != Py_TYPE(ob2)) {
Py_RETURN_NOTIMPLEMENTED;
}

PyHKEYObject *pyhkey1 = (PyHKEYObject *)ob1;
PyHKEYObject *pyhkey2 = (PyHKEYObject *)ob2;
return pyhkey1 == pyhkey2 ? 0 :
(pyhkey1 < pyhkey2 ? -1 : 1);
HKEY hkey1 = pyhkey1->hkey;
HKEY hkey2 = pyhkey2->hkey;
int result;

switch (op) {
case Py_EQ:
result = (hkey1 == hkey2);
break;
case Py_NE:
result = (hkey1 != hkey2);
break;
default:
/* Only support equality comparisons, not ordering */
Py_RETURN_NOTIMPLEMENTED;
}

if (result) {
Py_RETURN_TRUE;
}
else {
Py_RETURN_FALSE;
}
}

static Py_hash_t
Expand Down Expand Up @@ -365,6 +390,7 @@ static PyType_Slot pyhkey_type_slots[] = {
{Py_tp_traverse, _PyObject_VisitType},
{Py_tp_hash, PyHKEY_hashFunc},
{Py_tp_str, PyHKEY_strFunc},
{Py_tp_richcompare, PyHKEY_richcompare},

// Number protocol
{Py_nb_add, PyHKEY_binaryFailureFunc},
Expand Down
Loading