在 Python 中,当我们使用诸如 Boto 之类的 AWS 模块时,经常需要定义一个简化的包装类。在这个过程中,我们可能会使用 @property 来避免在库中使用特殊的“getter”和“setter”方法。这是 Python 中更符合风格的一种做法。
我们希望以一种类似于简单对象的方式来调用类中的方法,例如:
myclass.myprop = 5 # 将“5”发送到 myprop 的 setter 函数
result = myclass.myprop # 调用 myprop 的 getter 函数并将结果存储起来
我们还会处理多组对象,例如名称/值对的标签,并希望能够像访问容器(例如字典或列表)一样访问它们。例如,对于标签来说:
myclass.tags["newkey"] = "newvalue" # 运行应用标签到 AWS 的函数
result = myclass.tags["newkey"] # 访问 AWS 以获取“newkey”标签的值
2、解决方案
从目前的状况来看,似乎可以通过从 dict 继承类来实现这一点,但我们可能忽略了一些东西。那么,创建这种接口最符合 Python 风格的方法是什么呢?
根据问题中给出的一个回答,我们可以使用 Silas Ray 的解决方案,但对其进行修改,以便这些类可用于定义多个类似于字典的对象。这并不完全干净,但我们可以发布修改后的代码和解释说明,以帮助其他人理解这个解决方案。
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"
# 调用 AWS 来获取标签
def set_tag(self, tag, value):
print("Ran set tag")
# 调用 AWS 来设置标签
def remove_tag(self, tag):
print("Ran remove tag")
# 调用 AWS 来移除标签
def tag_list(self):
print("Ran list tags")
# 调用 AWS 来检索所有标签
def get_foo(self, foo):
print("Ran get foo")
return "fgsfds"
# 调用 AWS 来获取标签
def set_foo(self, foo, value):
print("Ran set foo")
# 调用 AWS 来设置标签
def remove_foo(self, tag):
print("Ran remove foo")
# 调用 AWS 来移除标签
def foo_list(self):
print("Ran list foo")
# 调用 AWS 来检索所有标签
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 会自动用所有者test填充self参数。但它们不是从test中调用的——当我们把它们传递给描述符时,我们传递了类级函数,它们没有self可以引用。为了解决这个问题,我们将self视为一个传统的参数。FakeDict中的obj实际上代表着已经传给它们的AWS对象,所以我们可以简单地将它作为函数的第一个参数传入。 -
造成这一切如此混乱的部分原因是
AWS、ProxyDescriptor和FakeDict之间有很多奇怪的循环引用。如果你对它感到困惑,请记住在ProxyDescriptor和FakeDict中,obj是AWS类的实例,该类已经传给它们,即使FakeDict的实例存在