¿Cómo puedo detectar si una variable es una función?

11 minutos de lectura

Avatar de usuario de Daniel H
Daniel H.

tengo una variable xy quiero saber si apunta a una función o no.

Esperaba poder hacer algo como:

>>> isinstance(x, function)

Pero eso me da:

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'function' is not defined

La razón por la que elegí eso es porque

>>> type(x)
<type 'function'>

  • Estoy deprimido por la cantidad de respuestas que solucionan el problema al buscar algunas llamar atributo o función invocable … Una forma limpia es sobre tipo (a) == tipos. Tipo de función como lo sugiere @ryan

    – AsTeR

    20 de septiembre de 2013 a las 12:09

  • @AsTeR La forma correcta de verificar las propiedades de los objetos tipo pato es preguntarles si graznan, no para ver si caben en un contenedor del tamaño de un pato. El enfoque de “compararlo directamente” dará la respuesta incorrecta para muchas funciones, como funciones integradas.

    – Juan Feminella

    2 de junio de 2014 a las 16:58

  • @JohnFeminella Aunque estoy de acuerdo contigo en principio. El OP no preguntó si se podía llamar, solo si es una función. ¿Quizás se podría argumentar que necesitaba una distinción entre, por ejemplo, funciones y clases?

    – McKay

    28 de febrero de 2017 a las 18:15

  • Para mis propósitos, vine aquí porque quería usar insepct.getsource en una variedad de objetos, y en realidad no importa si el objeto era invocable sino si era algo que daría ‘función’ para type(obj). Dado que Google me llevó aquí, diría que el comentario de AsTeR fue la respuesta más útil (para mí). Hay muchos otros lugares en Internet para que la gente los descubra. __call__ o callable.

    – tsbertalan

    07/12/2017 a las 21:23


  • @AsTeR Es types.FunctionType, con F mayúscula.

    – Ben Yeguas

    8 de noviembre de 2019 a las 17:32


Avatar de usuario de John Feminilla
Juan Feminella

Si esto es para Python 2.x o para Python 3.2+, puede usar callable(). Solía ​​estar en desuso, pero ahora no está en desuso, por lo que puede usarlo nuevamente. Puedes leer la discusión aquí: http://bugs.python.org/issue10518. Puedes hacer esto con:

callable(obj)

Si esto es para Python 3.x pero anterior a 3.2, verifique si el objeto tiene un __call__ atributo. Puedes hacer esto con:

hasattr(obj, '__call__')

El sugerido a menudo types.FunctionTypes o inspect.isfunction enfoque (ambos lo hacen exactamente lo mismo) viene con una serie de advertencias. Vuelve False para funciones que no son de Python. La mayoría funciones integradaspor ejemplo, se implementan en C y no en Python, por lo que devuelven False:

>>> isinstance(open, types.FunctionType)
False
>>> callable(open)
True

asi que types.FunctionType podría darte resultados sorprendentes. La forma adecuada de verificar las propiedades de los objetos tipo pato es preguntarles si graznan, no para ver si caben en un recipiente del tamaño de un pato.

  • Esto tampoco le dirá si es una función, solo si se puede llamar.

    – Chris B.

    9 de marzo de 2009 a las 4:02

  • Depende de la aplicación si la distinción importa o no; Sospecho que tiene razón en que no es así para la pregunta original, pero eso está lejos de ser seguro.

    – Chris B.

    9 de marzo de 2009 a las 5:33

  • Las clases pueden tener un llamar función que se le atribuye. Así que este definitivamente no es un buen método para distinguir. El método de Ryan es mejor.

    -Brian Bruggeman

    2 de diciembre de 2011 a las 20:12

  • el concepto de “escribir pato” hace que esta sea la mejor respuesta, por ejemplo, “¿qué importa si es una función siempre que se comporte como tal?”

    – jcomeau_ictx

    2 de enero de 2012 a las 4:02

  • Hay casos de uso en los que la distinción entre un invocable y una función es crucial, por ejemplo, al escribir un decorador (ver mi comentario sobre la respuesta de Ryan).

    – Turión

    6 de diciembre de 2013 a las 22:26

Avatar de usuario de Ryan
Ryan

Los tipos integrados que no tienen constructores en el espacio de nombres integrado (por ejemplo, funciones, generadores, métodos) están en el types módulo. Puedes usar types.FunctionType en un isinstance llamar:

>>> import types
>>> types.FunctionType
<class 'function'>

>>> def f(): pass

>>> isinstance(f, types.FunctionType)
True
>>> isinstance(lambda x : None, types.FunctionType)
True

Tenga en cuenta que esto utiliza una noción muy específica de “función” que generalmente no es lo que necesita. Por ejemplo, rechaza zip (técnicamente una clase):

>>> type(zip), isinstance(zip, types.FunctionType)
(<class 'type'>, False)

open (las funciones integradas tienen un tipo diferente):

>>> type(open), isinstance(open, types.FunctionType)
(<class 'builtin_function_or_method'>, False)

y random.shuffle (técnicamente un método de un oculto random.Random instancia):

>>> type(random.shuffle), isinstance(random.shuffle, types.FunctionType)
(<class 'method'>, False)

