Python中的容器是否有与@property等价的属性?

Python中的容器是否有与@property等价的属性?,python,dictionary,properties,boto,descriptor,Python,Dictionary,Properties,Boto,Descriptor,我正在用Python为AWS模块(特别是Boto)编写一个简化的包装器类。在这个过程中,我多次使用@property来避免在我的库中使用特殊的“getter”和“setter”方法——我被告知这是一种更具python风格的方法。当使用该类时,程序员调用这些方法,就像调用简单对象一样,如下所示: myclass.myprop = 5 # sends "5" to myprop's setter function result = myclass.myprop # calls

我正在用Python为AWS模块(特别是Boto)编写一个简化的包装器类。在这个过程中,我多次使用
@property
来避免在我的库中使用特殊的“getter”和“setter”方法——我被告知这是一种更具python风格的方法。当使用该类时,程序员调用这些方法,就像调用简单对象一样,如下所示:

myclass.myprop = 5         # sends "5" to myprop's setter function
result = myclass.myprop    # calls myprop's getter function and stores the result
但我也在处理几组对象——例如,标记的名称/值对——我希望像访问容器一样访问它们,可能是字典或列表。以标签为例:

myclass.tags["newkey"] = "newvalue"   # runs a function that applies tag in AWS
result = myclass.tags["newkey"]       # accesses AWS to get value of "newkey" tag
从我所看到的情况来看,通过子类化
dict
似乎可以做到这一点,但我觉得这里缺少了一些东西。创建这样一个接口的最具python风格的方法是什么

编辑:我最终使用了Silas Ray的解决方案,但对其进行了修改,以便这些类可以用于定义多个类似dict的对象。它并不完全干净,但我将在这里发布我修改过的代码和解释,以帮助其他有困难的人

class FakeDict(object):

    def __init__(self, obj, getter, setter, remover, lister):
        self.obj = obj
        self.getter = getter
        self.setter = setter
        self.lister = lister
        self.remover = remover

    def __getitem__(self, key):
        return self.getter(self.obj, key)

    def __setitem__(self, key, value):
        self.setter(self.obj, key, value)

    def __delitem__(self, key):
        self.remover(self.obj, key)

    def _set(self, new_dict):
        for key in self.lister(self.obj):
            if key not in new_dict:
                self.remover(self.obj, key)
        for key, value in new_dict.iteritems():
            self.setter(self.obj, key, value)

class ProxyDescriptor(object):

    def __init__(self, name, klass, getter, setter, remover, lister):
        self.name = name
        self.proxied_class = klass
        self.getter = getter
        self.setter = setter
        self.remover = remover
        self.lister = lister

    def __get__(self, obj, klass):
        if not hasattr(obj, self.name):
            setattr(obj, self.name, self.proxied_class(obj, self.getter, self.setter, self.remover, self.lister))
        return getattr(obj, self.name)

    def __set__(self, obj, value):
        self.__get__(obj, obj.__class__)._set(value)

class AWS(object):

    def get_tag(self, tag):
        print "Ran get tag"
        return "fgsfds"
        # Call to AWS to get tag

    def set_tag(self, tag, value):
        print "Ran set tag"
        # Call to AWS to set tag

    def remove_tag(self, tag):
        print "Ran remove tag"
        # Call to AWS to remove tag

    def tag_list(self):
        print "Ran list tags"
        # Call to AWS to retrieve all tags

    def get_foo(self, foo):
        print "Ran get foo"
        return "fgsfds"
        # Call to AWS to get tag

    def set_foo(self, foo, value):
        print "Ran set foo"
        # Call to AWS to set tag

    def remove_foo(self, tag):
        print "Ran remove foo"
        # Call to AWS to remove tag

    def foo_list(self):
        print "Ran list foo"
        # Call to AWS to retrieve all tags

    tags = ProxyDescriptor('_tags', FakeDict, get_tag, set_tag, remove_tag, tag_list)
    foos = ProxyDescriptor('_foos', FakeDict, get_foo, set_foo, remove_foo, foo_list)


test = AWS()

tagvalue = test.tags["tag1"]
print tagvalue
test.tags["tag1"] = "value1"
del test.tags["tag1"]

foovalue = test.foos["foo1"]
print foovalue
test.foos["foo1"] = "value1"
del test.foos["foo1"]
现在来解释一下

标记
foos
都是ProxyDescriptor的类级实例,在定义类时只实例化一次。它们已经移到了底部,因此可以引用上面的函数定义,这些函数定义用于定义各种字典操作的行为

ProxyDescriptor的大部分“魔法”都发生在
\uuuu get\uuuu
方法上。任何带有
test.tags
的代码都将运行描述符的
\uuuu get\uuu
方法,该方法只检查
test
(作为
obj
传入)是否具有名为
\u tags
的属性。如果没有,它将创建一个实例—以前传递给它的类的实例。这就是调用FakeDict的构造函数的地方。对于引用了
tags
AWS
的每个实例,它最终被调用并创建一次

