试图确定一个对象是否是一个函数,datetime.datetime.now导致了一个奇怪的问题



环境:

Linux & CPython 3.6.4

大家好,

我正在尝试确定对象是否是函数。我用类型库来做这件事,我写的代码如下。

class Foo:
@classmethod
def is_function(cls, obj):
''' determines whether an object is a function
'''
return isinstance(obj, types.FunctionType)
@classmethod
def is_classmethod(cls, obj):
''' determines whether an object is a class method
'''
type_ = type(cls.is_classmethod)
return isinstance(obj, type_)

第一个函数"is_function"不适用于类方法。所以,我写了第二个函数"is_classmethod"。我阅读了类型库的代码以找到类似"ClassmethodType"的内容,但我找到了以下代码,所以我在第二个函数中使用的类型是"type(cls.is_classmethod("。

# lib/python3.6/types.py at line 11
def _f(): pass
FunctionType = type(_f)

然后,我做了一些实验:

class Bar:
@classmethod
def b(cls):
pass
Foo.is_function(Bar.b)     # False
Foo.is_classmethod(Bar.b)  # True

这两个函数按预期工作。但是,当我将datetime.datetime.now传递给这些函数时,它们都返回False。

在执行此操作之前,我已经阅读了日期时间库的代码,datetime.datetime.now 绝对是一个类方法。

# lib/python3.6/datetime.py at line 1475
@classmethod
def now(cls, tz=None):
"Construct a datetime from time.time() and optional time zone info."
t = _time.time()
return cls.fromtimestamp(t, tz)

我什至无法想象这一点,怎么可能?第二个函数已经被证明,它确实可以检测类方法,我在 datetime.datetime.now 上没有发现任何特别的东西,它只是一个普通的类方法。

那么,是什么原因造成的呢?

您的代码不会将datetime.now检测为类方法,因为该方法是用 C 实现的。

>>> from datetime import datetime
>>> datetime.now
<built-in method now of type object at 0x7f723093a960>

仅在datetime模块的 C 实现不可用时,在datetime.py中找到的方法定义仅用作回退。在datetime.py的底部,您将找到尝试导入 C 实现的导入:

try:
from _datetime import *
except ImportError:
pass

因此,您找到的类和方法根本不使用。

用 C 实现的方法不使用@classmethod装饰器,因此函数的isinstance(...)检查永远不会成功。为了检测用 C 编写的方法,您必须利用types模块中定义的各种类型:

  • BuiltinMethodType
  • WrapperDescriptorType
  • MethodDescriptorType
  • ClassMethodDescriptorType
  • MethodWrapperType(用于绑定底线方法(

datetime.now恰好是BuiltinMethodType的一个实例:

>>> import types
>>> isinstance(datetime.now, types.BuiltinMethodType)
True

但是如果你想检测所有类型的类方法,你应该检查上述所有类型:

def is_classmethod(obj):
return isinstance(obj, (types.BuiltinMethodType,
types.WrapperDescriptorType,
types.MethodDescriptorType,
types.ClassMethodDescriptorType,
types.MethodWrapperType))

但是,您必须知道,如果函数方法在 C 中定义,则它们之间没有区别。因此,不可能可靠地检测类方法:

>>> is_classmethod(datetime.now)
True
>>> is_classmethod('foo'.__str__)
True
>>> is_classmethod(sorted)
True

听起来你并不是真的在进行类型检查。似乎您真正要寻找的是一种确定对象是否可调用的方法,而这样做的方法是使用内置函数callable

>>> callable(datetime.datetime.now)
True

常规函数,C函数,C方法,插槽包装器,NumPy ufunc,随便什么,如果可以调用,callable都会返回True。检查类型对实现细节过于敏感;Python 有太多的函数和类似函数的类型,任何库都可以引入新的类型。此外,描述符协议意味着当你尝试访问classmethod时,你实际上并没有获得classmethod对象,所以测试classmethods的想法首先是有问题的。

最新更新