我正在写一个包装getter / setter类函数或方法的类。因为我希望能够包装自己的类以及外部类,所以包装器类应充当(类似于属性的)类函数装饰器,并且也应以常规方式(即直接创建实例)工作。
换句话说,我希望包装器尽可能地灵活。 包装器类的非常简化的定义如下。
(我在macOS上使用的是Python 3.6.8)
class Wrapper(object):
def __init__(self, func):
self._f = func # func is a simple getter function with no arguments
def __get__(self, obj, owner):
if obj is None:
return self
return self.value(obj)
def value(self, obj=None):
if requires_obj(self._f):
return self._f(obj)
else:
return self._f()
此定义使得可以通过三种不同的方式使用包装器。
第一个选项:类函数装饰器
class Foo(object):
def __init__(self, bar):
self._b = bar
@Wrapper
def bar(self):
return self._b
foo = Foo(3)
value = foo.bar # property-like
value = Foo.bar.value(obj=foo) # obj argument required
第二个选项:具有未绑定功能的直接实例化
from module import Foo # external class, don't want to mess up with decorators
foo = Foo(3)
wrapper = Wrapper(Foo.bar) # instantiation with unbound function
value = wrapper.value(obj=foo) # obj argument required
第三个选项:使用方法直接实例化
from module import Foo # external class, don't want to mess up with decorators
foo = Foo(3)
wrapper = Wrapper(foo.bar) # instantiation with method
value = wrapper.value() # obj argument not required
但是,由于调用签名不同,我需要知道是包装一个未绑定的函数还是方法(未绑定的函数需要一个实例作为第一个参数)。换句话说,我想知道如何实现上面的requires_obj
功能?
对于纯Python类,我可以使用标准库中的types
模块很容易地弄清楚这一点(请参见下文)。但是,对于内置和外部类(例如C / C ++包装器),这种方法似乎不起作用...
正如我所提到的,对于Python类,以下工作正常。
def requires_obj(func):
if isinstance(func, types.MethodType):
return False
elif isinstance(func, types.FunctionType):
return True
raise TypeError('no idea...')
对于内置函数/方法,我希望以下代码可以工作。
def requires_obj(func):
if isinstance(func, types.MethodType):
return False
elif isinstance(func, types.FunctionType):
return True
elif isinstance(func, types.BuiltinMethodType):
return False
elif isinstance(func, types.BuiltinFunctionType):
return True
raise TypeError('no idea...')
但是,不是。
>>> requires_obj(str.upper) # expected: True
TypeError: no idea...
>>> requires_obj("Hello".upper) # expected: False
False
>>> from PyQt5.QtGui import QColor # PyQt5 is a C++ wrapper
>>> requires_obj(QColor.rgb) # expected: True
False
>>> requires_obj(QColor("red").rgb) # expected: False
False
我缺少什么?如何确定包装函数是未绑定还是已绑定,即需要实例作为第一个参数?
解决方案
我想出了一种可行的方法(至少在我测试过的情况下如此)。
def requires_obj(func):
# builtin types, e.g. str, dict, ...
if hasattr(func, '__objclass__'):
return True
# external (builtin) types, e.g. C++ wrapper
elif hasattr(func, '__self__') and func.__self__ is None:
return True
# pure Python types
elif isinstance(func, types.FunctionType):
return True
return False
>>> requires_obj(dict.get)
True
>>> requires_obj({'name': 'value'}.get)
False
>>> requires_obj(str.upper)
True
>>> requires_obj("Hello".upper)
False
>>> requires_obj(QColor.rgb)
True
>>> requires_obj(QColor('red').rgb)
False
它仍然认为必须有一种更简单的方法来实现requires_obj
函数,并且我不明白为什么使用types
模块的方法不起作用,但是至少该实现可用于我。