Python:结构化数据的惯用属性?

Phi*_*hil 7 python properties object inner-classes

我的代码中有一股难闻的气味.也许我只需要让它消失一段时间,但现在它让我烦恼.

我需要创建三个不同的输入文件来运行三个辐射传输建模(RTM)应用程序,以便我可以比较它们的输出.这个过程将重复数千组输入,所以我用python脚本自动化它.

我想将输入参数存储为一个通用的python对象,我可以将其传递给另外三个函数,每个函数都会将该通用对象转换为运行他们负责的RTM软件所需的特定参数.我认为这是有道理的,但随意批评我的方法.

每个RTM软件都有许多可能的输入参数.他们中的许多人都在重叠.他们中的大多数都保持合理的默认值,但应该很容易改变.

我开始时很简单 dict

config = {
    day_of_year: 138,
    time_of_day: 36000, #seconds
    solar_azimuth_angle: 73, #degrees
    solar_zenith_angle: 17, #degrees
    ...
}
Run Code Online (Sandbox Code Playgroud)

有很多参数,它们可以干净地分类成组,所以我想在以下内容中使用dicts dict:

config = {
    day_of_year: 138,
    time_of_day: 36000, #seconds
    solar: {
        azimuth_angle: 73, #degrees
        zenith_angle: 17, #degrees
        ...
    },
    ...
}
Run Code Online (Sandbox Code Playgroud)

我喜欢.但是有很多冗余属性.例如,如果另一个是已知的,那么可以找到太阳方位角和天顶角,那么为什么要对两者进行硬编码呢?所以我开始研究python的内置property.如果我将它存储为对象属性,这可以让我对数据做一些漂亮的事情:

class Configuration(object):
    day_of_year = 138,
    time_of_day = 36000, #seconds
    solar_azimuth_angle = 73, #degrees
    @property
    def solar_zenith_angle(self):
        return 90 - self.solar_azimuth_angle
    ...

config = Configuration()
Run Code Online (Sandbox Code Playgroud)

但是现在我已经失去了第二个dict例子中的结构.

请注意,一些属性是比我少琐碎的solar_zenith_angle例子,可能需要访问其他属性的属性组之外这是一部分.例如,我可以计算solar_azimuth_angle我是否知道一年中的某一天,时间,纬度和经度.

我在找什么:

存储配置数据的简单方法,其值可以以统一的方式访问,结构良好,可以作为属性(实际值)或属性(从其他属性计算)存在.

一种无聊的可能性:

将所有内容存储在前面概述的dicts的dict中,并在对象上运行其他函数并计算可计算值?这听起来并不好玩.或者干净.对我来说这听起来很混乱和令人沮丧.

一个有效的丑陋的:

经过很长一段时间尝试不同的策略并且大多数没有在哪里,我想出了一个似乎有效的解决方案:

我的课程:(闻起来有点功能,呃,时髦.确定.)

class SubConfig(object):
    """
    Store logical groupings of object attributes and properties.

    The parent object must be passed to the constructor so that we can still
    access the parent object's other attributes and properties. Useful if we
    want to use them to compute a property in here.
    """
    def __init__(self, parent, *args, **kwargs):
        super(SubConfig, self).__init__(*args, **kwargs)
        self.parent = parent


class Configuration(object):
    """
    Some object which holds many attributes and properties.

    Related configurations settings are grouped in SubConfig objects.
    """
    def __init__(self, *args, **kwargs):
        super(Configuration, self).__init__(*args, **kwargs)
        self.root_config = 2

        class _AConfigGroup(SubConfig):
            sub_config = 3
            @property
            def sub_property(self):
                return self.sub_config * self.parent.root_config
        self.group = _AConfigGroup(self) # Stinky?!
Run Code Online (Sandbox Code Playgroud)

我如何使用它们:(按照我的意愿工作)

config = Configuration()

# Inspect the state of the attributes and properties.
print("\nInitial configuration state:")
print("config.rootconfig: %s" % config.root_config)
print("config.group.sub_config: %s" % config.group.sub_config)
print("config.group.sub_property: %s (calculated)" % config.group.sub_property)

