簡體   English   中英

在運行時的python中,確定對象是否為類(舊類型和新類型)實例

[英]In python at runtime determine if an object is a class (old and new type) instance

我正在嘗試使用h5py模塊將一組深層嵌套的類,屬性,綁定方法等寫入HDF5文件中以進行長期存儲。 我真的很親近 我唯一無法解決的問題是在運行時以編程方式找出一種確定某項是否是類實例類型而不是列表,int等的方法。我需要遞歸到類實例,但顯然不應該遞歸為int,float等。這對於新舊類均適用。 我研究過的東西不起作用/我無法工作:

使用檢查模塊

>>> class R(object): pass
...
>>> _R = R()
>>> import inspect
>>> inspect.isclass(_R)
False
>>> inspect.isclass(R)
True

這沒有幫助,我需要像inspect.isclassinstance(_R)這樣的函數返回True

使用類型模塊

如果您使用舊類,則有一個名為InstanceType的類型,它與舊類的實例匹配,如下面的代碼所示

>>> import types
>>> class R(): pass #old-style class
...
>>> _R = R()
>>> import types
>>> type(_R) is types.InstanceType
True
>>> class R(object): pass #new-style class
...
>>> _R = R()
>>> type(_R) is types.InstanceType
False

但是,如果您使用新式類,則類型中沒有對應的types

盡管發帖者很可能需要重新考慮其設計,但在某些情況下,確實有必要區分使用C語言創建的內置/擴展類型實例和使用class語句在Python中創建的類實例。 雖然兩者都是類型,但是后者是CPython內部稱為“堆類型”的類型類別,因為它們的類型結構是在運行時分配的。 可以在__repr__輸出中看到python繼續區分它們:

>>> int       # "type"
<type 'int'>
>>> class X(object): pass
... 
>>> X         # "class"
<class '__main__.X'>

__repr__區別是通過檢查類型是否為堆類型來實現的。

根據應用程序的確切需求,可以通過以下方式之一來實現is_class_instance函數:

# Built-in types such as int or object do not have __dict__ by
# default. __dict__ is normally obtained by inheriting from a
# dictless type using the class statement.  Checking for the
# existence of __dict__ is an indication of a class instance.
#
# Caveat: a built-in or extension type can still request instance
# dicts using tp_dictoffset, and a class can suppress it with
# __slots__.
def is_class_instance(o):
    return hasattr(o, '__dict__')

# A reliable approach, but one that is also more dependent
# on the CPython implementation.
Py_TPFLAGS_HEAPTYPE = (1<<9)       # Include/object.h
def is_class_instance(o):
    return bool(type(o).__flags__ & Py_TPFLAGS_HEAPTYPE)

編輯

這是該功能第二個版本的說明。 它實際上使用CPython內部出於其自身目的而使用的相同測試來測試類型是否為“堆類型”。 這樣可以確保對於堆類型(“類”)的實例始終返回True,對於非堆類型(“ types”,還包括易於修復的舊式類)實例始終返回False。 它通過檢查是否tp_flags構件 C級的PyTypeObject結構具有Py_TPFLAGS_HEAPTYPE比特集。 該實現的弱點是它將Py_TPFLAGS_HEAPTYPE常量的值硬編碼為當前觀察到的值。 (這是必需的,因為該常量不會通過符號名顯示給Python。)盡管從理論上講該常量可以更改,但實際上不太可能發生,因為這樣的更改會無意中破壞現有擴展模塊的ABI。 查看Include/object.h Py_TPFLAGS常量定義 ,很明顯,正在小心地添加新的常量而不干擾舊的常量 另一個弱點是該代碼在非CPython實現(如Jython或IronPython)上運行的機會為零。

感謝@ user4815162342,我已經能夠使它工作。 這是一個經過稍微修改的版本,對於舊類和新類的實例將返回True:

#Added the check for old-style class
Py_TPFLAGS_HEAPTYPE = (1L<<9)       # Include/object.h
def is_class_instance(o):
    import types
    return (bool(type(o).__flags__ & Py_TPFLAGS_HEAPTYPE) 
            or type(o) is types.InstanceType)

tl; dr 只需調用遠在下面定義的is_object_pure_python()函數。

ibell一樣, user4815162342的權威Python 2.x專用解決方案給我留下了深刻的印象。 但是,在Pythonic天堂中一切都不好。

問題。 問題無處不在。

該解決方案(雖然很有見地)遭受了一些腐爛, 但是通過簡單的編輯卻無法解決,包括:

  • Python 3.x不支持L類型的后綴。 誠然,可以輕易解決。
  • 交叉解釋器的is_class_instance()實現無法說明使用__slots__優化的純Python類。
  • 在非CPython解釋器(例如pypy)下,特定is_class_instance() CPython的is_class_instance()實現失敗。
  • 沒有可比較的實現來檢測 (而不是類實例 )是基於純Python還是基於C的實現。

解決方案! 解決方案無處不在!

