Detecting empty function definitions in python

2020-07-03 05:31发布

I need to detect whether a function is an empty definition or not. It can be like:

def foo():
    pass

or like:

def foo(i, *arg, **kwargs):
    pass

or like:

foo = lambda x: None

What is the most elegant way to detect them using the 'inspect' module? Is there a better way than this:

def isEmptyFunction(func):
    e = lambda: None
    return func.__code__.co_code == e.__code__.co_code

4条回答
Lonely孤独者°
2楼-- · 2020-07-03 05:35

The method you propose does not quite work because empty functions that have docstrings have a slightly different bytecode.

The value of func.__code__.co_code for an empty function with no docstring is 'd\x00\x00S', while the value of it for a function with a docstring is 'd\x01\x00S'.

For my purposes, it works just to add the additional case to test for:

def isEmptyFunction(func):
    def empty_func():
        pass

    def empty_func_with_doc():
        """Empty function with docstring."""
        pass

    return func.__code__.co_code == empty_func.__code__.co_code or \
        func.__code__.co_code == empty_func_with_doc.__code__.co_code
查看更多
虎瘦雄心在
3楼-- · 2020-07-03 05:37

To answer the original question: I don't think there is a better way, but definitely a more resilient one.

Building on top of this answer by @kcon:

def isEmptyFunction(func): 
    def empty_func(): 
        pass

    def empty_func_with_doc(): 
        """Empty function with docstring.""" 
        pass 

    return func.__code__.co_code == empty_func.__code__.co_code or \
        func.__code__.co_code == empty_func_with_doc.__code__.co_code

which fails for the following:

def not_empty_returning_string():
    return 'not empty'

isEmptyFunction(just_return_string) # True

as well as for lambdas:

not_empty_lambda_returning_string = lambda x: 'not empty'

isEmptyFunction(not_empty_lambda_returning_string) # True

I built an extended version which also checks constants with the exception of docstrings:

def is_empty_function(f):
    """Returns true if f is an empty function."""

    def empty_func():
        pass

    def empty_func_with_docstring():
        """Empty function with docstring."""
        pass

    empty_lambda = lambda: None

    empty_lambda_with_docstring = lambda: None
    empty_lambda_with_docstring.__doc__ = """Empty function with docstring."""

    def constants(f):
        """Return a tuple containing all the constants of a function without:
            * docstring
        """
        return tuple(
            x
            for x in f.__code__.co_consts
            if x != f.__doc__
        )

    return (
            f.__code__.co_code == empty_func.__code__.co_code and
            constants(f) == constants(empty_func)
        ) or (
            f.__code__.co_code == empty_func_with_docstring.__code__.co_code and
            constants(f) == constants(empty_func_with_docstring)
        ) or (
            f.__code__.co_code == empty_lambda.__code__.co_code and
            constants(f) == constants(empty_lambda)
        ) or (
            f.__code__.co_code == empty_lambda_with_docstring.__code__.co_code and
            constants(f) == constants(empty_lambda_with_docstring)
        )

Testing:

#
# Empty functions (expect: is_empty_function(f) == True)
#

def empty():
    pass

def empty_with_docstring():
    """this is just an example docstring."""
    pass

empty_lambda = lambda: None

empty_lambda_with_docstring = lambda: None
empty_lambda_with_docstring.__doc__ = """this is just an example docstring."""

#
# Not empty functions (expect: is_empty_function(f) == False)
#

def not_empty():
    print('not empty');

def not_empty_with_docstring():
    """this is just an example docstring."""
    print('not empty');

not_empty_lambda = lambda: print('not empty')

not_empty_lambda_with_docstring = lambda: print('not empty')
not_empty_lambda_with_docstring.__doc__ = """this is just an example docstring."""

#
# Not empty functions returning a string (expect: is_empty_function(f) == False)
#

def not_empty_returning_string():
    return 'not empty'

def not_empty_returning_string_with_docstring():
    return 'not empty'

not_empty_lambda_returning_string = lambda: 'not empty'

not_empty_lambda_returning_string_with_docstring = lambda: 'not empty'
not_empty_lambda_returning_string_with_docstring.__doc__ = """this is just an example docstring."""


all([
  is_empty_function(empty) == True,
  is_empty_function(empty_with_docstring) == True,
  is_empty_function(empty_lambda) == True,
  is_empty_function(empty_lambda_with_docstring) == True,

  is_empty_function(not_empty) == False,
  is_empty_function(not_empty_with_docstring) == False,
  is_empty_function(not_empty_lambda) == False,
  is_empty_function(not_empty_lambda_with_docstring) == False,

  is_empty_function(not_empty_returning_string) == False,
  is_empty_function(not_empty_returning_string_with_docstring) == False,
  is_empty_function(not_empty_lambda_returning_string) == False,
  is_empty_function(not_empty_lambda_returning_string_with_docstring) == False,

]) # == True
查看更多
够拽才男人
4楼-- · 2020-07-03 05:40

Why would you do that? It looks like bad design. I would bet you wouldn't make anything faster.

python -m timeit -s'def a(): pass' -s'def b(): pass' 'if a.__code__.co_code == b.__code__.co_code: pass'
1000000 loops, best of 3: 0.293 usec per loop

python -m timeit -s 'def a(): pass' -s 'def b(): pass' 'a()'
10000000 loops, best of 3: 0.0941 usec per loop

It seems like it is magnitude slower to compare than to just do call, because there were 10 times more loops in the latter timeit. The equals operator actually is surely calls a.code.co_code.eq. So you are just making things slower.

查看更多
欢心
5楼-- · 2020-07-03 05:47

The way you're using works. A perhaps more "elegant" solution would be to have a list of functions, and in all your empty (or all your non-empty) functions you would add it to the list, and then check whether the function is in the list or not.

查看更多
登录 后发表回答