使用Python属性进行属性映射

时间:2013-01-05 01:44:24

标签: python

有没有办法让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属性对于getter,setter(以及我们喜欢的删除函数)实际上等同于b.c

动机:

当我们需要A作为B对象(其中b是一个实例)的代理包装器但仅共享数据属性而没有方法时,这将特别有用。这些属性允许AB对象的数据保持完全同步,而两者都由相同的代码使用。

5 个答案:

答案 0 :(得分:3)

decorator syntax for creating properties,然后有完整的custom-defined descriptors,但由于在Python和Python社区中主动阻止了setter / getter伪私有模式,因此实际上并没有广泛分布或常用的方式来做你想要的。

对于代理对象,您可以使用__getattr____setattr____getattribute__,或尝试通过使用__new__或{{0}来欺骗过程中的某些内容{1}}。

答案 1 :(得分:2)

如果您尝试将任何A对象的大量属性委托给其b成员,那么在__getattr____setattr__和{{{{}}}内部执行该操作可能更容易。 1}},例如:

__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() __setattr__定义,并且无需解释__delattr____getattr__之间的区别。如果您需要更多信息,请参阅the docs

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

__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)(四种可能性中的哪一种取决于您想要做什么),并使用该列表代替静态列表self.b.__class__

您当然可以按名称(例如,删除b_delegated方法)或按类型或任意谓词(例如,删除任何_private属性)对此进行过滤。

或者结合以上任何一种。

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

事实上,它并不是真正意义上的完美。这是你不应该经常做的事情,不应该在你做的时候试图伪装。很明显,callablectypes.cdll模块实际上委托给其他人,因为它对用户来说实际上很有用。如果你真的需要将一个类的大多数公共接口委托给另一个类,并且不希望用户知道委托...也许你不需要它。也许最好直接公开私有对象,或者重新组织对象模型,以便用户首先与正确的事物进行交互。

答案 2 :(得分:2)

我认为您正在寻找在ActiveState上发布的this forwardTo class

  

此配方允许您透明地将属性访问转发给另一个   你班上的对象。这样,您可以公开某些功能   您的班级实例的成员,例如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的代理。)

答案 3 :(得分: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')

答案 4 :(得分:0)

这是另一种方法,静态地将属性从一个对象转发到另一个对象,但要经济。

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

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)