為了解決這些問題,下面的特定於Python 3.x的解決方案刪除了L ,檢測到__slots__ ,已進行重構,以便偏向於在CPython下使用更可靠的CPython特定於is_class_instance()實現,並回is_class_instance()較不可靠的交叉解釋器is_class_instance()在所有其他解釋器下的實現, 並且已被概括為檢測類和類實例。

為了理智,讓我們先檢測類實例:

import platform

# If the active Python interpreter is the official CPython implementation,
# prefer a more reliable CPython-specific solution guaranteed to succeed.
if platform.python_implementation() == 'CPython':
    # Magic number defined by the Python codebase at "Include/object.h".
    Py_TPFLAGS_HEAPTYPE = (1<<9)

    def is_instance_pure_python(obj: object) -> bool:
        '''
        `True` if the passed object is an instance of a pure-Python class _or_
        `False` if this object is an instance of a C-based class (either builtin
        or defined by a C extension).
        '''

        return bool(type(obj).__flags__ & Py_TPFLAGS_HEAPTYPE)

# Else, fallback to a CPython-agnostic solution typically but *NOT*
# necessarily succeeding. For all real-world objects of interest, this is
# effectively successful. Edge cases exist but are suitably rare.
else:
    def is_instance_pure_python(obj: object) -> bool:
        '''
        `True` if the passed object is an instance of a pure-Python class _or_
        `False` if this object is an instance of a C-based class (either builtin
        or defined by a C extension).
        '''

        return hasattr(obj, '__dict__') or hasattr(obj, '__slots__')

證明在吉多的布丁中

單元測試證明了令人不快的事實:

>>> class PurePythonWithDict(object): pass
>>> class PurePythonWithSlots(object): __slots__ = ()
>>> unslotted = PurePythonWithDict()
>>> slotted = PurePythonWithSlots()
>>> is_instance_pure_python(unslotted)
True
>>> is_instance_pure_python(slotted)
True
>>> is_instance_pure_python(3)
False
>>> is_instance_pure_python([3, 1, 4, 1, 5])
False
>>> import numpy
>>> is_instance_pure_python(numpy.array((3, 1, 4, 1, 5)))
False

這會泛化為沒有實例的類嗎?

是的,但是這樣做並非易事。 奇怪的是,要檢測一個 (而不是類實例 )是純Python還是基於C的。 為什么? 因為即使基於C的類也提供__dict__屬性。 因此, hasattr(int, '__dict__') == True

但是,如果這是一種駭人聽聞的方式,那么就會有一種頑強的意志。 出於未知( 可能是平庸 )的原因,內置的dir() 僅針對基於C的類從其返回的列表中__dict__屬性名稱 因此,以交叉解釋器的方式檢測類是基於純Python還是基於C的類,將減少迭代搜索dir()返回的列表以獲取__dict__ 為了勝利:

import platform

# If the active Python interpreter is the official CPython interpreter,
# prefer a more reliable CPython-specific solution guaranteed to succeed.
if platform.python_implementation() == 'CPython':
    # Magic number defined by the Python codebase at "Include/object.h".
    Py_TPFLAGS_HEAPTYPE = (1<<9)

    def is_class_pure_python(cls: type) -> bool:
        '''
        `True` if the passed class is pure-Python _or_ `False` if this class
        is C-based (either builtin or defined by a C extension).
        '''

        return bool(cls.__flags__ & Py_TPFLAGS_HEAPTYPE)

# Else, fallback to a CPython-agnostic solution typically but *NOT*
# necessarily succeeding. For all real-world objects of interest, this is
# effectively successful. Edge cases exist but are suitably rare.
else:
    def is_class_pure_python(cls: type) -> bool:
        '''
        `True` if the passed class is pure-Python _or_ `False` if this class
        is C-based (either builtin or defined by a C extension).
        '''

        return '__dict__' in dir(cls) or hasattr(cls, '__slots__')

更多證明。 更多布丁。

更多測試驅動的真實性:

>>> class PurePythonWithDict(object): pass
>>> class PurePythonWithSlots(object): __slots__ = ()
>>> is_class_pure_python(PurePythonWithDict)
True
>>> is_class_pure_python(PurePythonWithSlots)
True
>>> is_class_pure_python(int)
False
>>> is_class_pure_python(list)
False
>>> import numpy
>>> is_class_pure_python(numpy.ndarray)
False

這就是她寫的

為了通用起見,讓我們將上面定義的低級函數統一為兩個高級函數,它們支持所有可能的Python解釋器下的所有可能的類型:

def is_object_pure_python(obj: object) -> bool:
   '''
   `True` if the passed object is either a pure-Python class or instance of
   such a class _or_ `False` if this object is either a C-based class
   (builtin or defined by a C extension) or instance of such a class.
   '''

   if isinstance(obj, type):
       return is_class_pure_python(obj)
   else:
       return is_instance_pure_python(obj)


def is_object_c_based(obj: object) -> bool:
   '''
   `True` if the passed object is either a C-based class (builtin or
   defined by a C extension) or instance of such a class _or_ `False` if this
   object is either a pure-Python class or instance of such a class.
   '''

   return not is_object_pure_python(obj)

看哪! 純Python。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM