7

コードに悪臭がします。少し空気を抜く必要があるのか​​もしれませんが、今は悩まされています。

出力を比較できるように、3 つの放射伝達モデリング (RTM) アプリケーションを実行するために 3 つの異なる入力ファイルを作成する必要があります。このプロセスは何千もの入力セットに対して繰り返されるため、Python スクリプトで自動化しています。

入力パラメーターを、他の 3 つの関数に渡すことができる汎用の 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内で s を使用することを考えましたdict:

config = {
    day_of_year: 138,
    time_of_day: 36000, #seconds
    solar: {
        azimuth_angle: 73, #degrees
        zenith_angle: 17, #degrees
        ...
    },
    ...
}

私はすきです。しかし、冗長なプロパティがたくさんあります。たとえば、太陽の方位角と天頂角は、もう一方がわかっている場合に見つけることができますが、なぜ両方をハードコーディングするのでしょうか? そこで、python の builtin を調べ始めまし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()

しかし、今では 2 番目のdict例の構造を失ってしまいました。

一部のプロパティは私の例よりも単純ではなく、solar_zenith_angleそれが属している属性のグループ外の他の属性へのアクセスが必要になる場合があることに注意してください。たとえばsolar_azimuth_angle、年、時刻、緯度、経度がわかれば計算できます。

私が探しているもの:

すべての値が統一された方法でアクセスでき、適切に構造化され、属性 (実際の値) またはプロパティ (他の属性から計算される) として存在する構成データを格納する簡単な方法。

ちょっと退屈な可能性:

前に概説した dicts の dicts にすべてを格納し、他の関数をオブジェクトに対して実行して、計算可能な値を計算しますか? これは面白くない。またはきれい。私には、面倒でイライラするように聞こえます。

動作する醜いもの:

長い間さまざまな戦略を試し、ほとんどどこにも行き着かなかった後、うまくいくと思われる1つの解決策を思いつきました。

私のクラス: (少しファンキー、えー、ファンキーなにおいがします。間違いなく。)

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コードは美しくありません!私は何か間違ったことをしているに違いない!

4

4 に答える 4

2

フィル、

func-y config に関するあなたの躊躇は、私にとって非常によく知られています :)

設定を 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

アップデート

式で自己、親、およびサブグループ属性の両方を使用するプロパティ config.bx があるとします。

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
于 2012-05-25T04:46:13.170 に答える
1

うわー、今日 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

そのほうがいいかもしれません。__init__次に、ディクショナリからすべてのデータをロードできるnice on Configurationを作成します。他の誰かがより良いアイデアを持っているかもしれません。

これはもう少し完全なものです(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
于 2012-05-25T00:02:59.267 に答える
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)

もちろん、この時点では、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です。

ところで、ネストされたグループのサポートは、読者の演習として残されています。

于 2012-05-25T00:19:07.697 に答える
0

dict をサブクラス化して、データが利用できない場合にデフォルトに戻るようにしたいと思います。このようなもの:

class fallbackdict(dict):
    ...

defaults = { 'pi': 3.14 }
x_config = fallbackdict(defaults)
x_config.update({
    'planck': 6.62606957e-34
})

もう 1 つの側面は、callable で対処できます。これが洗練されているか醜いかは、データ型宣言が有用かどうかによって異なります。

pi: (float, 3.14)

calc = lambda v: v[0](v[1])

x_config.update({
    'planck': (double, 6.62606957e-34),
    'calculated': (lambda x: 1.0 - calc(x_config['planck']), None)
})

状況によっては、何度も使用するとラムダが壊れる可能性があります。

より良いかどうかはわかりませんが、ほとんど辞書スタイルを保持しています。

于 2012-05-25T00:52:22.527 に答える