# Inspect whether the properties compute the correct value after we alter
# some attributes.
config.root_config = 4
config.group.sub_config = 5

print("\nState after modifications:")
print("config.rootconfig: %s" % config.root_config)
print("config.group.sub_config: %s" % config.group.sub_config)
print("config.group.sub_property: %s (calculated)" % config.group.sub_property)
Run Code Online (Sandbox Code Playgroud)

行为:(执行上述所有代码的输出,如预期的那样)

Initial configuration state:
config.rootconfig: 2
config.group.sub_config: 3
config.group.sub_property: 6 (calculated)

State after modifications:
config.rootconfig: 4
config.group.sub_config: 5
config.group.sub_property: 20 (calculated)
Run Code Online (Sandbox Code Playgroud)

为什么我不喜欢它:

将配置数据存储在主对象内部的类定义中__init__()并不会让人感到优雅.特别是在定义之后必须立即实例化它们.啊.我可以为父类处理它,当然,但是在构造函数中执行它...

在主Configuration对象之外存储相同的类也不会感觉优雅,因为内部类中的属性可能取决于Configuration(或其内部的兄弟)的属性.

我可以处理定义所有内容之外的函数,所以内部有类似的东西

@property
def solar_zenith_angle(self):
   return calculate_zenith(self.solar_azimuth_angle)
Run Code Online (Sandbox Code Playgroud)

但我无法弄清楚如何做类似的事情

@property
def solar.zenith_angle(self):
    return calculate_zenith(self.solar.azimuth_angle)
Run Code Online (Sandbox Code Playgroud)

(当我试图变得聪明时,我总是遇到<property object at 0xXXXXX>)

那么正确的方法是什么?我错过了一些基本的或采取非常错误的方法吗?有谁知道一个聪明的解决方案?

救命!我的python代码不漂亮!我一定做错了什么!

LaC*_*LaC 1

好吧,这是一个丑陋的方法,至少可以确保您的属性被调用:

class ConfigGroup(object):
    def __init__(self, config):
        self.config = config

    def __getattribute__(self, name):
        v = object.__getattribute__(self, name)
        if hasattr(v, '__get__'):
            return v.__get__(self, ConfigGroup)
        return v

class Config(object):
    def __init__(self):
        self.a = 10
        self.group = ConfigGroup(self)
        self.group.a = property(lambda group: group.config.a*2)
Run Code Online (Sandbox Code Playgroud)

当然,此时您最好完全放弃property并仅检查该属性是否可在__getattribute__.

或者你可以全力以赴享受元类的乐趣:

def config_meta(classname, parents, attrs):
    defaults = {}
    groups = {}
    newattrs = {'defaults':defaults, 'groups':groups}
    for name, value in attrs.items():
        if name.startswith('__'):
            newattrs[name] = value
        elif isinstance(value, type):
            groups[name] = value
        else:
            defaults[name] = value
    def init(self):
        for name, value in defaults.items():
            self.__dict__[name] = value
        for name, value in groups.items():
            group = value()
            group.config = self
            self.__dict__[name] = group
    newattrs['__init__'] = init
    return type(classname, parents, newattrs)

class Config2(object):
    __metaclass__ = config_meta
    a = 10
    b = 2
    class group(object):
        c = 5
        @property
        def d(self):
            return self.c * self.config.a
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

>>> c2.a
10
>>> c2.group.d
50
>>> c2.a = 6
>>> c2.group.d
30
Run Code Online (Sandbox Code Playgroud)

最终编辑(?):如果您不想self.config在子组属性定义中使用“回溯”,则可以使用以下内容:

class group_property(property):
    def __get__(self, obj, objtype=None):
        return super(group_property, self).__get__(obj.config, objtype)

    def __set__(self, obj, value):
        super(group_property, self).__set__(obj.config, value)

    def __delete__(self, obj):
        return super(group_property, self).__del__(obj.config)

class Config2(object):
    ...
    class group(object):
        ...
        @group_property
        def e(config):
            return config.group.c * config.a
Run Code Online (Sandbox Code Playgroud)

group_property 接收基本配置对象而不是组对象,因此路径始终从根开始。因此,e等价于前面定义的d

顺便说一句,支持嵌套组留给读者作为练习。