Si estás haciendo algo específico para types.FunctionType instancias, como descompilar su código de bytes o inspeccionar variables de cierre, use types.FunctionTypepero si solo necesita que se pueda llamar a un objeto como una función, use callable.

  • +1 respondiendo la pregunta. Sin embargo, tratar de adivinar si un objeto es una función, o incluso si es un objeto invocable, suele ser un error. Sin más información del OP, es difícil descartarlo, por supuesto, pero aún así …

    – bobince

    9 de marzo de 2009 a las 4:49

  • En realidad, devolverá False para las funciones integradas, como ‘abrir’, por ejemplo. Entonces, para ser específico, deberá usar isinstance (f, (types.FunctionType, types.BuiltinFunctionType)). Y, por supuesto, si desea estrictamente solo funciones, no invocables ni métodos.

    – Lukasz Korzybski

    20 de abril de 2011 a las 14:06

  • @ŁukaszKorzybski y para ser más precisos… también deberías buscar functools.partial: isinstance(f, (types.FunctionType, types.BuiltinFunctionType, functools.partial)) o revisando f.func en cuyo caso.

    – estani

    17 de enero de 2013 a las 12:04

  • @bobince, ¿qué tal este caso de uso: quiero escribir un decorador @foo que puedo usar tanto como @foo y como @foo(some_parameter). Luego necesita verificar con qué se está llamando, por ejemplo, la función para decorar (primer caso) o el parámetro (el segundo caso, en el que necesita devolver un decorador adicional).

    – Turión

    6 de diciembre de 2013 a las 22:24

  • types.BuiltinFunctionType es también el tipo de (“normal”) incorporado métodosque probablemente no quiera permitir, si no va al callable ruta.

    – usuario2357112

    12 de noviembre de 2018 a las 19:42

Avatar de usuario de Paolo
Pablo

Desde Python 2.1 puedes importar isfunction desde el inspect módulo.

>>> from inspect import isfunction
>>> def f(): pass
>>> isfunction(f)
True
>>> isfunction(lambda x: x)
True

  • Bien, pero parece devolver False para funciones integradas como open y hasattr.

    – Zecc

    4 mayo 2013 a las 19:43

  • @Zecc está incorporado es para eso

    – Pablo

    5 de mayo de 2013 a las 7:16

  • Ver el inspect.isfunction cadena de documentación: “Devuelve verdadero si el objeto es una función definida por el usuario”.

    -Mark Mikofski

    6 de agosto de 2013 a las 20:27


  • Tenga en cuenta que ‘isfunction’ no reconoce las funciones functool.partial.

    – ismael

    5 de diciembre de 2013 a las 19:16

  • los código fuente completo de isfunction es return isinstance(object, types.FunctionType) por lo que viene con las advertencias señaladas en la respuesta de @Ryan.

    – Boris Verjovskiy

    23 de enero de 2021 a las 21:24


Avatar de usuario de SingleNegationElimination
Eliminación de negación única

En el momento en que se ofreció, se pensó que la respuesta aceptada era correcta. Resulta que hay sin sustituto por callable()que está de vuelta en Python 3.2: Específicamente, callable() comprueba el tp_call campo del objeto que se está probando. No hay un equivalente simple de Python. La mayoría de las pruebas sugeridas son correctas la mayor parte del tiempo:

>>> class Spam(object):
...     def __call__(self):
...         return 'OK'
>>> can_o_spam = Spam()


>>> can_o_spam()
'OK'
>>> callable(can_o_spam)
True
>>> hasattr(can_o_spam, '__call__')
True
>>> import collections
>>> isinstance(can_o_spam, collections.Callable)
True

Podemos lanzar una llave inglesa en esto quitando el __call__ de la clase Y solo para mantener las cosas aún más emocionantes, agregue un falso __call__ a la instancia!

>>> del Spam.__call__
>>> can_o_spam.__call__ = lambda *args: 'OK?'

Tenga en cuenta que esto realmente no es invocable:

>>> can_o_spam()
Traceback (most recent call last):
  ...
TypeError: 'Spam' object is not callable

callable() devuelve el resultado correcto:

>>> callable(can_o_spam)
False

Pero hasattr es equivocado:

>>> hasattr(can_o_spam, '__call__')
True

can_o_spam tiene ese atributo después de todo; simplemente no se usa al llamar a la instancia.

Aún más sutil, isinstance() también se equivoca en esto:

>>> isinstance(can_o_spam, collections.Callable)
True

Debido a que usamos esta verificación antes y luego eliminamos el método, abc.ABCMeta
almacena en caché el resultado. Podría decirse que esto es un error en abc.ABCMeta. Dicho esto, realmente no hay forma posible de que pudo producir un resultado más preciso que el resultado que usando callable() mismo, desde el typeobject->tp_call
el método de ranura no es accesible de ninguna otra manera.

Solo usa callable()

Lo siguiente debería devolver un valor booleano:

