typeshed icon indicating copy to clipboard operation
typeshed copied to clipboard

`stdlib.weakref:finalize` todo clarification

Open kkirsche opened this issue 3 years ago • 3 comments

In weakref.pyi, there is the following todo:

https://github.com/python/typeshed/blob/master/stdlib/weakref.pyi#L127

class finalize:  # TODO: This is a good candidate for to be a `Generic[_P, _T]` class
    def __init__(self, __obj: object, __func: Callable[_P, Any], *args: _P.args, **kwargs: _P.kwargs) -> None: ...
    def __call__(self, _: Any = ...) -> Any | None: ...
    def detach(self) -> tuple[Any, Any, tuple[Any, ...], dict[str, Any]] | None: ...
    def peek(self) -> tuple[Any, Any, tuple[Any, ...], dict[str, Any]] | None: ...
    @property
    def alive(self) -> bool: ...
    atexit: bool

Looking at the documentation for finalize, seen here I can see that:

finalize provides a straight forward way to register a cleanup function to be called when an object is garbage collected. This is simpler to use than setting up a callback function on a raw weak reference, since the module automatically ensures that the finalizer remains alive until the object is collected.

And is constructed with the object, callable, etc. The implementation is seen here: https://github.com/python/cpython/blob/main/Lib/weakref.py#L540

I was wondering if this could be clarified to help me understand what the correct way to implement that was.

As I understand the docs of finalize though, I think this would be:

class finalize(Generic[_P, _T, _R]): # _P paramspec, _T for the object, _R for the return value of the callable
    atexit: bool
    def __init__(self, __obj: _T, __func: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs) -> None: ...
    def __call__(self, _: object _T = ...) -> _R | None: ... # return the result of P, change Any to object as it's unused
    def detach(self) -> tuple[_T, Callable[_P, _R], _P.args, _P.kwargs] | None: ...
    def peek(self) -> tuple[_T, Callable[_P, _R], _P.args, _P.kwargs] | None: ...
    @property
    def alive(self) -> bool: ...

Any tips would be appreciated

kkirsche avatar Jul 29 '22 13:07 kkirsche

Note, the use of _P.args in the tuple I believe is not allowed, though it's how as a user I had originally understood and expected ParamSpec to work. Similar to how I expected it to be able to be bound to a function so that you could re-use a parameter specification of an existing function in other functions, essentially always keeping them up to date.

kkirsche avatar Jul 29 '22 13:07 kkirsche

Cc @sobolevn as author of that comment.

srittau avatar Jul 29 '22 13:07 srittau

Yes, I was thinking about typing methods like detach:

 def detach(self):
        """If alive then mark as dead and return (obj, func, args, kwargs);
        otherwise return None"""
        info = self._registry.get(self)
        obj = info and info.weakref()
        if obj is not None and self._registry.pop(self, None):
            return (obj, info.func, info.args, info.kwargs or {})

sobolevn avatar Jul 29 '22 14:07 sobolevn

Closing as I fundamentally disagree with the suggested fix of simply removing the todo.

kkirsche avatar Aug 18 '22 12:08 kkirsche