Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preserving typing/typechecking while extending function with many arguments

I want to subclass/wrap subprocess.Popen. However, it has a lot of arguments. The usual ways to solve this, as far as I'm aware, are 1. "biting the bullet":

class MyPopen1(subprocess.Popen):
    def __init__(self, myarg1, myarg2, bufsize=-1, executable=None,
                 stdin=None, stdout=None, stderr=None,
                 preexec_fn=None, close_fds=True,
                 shell=False, cwd=None, env=None, universal_newlines=None,
                 startupinfo=None, creationflags=0,
                 restore_signals=True, start_new_session=False,
                 pass_fds=(), *, user=None, group=None, extra_groups=None,
                 encoding=None, errors=None, text=None, umask=-1, pipesize=-1,
                 process_group=None):

        arguments = self.handle_custom_arguments(myarg1, myarg2)
        super().__init__(arguments, bufsize, executable,
                 stdin, stdout, stderr,
                 preexec_fn, close_fds,
                 shell, cwd, env, universal_newlines,
                 startupinfo, creationflags,
                 restore_signals, start_new_session,
                 pass_fds, user=user, group=group, extra_groups=extra_groups,
                 encoding=encoding, errors=errors, text=text, umask=umask, pipesize=pipesize,
                 process_group=process_group)

or 2. using *args, **kwargs.

class MyPopen2(subprocess.Popen):
    def __init__(self, myarg1, myarg2, *args, **kwargs):
        arguments = self.handle_custom_arguments(myarg1, myarg2)
        super().__init__(arguments, *args, **kwargs)

The second is a lot easier to write; it requires no maintenance if a new argument is added to subprocess.Popen in 3.1x. The downside is that it has no types, which means no static typechecking and no function signature hinting in a text editor.

We can get a little closer with @functools.wraps(...), but at the cost of the signature needing to be the exact same as subprocess.Popen, and it overriding doc and etc. I don't think it's what wraps is intended for.

# Not good solution.
class MyPopen3(subprocess.Popen):
    @functools.wraps(subprocess.Popen.__init__)
    def __init__(self, myarg1: int, myarg2: str, *args, **kwargs):
        """
        :param myarg1: does foo
        :param myarg2: does bar
        """
        arguments = self.handle_custom_arguments(myarg1, myarg2)
        super().__init__(arguments, *args, **kwargs)

Ideally, I'd want a type signature like...

def __init__(self, 
             myarg1: int, myarg2: str, 
             *args: ArgsOf[subprocess.Popen.__init__][1:], 
             **kwargs: KwargsOf[subprocess.Popen.__init__]):

Is there any way to get this sort of effect? If not, how can I preserve typechecking when doing something like this?

like image 301
Kaia Avatar asked Feb 06 '26 21:02

Kaia


1 Answers

You can use Unpack[] from the typing library to grab type hints from a TypedDict:

from typing import TypedDict, Unpack

class Movie(TypedDict):
    name: str
    year: int

def foo(**kwargs: Unpack[Movie]) -> None: ..

https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-kwargs

But given that this still makes you explicitly lay out all params in a new object, I'm not sure how much lift this is going to give you - the first method might be more expedient.

like image 99
walkrflocka Avatar answered Feb 09 '26 12:02

walkrflocka



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!