我们通过描述符和
FakeDict
的构造函数传递了四个函数集,但是在
FakeDict
中使用它们有点棘手,因为上下文已经改变了。如果我们直接在AWS类的实例中使用函数(如
test.get_tag
),Python会自动用所有者
test
填充
self
参数。但是它们不是从
test
调用的-当我们将它们传递给描述符时,我们传递了类级函数,这些函数没有
self
可引用。为了解决这个问题,我们将
self
视为一个传统的论点
obj
in
FakeDict
实际上代表了我们的
test
对象,因此我们可以将它作为第一个参数传递给函数

部分原因是在
AWS
ProxyDescriptor
FakeDict
之间有很多奇怪的循环引用。如果您在理解它时遇到困难,请记住,在“ProxyDescriptor”和“FakeDict”中,
obj
是已传递给它们的AWS类的实例,即使
FakeDict
的实例存在于AWS类的同一实例中。

实现挂钩到
对象[…]
索引或项目访问:

>>> class DuplexContainer(object):
...     def __init__(self):
...         self._values = ['foo', 'bar', 'baz']
...     def __getitem__(self, item):
...         if item in self._values:
...             return self._values.index(item)
...         return self._values[item]
... 
>>> d = DuplexContainer()
>>> d[1]
'bar'
>>> d['baz']
2
要支持项目分配,您可以实现
\uuuuu setitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

您还可以选择支持切片;当有人在您的自定义对象上使用时,会向
\uu*item\uuux()
钩子传递一个钩子,然后您可以根据切片索引返回值、设置值或删除值:

>>> class DuplexContainer(object):
...     def __init__(self):
...         self._values = ['foo', 'bar', 'baz']
...     def __getitem__(self, item):
...         if isinstance(item, slice):
...             return ['Slice-item {}'.format(self._values[i]) 
...                     for i in range(*item.indices(len(self._values)))]
...         if item in self._values:
...             return self._values.index(item)
...         return self._values[item]
... 
>>> d = DuplexContainer()
>>> d[:2]
['Slice-item foo', 'Slice-item bar']

@Martjin Pieters在
\uu getitem\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
(和
)方面是正确的,但是我猜,你应该考虑写一个习惯<代码>属性实际上是描述符本身

class AWSTagsProxy(object):

    def __init__(self, aws_inst):

        self.aws_inst = aws_inst

    def __getitem__(self, key):

        return self.aws_inst.get_tag(key)

    def __setitem__(self, key, value):

        self.aws_inst.set_tag(key, value)

    def __delitem__(self, key):

        self.aws_inst.remove_tag(key)

    def _set(self, tag_dict):

        for tag in self.aws_inst.tag_list():
            if tag not in tag_dict:
                self.aws_inst.remove_tag(tag)
        for tag, value in tag_dict.iteritems():
            self.aws_inst.set_tag(tag, value)

class ProxyDescriptor(object):

    def __init__(self, name, klass):

        self.name = name
        self.proxied_class = klass

    def __get__(self, obj, klass):

        if not hasattr(obj, self.name):
            setattr(obj, self.name, self.proxied_class(obj))
        return getattr(obj, self.name)

    def __set__(self, obj, value):

        self.__get__(obj, obj.__class__)._set(value)

class AWS(object):

    tags = ProxyDescriptor('_tags', AWSTagsProxy)

    def get_tag(self, tag):

        # Call to AWS to get tag

    def set_tag(self, tag, value):

        # Call to AWS to set tag

    def remove_tag(self, tag):

        # Call to AWS to remove tag

    def tag_list(self):

        # Call to AWS to retrieve all tags

这在任何情况下都更类似于
property
setter和getter方法,因为您的
\uuuu setitem\uuuu
\uuu getitem\uuuuu
可以访问包含的实例(
ProxyDescriptor
实例范围中的
aws\u inst
实例范围中的
obj
)与
属性
方法访问
self

的方式类似,不要忘记
\uuuuu setitem\uuuuuuuu
,也可能是
\uuuu delitem\uuuuuuu
。Pythonic方式是既不使用getter和setter方法,也不使用
@property
。属性访问和属性分配的语义本身就足够了。除非!除非您需要验证输入或计算输出。请注意,您可以先不使用属性,然后切换到使用
@property
,而无需更改客户端代码。所以你只需要在需要的时候添加它。这非常接近我想要的,我可以在我的代码中使用它,但我不知道它实际上是如何工作的。你能更详细地解释一下下面发生了什么吗?例如,AWSTagsProxy的构造函数在哪里被调用?另外,tags对象是在类级别而不是在init上被调用的-这对类的多个实例有影响吗?有没有办法将函数传递到AWSTagsProxy,而不是硬编码为“set_tag”、“remove_tag”等。?我很想让它们成为泛型的,只是为我的包装类中的每一个类似dict的构造实例化它们。很抱歉问了这么多问题-我真的很想更好地理解描述符。从你的问题编辑中,看起来你差不多明白了。我这样组织事情的原因是