3

我正在用 Python 为 AWS 模块(特别是 Boto)编写一个简化的包装类。在这个过程中,我曾经多次@property避免在我的库中使用特殊的“getter”和“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

从我所看到的来看,似乎可以通过 subclassing 来做到这一点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"]

现在解释一下。

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

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

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

AWS造成这种混乱的部分原因是,ProxyDescriptor和.之间有很多奇怪的循环引用FakeDict。如果您无法理解它,请记住,在“ProxyDescriptor”和“FakeDict”中,obj都是已传递给它们的 AWS 类的实例,即使该实例FakeDict存在于同一 AWS 实例中班级。

4

2 回答 2

3

实现__getitem__挂钩以挂钩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

支持item分配,可以实现__setitem__(),删除由处理__delitem__()

您还可以选择支持切片;当有人在您的自定义对象上使用切片表示法__*item__()时,挂钩会传递一个切片对象,然后您可以根据切片索引根据需要返回值、设置值或删除值:

>>> 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']
于 2013-09-18T19:50:08.667 回答
0

__getitem__@Martjin Pieters 与(and )走上了正轨__setitem__,但由于我猜您可能希望您的具有容器接口的对象充当底层接口 (AWS) 的代理,因此您将成为容器钩子需要从包含对象访问状态,您应该考虑编写自定义描述符propertys 实际上是描述符本身。

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

这在任何情况下都更类似于setter和propertygetter 方法,因为您可以访问包含实例(在实例范围内,在实例范围内),类似于方法访问.__setitem____getitem__objProxyDescriptoraws_instAWSTagsProxypropertyself

于 2013-09-18T19:57:25.587 回答