Python中的容器是否等同于@property?

时间:2013-09-18 19:47:15

标签: python dictionary properties boto descriptor

我在Python中为AWS模块编写了一个简化的包装类(具体来说是Boto)。在这个过程中,我曾多次使用@property来避免特殊的“吸气”问题。和" setter"在我的图书馆中的方法 - 我告诉我这是更加pythonic的方式。当使用该类时,程序员将这些方法称为简单对象,如下所示:

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来做到这一点,但我觉得我在这里遗漏了一些东西。创建这样的界面的最pythonic方法是什么?

编辑:我最终使用了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"]

现在解释一下。

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

大多数"魔法"发生在ProxyDescriptor的__get__方法上。任何带有test.tags的代码都将运行描述符的__get__方法,该方法只检查test(作为obj传入)是否具有名为_tags的属性。如果它没有,它会创建一个 - 之前传递给它的类的实例。这是FakeDict的构造函数被调用的地方。它最终被调用并为AWS引用tags的每个实例创建一次。

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

令人如此困惑的部分原因是AWSProxyDescriptorFakeDict之间存在大量奇怪的循环引用。如果您无法理解它,请记住,在“ProxyDescriptor”中,并且' FakeDict',obj是已传递给它们的AWS类的实例,即使FakeDict的实例存在于AWS类的同一实例中。 / p>

2 个答案:

答案 0 :(得分:3)

实施__getitem__ hook以挂钩object[..]索引或项目访问权限:

>>> 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

要支持项目分配,您可以实施__setitem__(),删除由__delitem__()处理。

您也可以选择支持切片;当有人在您的自定义对象上使用slice notation时,__*item__()挂钩会传递slice object,然后您可以根据切片索引返回值,设置值或删除值:

>>> 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']

答案 1 :(得分:0)

@Martjin Pieters正在使用__getitem__(和__setitem__),但是因为我猜你可能希望带有容器接口的对象充当底层的代理接口(AWS),因此您的容器挂钩将需要从包含对象访问状态,您应该看一下自定义descriptorproperty实际上是描述符本身。

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方法,因为__setitem____getitem__可以访问{{1}中的包含实例obj实例范围,ProxyDescriptor实例范围中的aws_inst类似于AWSTagsProxy方法可以访问property的方式。