如何使用partialmethod访问类的属性?

时间:2015-06-17 17:05:35

标签: python python-3.x properties descriptor

我需要使用它们的属性在类定义中创建许多类似的函数。对我来说,为此使用部分功能是完全合理的。但是,属性没有传递我想要的部分方法(例如传递属性对象,而不是它解析的内容)。

示例代码:

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

如果存在差异,我有时会使用描述符类而不是属性。它有同样的错误:

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

2 个答案:

答案 0 :(得分:1)

partialmethod对象只处理函数对象的描述符委托,对于描述符本身的任何参数都不处理。在创建partialmethod对象时,没有足够的上下文(尚未创建任何类,更不用说该类的实例),并且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

答案 1 :(得分:0)

您必须从实例上下文foo访问self.foo。所以你不能在这里使用partialmethod。定义一种方法:

def echo(self, *args):
    return mk_foobar(self.foo, *args)