我正在用 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
)是否有一个名为_tags
yet 的属性。如果没有,它会创建一个 - 一个之前传递给它的类的实例。这FakeDict
是调用构造函数的地方。AWS
对于每个引用where的实例,它最终会被调用和创建一次tags
。
我们已经通过描述符和FakeDict
构造函数传递了这组四个函数——但是在内部使用它们FakeDict
有点棘手,因为上下文已经改变了。如果我们直接在 AWS 类的实例中使用函数(如test.get_tag
),Python 会自动self
用 owner 填充参数test
。但是它们并没有被调用test
——当我们将它们传递给描述符时,我们传递了类级别的函数,这些函数没有self
引用。为了解决这个问题,我们将self
其视为传统论点。 obj
inFakeDict
实际上代表我们的test
对象 - 所以我们可以将它作为第一个参数传递给函数。
AWS
造成这种混乱的部分原因是,ProxyDescriptor
和.之间有很多奇怪的循环引用FakeDict
。如果您无法理解它,请记住,在“ProxyDescriptor”和“FakeDict”中,obj
都是已传递给它们的 AWS 类的实例,即使该实例FakeDict
存在于同一 AWS 实例中班级。