Python:结构化数据的习惯属性?
我的代码中有一种难闻的气味。 也许我只需要让它散发一点点,但现在它正在扰乱我。
我需要创建三个不同的输入文件来运行三个辐射传输建模(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
...
}
有很多参数,并且它们可以清晰地分为组,所以我想在dict
中使用dict
:
config = {
day_of_year: 138,
time_of_day: 36000, #seconds
solar: {
azimuth_angle: 73, #degrees
zenith_angle: 17, #degrees
...
},
...
}
我喜欢。 但是有很多冗余属性。 例如,如果另一个是已知的,可以找到太阳的方位角和天顶角,那么为什么要硬编码呢? 所以我开始研究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()
但是现在我已经失去了第二个dict
例子中的结构。
请注意,某些属性不如我的solar_zenith_angle
示例微不足道,并且可能需要访问属于它所属属性组之外的其他属性。 例如,如果我知道一年中的某一天,一天中的时间,纬度和经度,我可以计算出solar_azimuth_angle
。
我在找什么:
存储配置数据的一种简单方法,其值可以全部以统一的方式访问,结构良好,可以作为属性(实际值)或属性(从其他属性计算)存在。
一种无聊的可能性:
将所有内容都存储在前面概述的词典中,并让其他函数遍历对象并计算可计算值? 这听起来不好玩。 或干净。 对我来说,这听起来很混乱和令人沮丧。
一个丑陋的作品:
经过很长时间的尝试不同的策略,并且大部分都没有到达哪里,我想出了一个似乎可行的解决方案:
我的课程:(闻起来有点func-y,呃,时髦,def-initely。)
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?!
我如何使用它们:(按我的意愿工作)
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)
行为:(如预期的那样执行上述所有代码的输出)
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)
为什么我不喜欢它:
将配置数据存储在主对象__init__()
内部的类定义中并不令人感觉优雅。 尤其是在像这样定义之后立即实例化它们。 啊。 我可以处理父类,肯定,但在构造函数中做...
在主Configuration
对象之外存储相同的类也不会感觉优雅,因为内部类中的属性可能取决于Configuration
(或其内部的同级)的属性。
我可以处理所有事情之外的功能定义,所以里面有类似的东西
@property
def solar_zenith_angle(self):
return calculate_zenith(self.solar_azimuth_angle)
但我无法弄清楚如何做类似的事情
@property
def solar.zenith_angle(self):
return calculate_zenith(self.solar.azimuth_angle)
(当我试图很聪明的时候,我总是碰到<property object at 0xXXXXX>
)
那么对此有什么正确的方法呢? 我错过了一些基本的东西,或者采取了非常错误的方法? 有谁知道一个聪明的解决方案?
帮帮我! 我的Python代码并不漂亮! 我一定做错了什么!
那么,这是一个丑陋的方式,至少要确保你的属性被调用:
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)
当然,在这一点上,你可能完全放弃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
像这样使用它:
>>> c2.a
10
>>> c2.group.d
50
>>> c2.a = 6
>>> c2.group.d
30
最终编辑(?):如果您不想在子组属性定义中使用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
group_property接收基本配置对象而不是组对象,因此路径始终从根开始。 因此, e
等同于先前定义的d
。
顺便说一句,支持嵌套组是留给读者的一个练习。
菲尔,
你对func-y配置的犹豫对我来说很熟悉:)
我建议你将你的配置不是作为python文件存储,而是作为结构化数据文件存储。 我个人比较喜欢YAML,因为它看起来很干净,就像你刚开始设计时一样。 当然,您需要为自动计算的属性提供公式,但除非您输入的代码太多,否则不会太糟糕。 这是我使用PyYAML lib的实现。
配置文件(config.yml):
day_of_year: 138
time_of_day: 36000 # seconds
solar:
azimuth_angle: 73 # degrees
zenith_angle: !property 90 - self.azimuth_angle
代码:
import yaml
yaml.add_constructor("tag:yaml.org,2002:map", lambda loader, node:
type("Config", (object,), loader.construct_mapping(node))())
yaml.add_constructor("!property", lambda loader, node:
property(eval("lambda self: " + loader.construct_scalar(node))))
config = yaml.load(open("config.yml"))
print "LOADED config.yml"
print "config.day_of_year:", config.day_of_year
print "config.time_of_day:", config.time_of_day
print "config.solar.azimuth_angle:", config.solar.azimuth_angle
print "config.solar.zenith_angle:", config.solar.zenith_angle, "(calculated)"
print
config.solar.azimuth_angle = 65
print "CHANGED config.solar.azimuth_angle = 65"
print "config.solar.zenith_angle:", config.solar.zenith_angle, "(calculated)"
输出:
LOADED config.yml
config.day_of_year: 138
config.time_of_day: 36000
config.solar.azimuth_angle: 73
config.solar.zenith_angle: 17 (calculated)
CHANGED config.solar.azimuth_angle = 65
config.solar.zenith_angle: 25 (calculated)
配置可以是任何深度,属性可以使用任何子组值。 试试这个例子:
a: 1
b:
c: 3
d: some text
e: true
f:
g: 7.01
x: !property self.a + self.b.c + self.b.f.g
假设你已经加载了这个配置:
>>> config
<__main__.Config object at 0xbd0d50>
>>> config.a
1
>>> config.b
<__main__.Config object at 0xbd3bd0>
>>> config.b.c
3
>>> config.b.d
'some text'
>>> config.b.e
True
>>> config.b.f
<__main__.Config object at 0xbd3c90>
>>> config.b.f.g
7.01
>>> config.x
11.01
>>> config.b.f.g = 1000
>>> config.x
1004
UPDATE
让我们有一个属性config.bx,在其公式中使用self,parent和subgroup属性:
a: 1
b:
x: !property self.parent.a + self.c + self.d.e
c: 3
d:
e: 5
然后,我们只需要在子组中添加对父项的引用:
import yaml
def construct_config(loader, node):
attrs = loader.construct_mapping(node)
config = type("Config", (object,), attrs)()
for k, v in attrs.iteritems():
if v.__class__.__name__ == "Config":
setattr(v, "parent", config)
return config
yaml.add_constructor("tag:yaml.org,2002:map", construct_config)
yaml.add_constructor("!property", lambda loader, node:
property(eval("lambda self: " + loader.construct_scalar(node))))
config = yaml.load(open("config.yml"))
让我们看看它是如何工作的:
>>> config.a
1
>>> config.b.c
3
>>> config.b.d.e
5
>>> config.b.parent == config
True
>>> config.b.d.parent == config.b
True
>>> config.b.x
9
>>> config.a = 1000
>>> config.b.x
1008
哇,我刚刚阅读了一篇关于r / python上的描述符的文章,但我不认为黑客描述符会给你想要的。
我知道的处理这种子配置的唯一东西就是平坦的。 无论如何,这是Flatland的工作原理。
但你可以这样做:
class Configuration(Form):
day_of_year = Integer
time_of_day = Integer
class solar(Form):
azimuth_angle = Integer
solar_angle = Integer
然后加载字典
config = Configuration({
day_of_year: 138,
time_of_day: 36000, #seconds
solar: {
azimuth_angle: 73, #degrees
zenith_angle: 17, #degrees
...
},
...
})
我喜欢平地,但我不确定你使用它会获得多少收益。
您可以将元类或装饰器添加到您的类定义中。
就像是
def instantiate(klass):
return klass()
class Configuration(object):
@instantiate
class solar(object):
@property
def azimuth_angle(self):
return self.azimuth_angle
这可能会更好。 然后在Configuration上创建一个漂亮的__init__
,它可以加载字典中的所有数据。 我不知道也许别人有更好的主意。
这里有一些更完整的东西(没有LaC的答案那么神奇,但稍微不通用)。
def instantiate(clazz): return clazz()
#dummy functions for testing
calc_zenith_angle = calc_azimuth_angle = lambda(x): 3
class Solar(object):
def __init__(self):
if getattr(self,'azimuth_angle',None) is None and getattr(self,'zenith_angle',None) is None:
return AttributeError("must have either azimuth_angle or zenith_angle provided")
if getattr(self,'zenith_angle',None) is None:
self.zenith_angle = calc_zenith_angle(self.azimuth_angle)
elif getattr(self,'azimuth_angle',None) is None:
self.azimuth_angle = calc_azimuth_angle(self.zenith_angle)
class Configuration(object):
day_of_year = 138
time_of_day = 3600
@instantiate
class solar(Solar):
azimuth_angle = 73
#zenith_angle = 17 #not defined
#if you don't want auto-calculation to be done automagically
class ConfigurationNoAuto(object):
day_of_year = 138
time_of_day = 3600
@instantiate
class solar(Solar):
azimuth_angle = 73
@property
def zenith_angle(self):
return calc_zenith_angle(self.azimuth_angle)
config = Configuration()
config_no_auto = ConfigurationNoAuto()
>>> config.day_of_year
138
>>> config_no_auto.day_of_year
138
>>> config_no_auto.solar.azimuth_angle
73
>>> config_no_auto.solar.zenith_angle
3
>>> config.solar.zenith_angle
3
>>> config.solar.azimuth_angle
7
链接地址: http://www.djcxy.com/p/59831.html