如何使用部分方法访问 class 的 属性?
How to access a class's property using a partialmethod?
我需要使用它们的属性在 class 定义中创建许多类似的函数。对我来说,为此使用部分功能非常有意义。但是,这些属性没有将我想要的传递给部分方法(例如,传递的是 属性 对象,而不是它解析的对象)。
示例代码:
from functools import partialmethod
def mk_foobar(*args):
for a in args:
print(a)
print('bar')
class Foo(object):
@property
def foo(self):
return 'foo'
echo = partialmethod(mk_foobar, foo)
这产生:
> <__main__.Foo object at 0x04326A50>
> <property object at 0x0432A4E0>
> bar
我想要的是:
> <__main__.Foo object at 0x04326A50>
> foo
> bar
以防万一,我有时也会使用描述符 classes 而不是属性。它有同样的错误:
from functools import partialmethod
class Child(object):
def __init__(self, item):
self.item = item
def __get__(self, instance, objtype):
return 'child argument was: %s' % self.item
def mk_foobar(*args):
for a in args:
print(a)
print('foobar')
class Foo(object):
a = Child('b')
echo = partialmethod(mk_foobar, a)
这产生:
<__main__.Foo object at 0x01427690>
<__main__.Child object at 0x01427190>
foobar
当我想要的时候:
<__main__.Foo object at 0x01427690>
'child argument was b'
foobar
您必须从实例上下文 self.foo
访问 foo
。所以你不能在这里使用 partialmethod
。定义方法:
def echo(self, *args):
return mk_foobar(self.foo, *args)
A partialmethod
对象将只处理函数对象的描述符委托,不处理本身是描述符的任何参数。在创建 partialmethod
对象时,没有足够的上下文(尚未创建 class,更不用说那个 class 的实例),并且 partialmethod
对象通常不应作用于您添加的参数。
您可以编写自己的描述符对象来执行委托:
from functools import partial, partialmethod
class partialmethod_with_descriptorargs(partialmethod):
def __get__(self, obj, cls):
get = getattr(self.func, "__get__", None)
result = None
if get is not None:
new_func = get(obj, cls)
if new_func is not self.func:
args = [a.__get__(obj, cls) if hasattr(a, '__get__') else a
for a in self.args]
kw = {k: v.__get__(obj, cls) if hasattr(v, '__get__') else v
for k, v in self.keywords.items()}
result = partial(new_func, *args, **kw)
try:
result.__self__ = new_func.__self__
except AttributeError:
pass
if result is None:
# If the underlying descriptor didn't do anything, treat this
# like an instance method
result = self._make_unbound_method().__get__(obj, cls)
return result
这会尽可能晚地将参数中的任何描述符绑定到方法绑定到的相同上下文。这意味着您的属性会在 查找分部方法 时查找,而不是在创建 partialmethod
或调用方法时查找。
将此应用于您的示例然后会产生预期的输出:
>>> def mk_foobar(*args):
... for a in args:
... print(a)
... print('bar')
...
>>> class Foo(object):
... @property
... def foo(self):
... return 'foo'
... echo = partialmethod_with_descriptorargs(mk_foobar, foo)
...
>>> Foo().echo()
<__main__.Foo object at 0x10611c9b0>
foo
bar
>>> class Child(object):
... def __init__(self, item):
... self.item = item
... def __get__(self, instance, objtype):
... return 'child argument was: %s' % self.item
...
>>> class Bar(object):
... a = Child('b')
... echo = partialmethod_with_descriptorargs(mk_foobar, a)
...
>>> Bar().echo()
<__main__.Bar object at 0x10611cd30>
child argument was: b
bar
任何描述符参数的绑定都在绑定方法的同时发生;在存储延迟调用的方法时考虑到这一点:
>>> class Baz(object):
... _foo = 'spam'
... @property
... def foo(self):
... return self._foo
... echo = partialmethod_with_descriptorargs(mk_foobar, foo)
...
>>> baz = Baz()
>>> baz.foo
'spam'
>>> baz.echo()
<__main__.Baz object at 0x10611e048>
spam
bar
>>> baz_echo = baz.echo # just the method
>>> baz._foo = 'ham'
>>> baz.foo
'ham'
>>> baz_echo()
<__main__.Baz object at 0x10611e048>
spam
bar
>>> baz.echo()
<__main__.Baz object at 0x10611e048>
ham
bar
如果这是一个问题,请不要使用 partialmethod
对象;改为使用装饰器(类似)方法:
from functools import wraps
def with_descriptor_defaults(*defaultargs, **defaultkeywords):
def decorator(fn):
@wraps(fn)
def wrapper(self, *args, **kwargs):
args = [a.__get__(self, type(self)) if hasattr(a, '__get__') else a
for a in defaultargs] + list(args)
kw = {k: v.__get__(self, type(self)) if hasattr(v, '__get__') else v for k, v in defaultkeywords.items()}
kw.update(kwargs)
return fn(self, *args, **kw)
return wrapper
return decorator
并通过传入默认值来使用它,然后是函数:
class Foo(object):
@property
def foo(self):
return 'foo'
echo = with_descriptor_defaults(foo)(mk_foobar)
但它也可以用作装饰器:
class Bar(object):
@property
def foo(self):
return 'foo'
@with_descriptor_defaults(foo)
def echo(*args):
for a in args:
print(a)
print('bar')
这解决了调用时的描述符默认值:
>>> class Baz(object):
... _foo = 'spam'
... @property
... def foo(self):
... return self._foo
... echo = with_descriptor_defaults(foo)(mk_foobar)
...
>>> baz = Baz()
>>> baz_echo = baz.echo
>>> baz._foo = 'ham'
>>> baz_echo()
<__main__.Baz object at 0x10611e518>
ham
bar
我需要使用它们的属性在 class 定义中创建许多类似的函数。对我来说,为此使用部分功能非常有意义。但是,这些属性没有将我想要的传递给部分方法(例如,传递的是 属性 对象,而不是它解析的对象)。
示例代码:
from functools import partialmethod
def mk_foobar(*args):
for a in args:
print(a)
print('bar')
class Foo(object):
@property
def foo(self):
return 'foo'
echo = partialmethod(mk_foobar, foo)
这产生:
> <__main__.Foo object at 0x04326A50>
> <property object at 0x0432A4E0>
> bar
我想要的是:
> <__main__.Foo object at 0x04326A50>
> foo
> bar
以防万一,我有时也会使用描述符 classes 而不是属性。它有同样的错误:
from functools import partialmethod
class Child(object):
def __init__(self, item):
self.item = item
def __get__(self, instance, objtype):
return 'child argument was: %s' % self.item
def mk_foobar(*args):
for a in args:
print(a)
print('foobar')
class Foo(object):
a = Child('b')
echo = partialmethod(mk_foobar, a)
这产生:
<__main__.Foo object at 0x01427690>
<__main__.Child object at 0x01427190>
foobar
当我想要的时候:
<__main__.Foo object at 0x01427690>
'child argument was b'
foobar
您必须从实例上下文 self.foo
访问 foo
。所以你不能在这里使用 partialmethod
。定义方法:
def echo(self, *args):
return mk_foobar(self.foo, *args)
A partialmethod
对象将只处理函数对象的描述符委托,不处理本身是描述符的任何参数。在创建 partialmethod
对象时,没有足够的上下文(尚未创建 class,更不用说那个 class 的实例),并且 partialmethod
对象通常不应作用于您添加的参数。
您可以编写自己的描述符对象来执行委托:
from functools import partial, partialmethod
class partialmethod_with_descriptorargs(partialmethod):
def __get__(self, obj, cls):
get = getattr(self.func, "__get__", None)
result = None
if get is not None:
new_func = get(obj, cls)
if new_func is not self.func:
args = [a.__get__(obj, cls) if hasattr(a, '__get__') else a
for a in self.args]
kw = {k: v.__get__(obj, cls) if hasattr(v, '__get__') else v
for k, v in self.keywords.items()}
result = partial(new_func, *args, **kw)
try:
result.__self__ = new_func.__self__
except AttributeError:
pass
if result is None:
# If the underlying descriptor didn't do anything, treat this
# like an instance method
result = self._make_unbound_method().__get__(obj, cls)
return result
这会尽可能晚地将参数中的任何描述符绑定到方法绑定到的相同上下文。这意味着您的属性会在 查找分部方法 时查找,而不是在创建 partialmethod
或调用方法时查找。
将此应用于您的示例然后会产生预期的输出:
>>> def mk_foobar(*args):
... for a in args:
... print(a)
... print('bar')
...
>>> class Foo(object):
... @property
... def foo(self):
... return 'foo'
... echo = partialmethod_with_descriptorargs(mk_foobar, foo)
...
>>> Foo().echo()
<__main__.Foo object at 0x10611c9b0>
foo
bar
>>> class Child(object):
... def __init__(self, item):
... self.item = item
... def __get__(self, instance, objtype):
... return 'child argument was: %s' % self.item
...
>>> class Bar(object):
... a = Child('b')
... echo = partialmethod_with_descriptorargs(mk_foobar, a)
...
>>> Bar().echo()
<__main__.Bar object at 0x10611cd30>
child argument was: b
bar
任何描述符参数的绑定都在绑定方法的同时发生;在存储延迟调用的方法时考虑到这一点:
>>> class Baz(object):
... _foo = 'spam'
... @property
... def foo(self):
... return self._foo
... echo = partialmethod_with_descriptorargs(mk_foobar, foo)
...
>>> baz = Baz()
>>> baz.foo
'spam'
>>> baz.echo()
<__main__.Baz object at 0x10611e048>
spam
bar
>>> baz_echo = baz.echo # just the method
>>> baz._foo = 'ham'
>>> baz.foo
'ham'
>>> baz_echo()
<__main__.Baz object at 0x10611e048>
spam
bar
>>> baz.echo()
<__main__.Baz object at 0x10611e048>
ham
bar
如果这是一个问题,请不要使用 partialmethod
对象;改为使用装饰器(类似)方法:
from functools import wraps
def with_descriptor_defaults(*defaultargs, **defaultkeywords):
def decorator(fn):
@wraps(fn)
def wrapper(self, *args, **kwargs):
args = [a.__get__(self, type(self)) if hasattr(a, '__get__') else a
for a in defaultargs] + list(args)
kw = {k: v.__get__(self, type(self)) if hasattr(v, '__get__') else v for k, v in defaultkeywords.items()}
kw.update(kwargs)
return fn(self, *args, **kw)
return wrapper
return decorator
并通过传入默认值来使用它,然后是函数:
class Foo(object):
@property
def foo(self):
return 'foo'
echo = with_descriptor_defaults(foo)(mk_foobar)
但它也可以用作装饰器:
class Bar(object):
@property
def foo(self):
return 'foo'
@with_descriptor_defaults(foo)
def echo(*args):
for a in args:
print(a)
print('bar')
这解决了调用时的描述符默认值:
>>> class Baz(object):
... _foo = 'spam'
... @property
... def foo(self):
... return self._foo
... echo = with_descriptor_defaults(foo)(mk_foobar)
...
>>> baz = Baz()
>>> baz_echo = baz.echo
>>> baz._foo = 'ham'
>>> baz_echo()
<__main__.Baz object at 0x10611e518>
ham
bar