callable(x)

  • Eso resuelve su problema, pero aún crea un misterio: si x es de clase ‘función’ en el módulo incorporadoy help(x.__class__) describe “función de clase”, ¿por qué “función” aparentemente “no está definida”?

    – ken

    9 de marzo de 2009 a las 3:52

  • “función” no es una palabra clave ni un tipo incorporado. El tipo de funciones se define en el módulo “types”, como “types.FunctionType”

    – Chris B.

    9 de marzo de 2009 a las 4:03

Avatar de usuario de Lutz Prechelt
Lutz Prechelt

Resultado

invocable (x) hasattr(x, ‘__llamar__’) inspeccionar.isfunction(x) inspeccionar.ismethod(x) inspeccionar.isgeneratorfunction(x) inspeccionar.isroutinefunction(x) inspeccionar.isasyncgenfunction(x) isinstance(x, escribiendo. Llamable) isinstance(x, tipos.BuiltinFunctionType) isinstance(x, tipos.BuiltinMethodType) esinstancia(x, tipos.TipoFunción) isinstance(x, tipos.MethodType) isinstance(x, tipos.LambdaType) isinstance(x, functools.parcial)
impresión × × × × × × × × ×
función × × × × × × × ×
functools.parcial × × × × × × × × × ×
× × × × × × × ×
generador × × × × × × ×
función_async × × × × × × ×
generador_async × × × × × × ×
A × × × × × × × × × × ×
metanfetamina × × × × × × × ×
metanfetamina × × × × × × × × ×
metástasis × × × × × × × ×
import types
import inspect
import functools
import typing


def judge(x):
    name = x.__name__ if hasattr(x, '__name__') else 'functools.partial'
    print(name)
    print('\ttype({})={}'.format(name, type(x)))
    print('\tcallable({})={}'.format(name, callable(x)))
    print('\thasattr({}, \'__call__\')={}'.format(name, hasattr(x, '__call__')))
    print()
    print('\tinspect.isfunction({})={}'.format(name, inspect.isfunction(x)))
    print('\tinspect.ismethod({})={}'.format(name, inspect.ismethod(x)))
    print('\tinspect.isgeneratorfunction({})={}'.format(name, inspect.isgeneratorfunction(x)))
    print('\tinspect.iscoroutinefunction({})={}'.format(name, inspect.iscoroutinefunction(x)))
    print('\tinspect.isasyncgenfunction({})={}'.format(name, inspect.isasyncgenfunction(x)))
    print()
    print('\tisinstance({}, typing.Callable)={}'.format(name, isinstance(x, typing.Callable)))
    print('\tisinstance({}, types.BuiltinFunctionType)={}'.format(name, isinstance(x, types.BuiltinFunctionType)))
    print('\tisinstance({}, types.BuiltinMethodType)={}'.format(name, isinstance(x, types.BuiltinMethodType)))
    print('\tisinstance({}, types.FunctionType)={}'.format(name, isinstance(x, types.FunctionType)))
    print('\tisinstance({}, types.MethodType)={}'.format(name, isinstance(x, types.MethodType)))
    print('\tisinstance({}, types.LambdaType)={}'.format(name, isinstance(x, types.LambdaType)))
    print('\tisinstance({}, functools.partial)={}'.format(name, isinstance(x, functools.partial)))


def func(a, b):
    pass


partial = functools.partial(func, a=1)

_lambda = lambda _: _


def generator():
    yield 1
    yield 2


async def async_func():
    pass


async def async_generator():
    yield 1


class A:
    def __call__(self, a, b):
        pass

    def meth(self, a, b):
        pass

    @classmethod
    def classmeth(cls, a, b):
        pass

    @staticmethod
    def staticmeth(a, b):
        pass


for func in [print,
             func,
             partial,
             _lambda,
             generator,
             async_func,
             async_generator,
             A,
             A.meth,
             A.classmeth,
             A.staticmeth]:
    judge(func)

Tiempo

Elija los tres métodos más comunes:

veces
invocable (x) 0.86
hasattr(x, ‘__llamar__’) 1.36
isinstance(x, escribiendo. Llamable) 12.19
import typing
from timeit import timeit


def x():
    pass


def f1():
    return callable(x)


def f2():
    return hasattr(x, '__call__')


def f3():
    return isinstance(x, typing.Callable)


print(timeit(f1, number=10000000))
print(timeit(f2, number=10000000))
print(timeit(f3, number=10000000))
# 0.8643081
# 1.3563508
# 12.193492500000001

  • Eso resuelve su problema, pero aún crea un misterio: si x es de clase ‘función’ en el módulo incorporadoy help(x.__class__) describe “función de clase”, ¿por qué “función” aparentemente “no está definida”?

    – ken

    9 de marzo de 2009 a las 3:52

  • “función” no es una palabra clave ni un tipo integrado. El tipo de funciones se define en el módulo “types”, como “types.FunctionType”

    – Chris B.

    9 de marzo de 2009 a las 4:03

Avatar de usuario de Gary van der Merwe
Gary van der Merwe

La herramienta 2to3 de Python (http://docs.python.org/dev/library/2to3.html) sugiere:

import collections
isinstance(obj, collections.Callable)

Parece que este fue elegido en lugar del hasattr(x, '__call__') método debido a http://bugs.python.org/issue7006.

¿Ha sido útil esta solución?