3

有没有办法让 Python@property同时充当 setter 和 getter?

我觉得我以前在某个地方见过这个但不记得也不能自己重新创建解决方案。

例如,而不是:

class A(object):
  def __init__(self, b): self.b = b
  def get_c(self): return self.b.c
  def set_c(self, value): self.b.c = value
  c = property(get_c, set_c)

我们可以以某种方式表明,对于A对象,该c属性实际上等同b.c于 getter、setter(如果我们愿意,还有 deleter)。

动机:

当我们需要成为对象(其中的一个实例)A的代理包装器但只共享数据属性而不共享方法时,这将特别有用。诸如此类的属性将允许和对象的数据在同一代码使用时保持完全同步。BbAB

4

5 回答 5

4

如果您尝试将大量属性从任何 A 对象委托给其b成员,那么在 、 和 中执行此操作可能更容易__getattr____setattr__例如__delattr__

class A(object):
    delegated = ['c', 'd', 'e', 'f']
    def __getattr__(self, attr):
        if attr in A.delegated:
            return getattr(self.b, attr)
        raise AttributeError()

为了简洁起见,为了避免解释和之间的区别,我没有在这里展示and 的__setattr__定义。如果您需要更多信息,请参阅文档。__delattr____getattr____getattribute__

这很容易扩展到想要将不同属性代理给不同成员的类:

class A(object):
    b_delegated = ['c', 'd', 'e', 'f']
    x_delegated = ['y', 'z']
    def __getattr__(self, attr):
        if attr in A.b_delegated:
            return getattr(self.b, attr)
        elif attr in A.x_delegated:
            return getattr(self.x, attr)
        else:
            raise AttributeError()

如果您需要动态地委托所有属性,那几乎同样简单。您只需在初始化时或调用时(四种可能性中的哪一种取决于您想要做什么)获得一个self.b's 属性(或's)列表,并使用它来代替静态列表。self.b.__class__b_delegated

您当然可以按名称(例如,删除_private方法)、类型或任意谓词(例如,删除任何callable属性)过滤它。

或者结合以上任何一种。

无论如何,这是在 Python 中进行(尤其是动态)代理的惯用方式。它并不完美,但尝试发明一种不同的机制可能不是一个好主意。

事实上,它并不意味着完美。这是你不应该经常做的事情,也不应该在你做的时候试图掩饰。很明显,一个ctypes.cdll或一个pyobjc模块实际上是委托给别的东西,因为它实际上对用户知道这一点很有用。如果您确实需要将一个类的大部分公共接口委托给另一个类,并且不希望用户知道委托……也许您不需要它。也许最好直接公开私有对象,或者重新组织您的对象模型,以便用户首先与正确的事物进行交互。

于 2013-01-05T02:09:15.717 回答
4

我认为您正在寻找ActiveState 上发布的这个 forwardTo 类。

这个秘籍让你透明地将属性访问转发到你的类中的另一个对象。这样,您可以直接从类实例的某些成员公开功能,例如 foo.baz() 而不是 foo.bar.baz()。

class forwardTo(object):
    """
    A descriptor based recipe that makes it possible to write shorthands
    that forward attribute access from one object onto another.

    >>> class C(object):
    ...     def __init__(self):
    ...         class CC(object):
    ...             def xx(self, extra):
    ...                 return 100 + extra
    ...             foo = 42
    ...         self.cc = CC()
    ...
    ...     localcc = forwardTo('cc', 'xx')
    ...     localfoo = forwardTo('cc', 'foo')
    ...
    >>> print C().localcc(10)
    110
    >>> print C().localfoo
    42

    Arguments: objectName - name of the attribute containing the second object.
               attrName - name of the attribute in the second object.
    Returns:   An object that will forward any calls as described above.
    """
    def __init__(self, objectName, attrName):
        self.objectName = objectName
        self.attrName = attrName
    def __get__(self, instance, owner=None):
        return getattr(getattr(instance, self.objectName), self.attrName)
    def __set__(self, instance, value):
        setattr(getattr(instance, self.objectName), self.attrName, value)
    def __delete__(self, instance):
        delattr(getattr(instance, self.objectName), self.attrName)

对于更健壮的代码,您可能需要考虑替换getattr(instance, self.objectName)operator.attrgetter(self.objectName)(instance). 这将允许objectName成为一个带点的名称(例如,因此您可以A.c成为 的代理A.x.y.z.d)。

于 2013-01-07T04:55:56.747 回答
3

用于创建属性的装饰器语法,然后有完整的自定义描述符,但由于在 Python 和 Python 社区中积极劝阻 setter/getter 伪私有模式,因此实际上并没有广泛分布或常用的方式做你正在寻找的东西。

对于代理对象,您可以使用__getattr____setattr____getattribute__,或者尝试在流程的早期通过使用 或 来操纵__new__事物metaclass

于 2013-01-05T01:47:37.217 回答
1
def make_property(parent, attr):
  def get(self):
    return getattr(getattr(self, parent), attr)
  def set(self, value):
    setattr(getattr(self, parent), attr, value)
  return property(get, set)

class A(object):
  def __init__(self, b): self.b = b
  c = make_property('b', 'c')
于 2013-01-05T01:53:06.380 回答
0

这是另一种实现方式,将属性从一个对象静态转发到另一个对象,但很经济。

它允许在两行中转发 get/set 属性,在一行中转发仅区域属性,利用类级别的动态属性定义和 lambdas。

class A:
    """Classic definition of property, with decorator"""
    _id = ""
    _answer = 42

    @property
    def id(self):
        return self._id

    @id.setter
    def id(self, value):
        self._id = value

    @property
    def what(self):
        return self._answer


class B:
    obj = A()
    # Forward "id" from self.obj
    id = property(lambda self: self.obj.id,
                  lambda self, value: setattr(self.obj, "id", value))
    # Forward read-only property from self.obj
    what = property(lambda self: self.obj.what)
于 2021-01-11T08:56:47.703 回答