我在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方法是什么?
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的类级实例,并且在定义类时仅实例化一次。它们已被移到底部,因此它们可以引用它们上面的函数定义,这些函数定义用于定义各种字典操作的行为。
大多数"魔法"发生在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
对象 - 所以我们可以将其作为函数的第一个参数传递。
令人如此困惑的部分原因是AWS
,ProxyDescriptor
和FakeDict
之间存在大量奇怪的循环引用。如果您无法理解它,请记住,在“ProxyDescriptor”中,并且' FakeDict',obj
是已传递给它们的AWS类的实例,即使FakeDict
的实例存在于AWS类的同一实例中。 / p>
答案 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),因此您的容器挂钩将需要从包含对象访问状态,您应该看一下自定义descriptor。 property
实际上是描述符本身。
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
的方式。