问题
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
回答1:
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
回答2:
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.
回答3:
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:
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.
来源:https://stackoverflow.com/questions/13620542/detecting-empty-function-definitions-in-python