【问题标题】:How to access a class's property using a partialmethod?如何使用部分方法访问类的属性?
【发布时间】:2015-09-03 01:35:31
【问题描述】:

我需要使用它们的属性在类定义中创建许多类似的函数。对我来说,为此使用部分函数非常有意义。但是,属性没有将我想要的传递给部分方法(例如,传递的是属性对象,而不是它解析为的)。

示例代码:

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

【问题讨论】:

  • 那是因为在执行类体时,还没有类,更不用说实例来绑定描述符协议了。你不能用partialmethod()做你想做的事。
  • 实现此目的的“最佳实践”是什么?如何在 init 中创建所有带有 partial 的方法?
  • 然后你会得到描述符的输出一次,在__init__时间。大概您希望尽可能晚地解析描述符(因此您可以获得“实时”属性值)。我已经写了一个答案。

标签: python python-3.x properties descriptor


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

      猜你喜欢
      • 2015-04-25
      • 1970-01-01
      • 2011-07-27
      • 1970-01-01
      • 2011-08-11
      • 1970-01-01
      • 2011-06-03
      • 2021-11-27
      • 1970-01-01
      相关资源
      最近更新 更多