Removing a python function definition entirely but keeping the signature

Viewed 82

Lets say I have a function:

def foo(arg1: int, arg2: Text):
  print(arg1, arg2)

Given foo, how could I programmatically create a second function that has the same function signature but just passes? e.g.

def foo(arg1: int, arg2: Text):
  pass

Context:

I have a worker library that looks like:

registry = {}

def foo(arg1: int, arg2: Sequence[Dict[Text, Any]]):
  run_foo()
  
registry['foo'] = foo

while true:
  message = json.loads(queue.get_message())
  registry[message['name']](*message['args'])

and I have an API server library that looks something like this:

def enqueue(fn_name: str, fn_args: Any):
  message = json.dumps({'name': fn_name, 'args': fn_args})
  queue.send_message(message)

The API server and the worker binaries have a messenger queue between them, like RabbitMQ or SQS or whatever. But the API server can import the worker as a library, and thereby get all of the function signatures of the worker. I am running pytype, a static python type checker. I want to modify the enqueue function to do something like:

import workerlib

def enqueue(fn_name: str, fn_args: Any):

  # This should 'simulate' a fn call to foo with the same sig,
  # but without calling it because the function just passes now.
  # The simulation would be caught by pytype, which would give me
  # static type checking across the messenger queue boundary
  foo = workerlib.registry[fn_name]
  foo = make_fn_pass(foo)
  foo(*fn_args)  

  message = json.dumps({'name': fn_name, 'args': fn_args})
  queue.send_message(message)

Open to suggestions on how to do this other ways. The goal is to get the static type checker to understand what function is intended, even though the function itself is not being called directly.


EDIT One possible approach might be to do something like:

import types


def copy_func(f, name=None):
  fn = types.FunctionType(f.__code__, f.__globals__, name or f.__name__,
                          f.__defaults__, f.__closure__)
  # in case f was given attrs (note this dict is a shallow copy):
  fn.__dict__.update(f.__dict__)
  return fn


def foo(arg1, arg2):
  print(arg1, arg2)


def dummy(*args, **kwargs):
  pass


x = copy_func(foo)
x.__code__ = dummy.__code__

x(1, 2)  # Succeed pytype, and does nothing at runtime.
x(1, 2, 3)  # SHOULD fail pytype, does nothing at runtime.
foo(1, 2)  # Works as normal. 

The problem here is that pytype cannot track the function signature of foo through the copy_func function, so the call to x(1, 2, 3) doesn't trigger an error from pytype.

1 Answers

Ok, can't say for sure that this is the best route, but this seems to work for me with pytype:

from typing import TypeVar

T = TypeVar('T')


def foo(arg1: int):
  return arg1


def dummy(*args, **kwargs):
  pass


def just_sig(f: T) -> T:
  # Basically we just lie about the return type here...
  return dummy

x = just_sig(foo)
x(1, 2)  # Succeed pytype, and do nothing at runtime.
x(1, 2, 3)  # Fail pytype (expected 2, got 3), do nothing at runtime.
foo(1, 2)  # This should work as normal.

Very open to other approaches though, this feels fragile. One obvious downside is that x is now typed as returning an int when it doesnt actually return anything...

Related