__getattr__ 特殊方法破坏 Python 3.x 中的 inspect.getmembers() 的解决方法
Workaround for __getattr__ special method breaking inspect.getmembers() in Python 3.x
inspect.getmembers
函数在与定义 __getattr__
方法的 class 一起使用时中断(在 Python 3.x 中):
import inspect
class C:
def __getattr__(self, name):
def wrapper():
print("For MCVE purposes, this does nothing useful.")
return wrapper
print(inspect.getmembers(C()))
产生的错误如下所示:
Traceback (most recent call last):
File "tmp.py", line 9, in <module>
print(inspect.getmembers(C()))
File "/usr/lib/python3.7/inspect.py", line 330, in getmembers
for base in object.__bases__:
TypeError: 'function' object is not iterable
看来问题在于 inspect.getmodule
依赖于 更容易请求宽恕 方法并假设 __bases__
属性要么不存在(抛出 AttributeError
异常)或包含基础 classes.
我可以通过在 C.__getattr__
中显式地抛出 AttributeError
来解决这个问题;但是,我想将 inspect.getmembers
与我无法控制的 classes 对象一起使用。我的问题是:是否有解决此问题的方法,无需对 class C 或其实例进行任何修改?
在调用 inspect.getmembers(c)
之前尝试设置 c.__bases__ = ()
。
编辑:更安全、更详尽的版本以回应评论:
object.__setattr__(c, '__bases__', ())
try:
members = inspect.getmembers(c)
finally:
object.__delattr__(c, '__bases__')
如果出现以下情况,这将失败:
C
定义了 __slots__
或由于某些其他原因不允许设置 __bases__
,这将引发异常。
c
已经有一个不应被覆盖的实际 __bases__
属性。这可以用一些额外的代码来检查。
您要求提供一种无需修改 class C 或其实例即可工作的变通方法。这是一个完成这项工作的解决方案,尽管它涉及重写 long getmembers()
函数,这可能也不方便。也许这更适合图书馆的合并请求。
(来自 https://github.com/python/cpython/blob/3.7/Lib/inspect.py 的源代码)
import inspect
def getmembers(object, predicate=None):
"""Return all members of an object as (name, value) pairs sorted by name.
Optionally, only return members that satisfy a given predicate."""
# Line below adds inspect. reference to isclass()
if inspect.isclass(object):
# Line below adds inspect. reference to getmro()
mro = (object,) + inspect.getmro(object)
else:
mro = ()
results = []
processed = set()
names = dir(object)
# :dd any DynamicClassAttributes to the list of names if object is a class;
# this may result in duplicate entries if, for example, a virtual
# attribute with the same name as a DynamicClassAttribute exists
try:
for base in object.__bases__:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute):
names.append(k)
# Line below edited to catch TypeError
except (AttributeError, TypeError):
pass
for key in names:
# First try to get the value via getattr. Some descriptors don't
# like calling their __get__ (see bug #1785), so fall back to
# looking in the __dict__.
try:
value = getattr(object, key)
# handle the duplicate key
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
# could be a (currently) missing slot member, or a buggy
# __dir__; discard and move on
continue
if not predicate or predicate(value):
results.append((key, value))
processed.add(key)
results.sort(key=lambda pair: pair[0])
return results
现在如果你 运行 你的新 getmembers()
:
class C:
def __getattr__(self, name):
def wrapper():
print("For MCVE purposes, this does nothing useful.")
return wrapper
print(getmembers(C()))
您应该得到想要的结果:
[('__class__', <class '__main__.C'>), ('__delattr__', <method-wrapper '__delattr__' of C object at 0x1155fa7b8>), ('__dict__', {}), ('__dir__', <built-in method __dir__ of C object at 0x1155fa7b8>), ('__doc__', None), ('__eq__', <method-wrapper '__eq__' of C object at 0x1155fa7b8>), ('__format__', <built-in method __format__ of C object at 0x1155fa7b8>), ('__ge__', <method-wrapper '__ge__' of C object at 0x1155fa7b8>), ('__getattr__', <bound method C.__getattr__ of <__main__.C object at 0x1155fa7b8>>), ('__getattribute__', <method-wrapper '__getattribute__' of C object at 0x1155fa7b8>), ('__gt__', <method-wrapper '__gt__' of C object at 0x1155fa7b8>), ('__hash__', <method-wrapper '__hash__' of C object at 0x1155fa7b8>), ('__init__', <method-wrapper '__init__' of C object at 0x1155fa7b8>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x7fd01899e4c8>), ('__le__', <method-wrapper '__le__' of C object at 0x1155fa7b8>), ('__lt__', <method-wrapper '__lt__' of C object at 0x1155fa7b8>), ('__module__', '__main__'), ('__ne__', <method-wrapper '__ne__' of C object at 0x1155fa7b8>), ('__new__', <built-in method __new__ of type object at 0x1008b0c48>), ('__reduce__', <built-in method __reduce__ of C object at 0x1155fa7b8>), ('__reduce_ex__', <built-in method __reduce_ex__ of C object at 0x1155fa7b8>), ('__repr__', <method-wrapper '__repr__' of C object at 0x1155fa7b8>), ('__setattr__', <method-wrapper '__setattr__' of C object at 0x1155fa7b8>), ('__sizeof__', <built-in method __sizeof__ of C object at 0x1155fa7b8>), ('__str__', <method-wrapper '__str__' of C object at 0x1155fa7b8>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x7fd01899e4c8>), ('__weakref__', None)]
inspect.getmembers
函数在与定义 __getattr__
方法的 class 一起使用时中断(在 Python 3.x 中):
import inspect
class C:
def __getattr__(self, name):
def wrapper():
print("For MCVE purposes, this does nothing useful.")
return wrapper
print(inspect.getmembers(C()))
产生的错误如下所示:
Traceback (most recent call last):
File "tmp.py", line 9, in <module>
print(inspect.getmembers(C()))
File "/usr/lib/python3.7/inspect.py", line 330, in getmembers
for base in object.__bases__:
TypeError: 'function' object is not iterable
看来问题在于 inspect.getmodule
依赖于 更容易请求宽恕 方法并假设 __bases__
属性要么不存在(抛出 AttributeError
异常)或包含基础 classes.
我可以通过在 C.__getattr__
中显式地抛出 AttributeError
来解决这个问题;但是,我想将 inspect.getmembers
与我无法控制的 classes 对象一起使用。我的问题是:是否有解决此问题的方法,无需对 class C 或其实例进行任何修改?
在调用 inspect.getmembers(c)
之前尝试设置 c.__bases__ = ()
。
编辑:更安全、更详尽的版本以回应评论:
object.__setattr__(c, '__bases__', ())
try:
members = inspect.getmembers(c)
finally:
object.__delattr__(c, '__bases__')
如果出现以下情况,这将失败:
C
定义了__slots__
或由于某些其他原因不允许设置__bases__
,这将引发异常。c
已经有一个不应被覆盖的实际__bases__
属性。这可以用一些额外的代码来检查。
您要求提供一种无需修改 class C 或其实例即可工作的变通方法。这是一个完成这项工作的解决方案,尽管它涉及重写 long getmembers()
函数,这可能也不方便。也许这更适合图书馆的合并请求。
(来自 https://github.com/python/cpython/blob/3.7/Lib/inspect.py 的源代码)
import inspect
def getmembers(object, predicate=None):
"""Return all members of an object as (name, value) pairs sorted by name.
Optionally, only return members that satisfy a given predicate."""
# Line below adds inspect. reference to isclass()
if inspect.isclass(object):
# Line below adds inspect. reference to getmro()
mro = (object,) + inspect.getmro(object)
else:
mro = ()
results = []
processed = set()
names = dir(object)
# :dd any DynamicClassAttributes to the list of names if object is a class;
# this may result in duplicate entries if, for example, a virtual
# attribute with the same name as a DynamicClassAttribute exists
try:
for base in object.__bases__:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute):
names.append(k)
# Line below edited to catch TypeError
except (AttributeError, TypeError):
pass
for key in names:
# First try to get the value via getattr. Some descriptors don't
# like calling their __get__ (see bug #1785), so fall back to
# looking in the __dict__.
try:
value = getattr(object, key)
# handle the duplicate key
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
# could be a (currently) missing slot member, or a buggy
# __dir__; discard and move on
continue
if not predicate or predicate(value):
results.append((key, value))
processed.add(key)
results.sort(key=lambda pair: pair[0])
return results
现在如果你 运行 你的新 getmembers()
:
class C:
def __getattr__(self, name):
def wrapper():
print("For MCVE purposes, this does nothing useful.")
return wrapper
print(getmembers(C()))
您应该得到想要的结果:
[('__class__', <class '__main__.C'>), ('__delattr__', <method-wrapper '__delattr__' of C object at 0x1155fa7b8>), ('__dict__', {}), ('__dir__', <built-in method __dir__ of C object at 0x1155fa7b8>), ('__doc__', None), ('__eq__', <method-wrapper '__eq__' of C object at 0x1155fa7b8>), ('__format__', <built-in method __format__ of C object at 0x1155fa7b8>), ('__ge__', <method-wrapper '__ge__' of C object at 0x1155fa7b8>), ('__getattr__', <bound method C.__getattr__ of <__main__.C object at 0x1155fa7b8>>), ('__getattribute__', <method-wrapper '__getattribute__' of C object at 0x1155fa7b8>), ('__gt__', <method-wrapper '__gt__' of C object at 0x1155fa7b8>), ('__hash__', <method-wrapper '__hash__' of C object at 0x1155fa7b8>), ('__init__', <method-wrapper '__init__' of C object at 0x1155fa7b8>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x7fd01899e4c8>), ('__le__', <method-wrapper '__le__' of C object at 0x1155fa7b8>), ('__lt__', <method-wrapper '__lt__' of C object at 0x1155fa7b8>), ('__module__', '__main__'), ('__ne__', <method-wrapper '__ne__' of C object at 0x1155fa7b8>), ('__new__', <built-in method __new__ of type object at 0x1008b0c48>), ('__reduce__', <built-in method __reduce__ of C object at 0x1155fa7b8>), ('__reduce_ex__', <built-in method __reduce_ex__ of C object at 0x1155fa7b8>), ('__repr__', <method-wrapper '__repr__' of C object at 0x1155fa7b8>), ('__setattr__', <method-wrapper '__setattr__' of C object at 0x1155fa7b8>), ('__sizeof__', <built-in method __sizeof__ of C object at 0x1155fa7b8>), ('__str__', <method-wrapper '__str__' of C object at 0x1155fa7b8>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x7fd01899e4c8>), ('__weakref__', None)]