有没有更好的方法为动态命名的属性创建getter属性?

3
我在我的一个类中建模json数据时有许多相似的字段。所有字段都初始化为None,以帮助静态工具知道它们的存在,然后辅助函数根据它们建模的json数据来帮助初始化它们(如果你想知道,这里是The SecondHandSongs API)。
一些数据只检索额外数据的uri,因此我想使用初始化隐藏变量为None并在第一次请求时获取/解码数据的旧技巧。但是 setattr(self.__class__)看起来很丑陋。
有没有更好的方法来做(在Python中动态设置属性)?
def  _initialize_url_fields(self, attrNamesToFactoryFunction, json_data):
    for (name, factoryFunction) in attrNamesToFactoryFunction.iteritems():
        try:
            url = json_data[name]
        except KeyError:
            continue
        setattr(self, name + "_url", url)
        setattr(self, "_" + name, None)    
        setattr(self.__class__, name, property(lambda s: s._getter("_" + name, url, factoryFunction)))        

def _getter(self, hidden_prop_name, url, factoryFunction):
    if not getattr(self, hidden_prop_name):
        json_data = SHSDataAcess.getSHSData(url)
        setattr(self, hidden_prop_name, factoryFunction(json_data))
    return getattr(self, hidden_prop_name)

编辑:

我刚刚意识到我正在尝试在从 init 调用的实例方法中设置属性。正如可以预料的那样,第二次失败了。

编辑2:

在意识到我正在为每个对象设置属性后(如果不是单例类,则不可能),以下是我修复它的方法:

class ShsData(object):
    def  _initialize_url_fields(self, attrNamesToFactoryFunctions, json_data):
        for (name, factoryFunction) in attrNamesToFactoryFunctions.items():
            self._getter_factory_functions[name] = factoryFunction 
            uri = None
            try:
                uri = json_data[name]
            except KeyError:
                pass
            setattr(self, name + "_uri", uri)
            setattr(self, "_" + name, None)

def _fetch_shs_data_on_first_access_getter(base_prop_name):
    def getter(self):
        factoryFunction = self._getter_factory_functions[base_prop_name]
        hidden_prop_name = "_" + base_prop_name 
        uri_prop_name = base_prop_name + "_uri"
        if not getattr(self, hidden_prop_name):
            if getattr(self, uri_prop_name):
                json_data = SHSDataAcess.getSHSData(getattr(self, uri_prop_name))
                setattr(self, hidden_prop_name, factoryFunction(json_data))
            else:
                return None
        return getattr(self, hidden_prop_name)
    return getter

class ShsArtist(ShsData):

    performances_data = property(_fetch_shs_data_on_first_access_getter("performances"))
    creditedWorks_data = property(_fetch_shs_data_on_first_access_getter("creditedWorks"))
    releases_data = property(_fetch_shs_data_on_first_access_getter("releases"))

    def __init__(self, json_data):
        ...
        self._initialize_url_fields({"performances": lambda xs: [ShsPerformance(x) for x in xs],
                                     "creditedWorks": lambda xs: [ShsWork(x) for x in xs],
                                     "releases": lambda xs: [ShsRelease(x) for x in xs]},
                                    json_data)

3
另一种选择是使用自定义的__getattr__,但我认为这并不会更美观。 - Martijn Pieters
属性有什么问题:http://docs.python.org/howto/descriptor.html。我相信你可以使用缓存装饰器来构建它。 - Marcin
@Marcin:请注意,OP已经在使用属性,并动态地分配它们。 - Martijn Pieters
如果你不喜欢 getattrsetattr,你可以使用 __dict__。 尽管我认为这并没有解决任何问题。 - Bakuriu
2
@MartijnPieters 对,我不明白为什么他不只是添加缓存。 - Marcin
我会建议在这里编写一个自定义的 __getattr__。如果这个类经常用于不同的自定义属性名进行实例化,你最终会创建很多与该类相关联的getter,并且这些getter将无法进行垃圾回收。此外,如果不同的工厂函数与相同的键相关联,则会覆盖另一个工厂函数。这看起来像是一堆等待发生的错误... - Lucas Wiman
1个回答

0

我可能会子类化属性来处理您的常见情况。就像这样:

class shs_klass_property(property):

    def __init__(self, name, klass):
        self.name = name
        self.klass = klass
        self.cached_name = '_%s' % name
        super(shs_klass_property, self).__init__(self.compute)

    def compute(self, obj):
        if not hasattr(obj, self.cached_name):
            if self.name in obj._json_data:
                # if needed handle cases where this isn't a list
                val = [self.klass(x) for x in obj._json_data[self.name]]
            else:
                val = None
            setattr(obj, self.cached_name, val)
        return getattr(obj, self.cached_name)

class ShsData(object):
    def __init__(self, json_data):
        self._json_data = json_data

class ShsWork(ShsData):
    pass

class ShsArtist(ShsData):
    works = shs_klass_property('works', ShsWork)

如果您总是想要设置URI,您可以尝试以下方法:
# if you change this to pass in "_json_data" too,
# you'd have a simple general purpose delegation decorator
class shs_json_property(property):

    def __init__(self, name):
        self.name = name
        super(shs_json_property, self).__init__(self.compute)

    def compute(self, obj):
        return obj._json_data.get(self.name, None)

# a helper to set both. not necessary but saves a line of code.
def shs_property_pair(name, klass):
    return (shs_klass_property(name, klass),
            shs_json_property(name))

class ShsArtist(ShsData):
    works, works_uri = shs_property_pair('works', ShsWork)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接