3

Context特定の計算が実行されるコンテキストへのアクセス ポイントとして機能するインスタンスに大きく依存するアプリケーションがあります。

Contextインスタンスへのアクセスを提供したい場合は、次のことができます。

  1. に頼るglobal
  2. Contextを必要とするすべての関数にパラメーターとして渡す

むしろglobal変数を使用したくありません。インスタンスをすべての関数に渡すのContextは面倒で冗長です。

計算をどのように「非表示にしますが、アクセスできるようにしますContextか? 」

たとえば、Contextさまざまなデータに基づいて惑星の状態 (位置と速度) を単純に計算するとします。

class Context(object):
 def state(self, planet, epoch):
  """base class --- suppose `state` is meant
     to return a tuple of vectors."""
  raise NotImplementedError("provide an implementation!")

class DE405Context(Context):
"""Concrete context using DE405 planetary ephemeris"""
 def state(self, planet, epoch):
   """suppose that de405 reader exists and can provide
      the required (position, velocity) tuple."""
   return de405reader(planet, epoch)

def angular_momentum(planet, epoch, context):
 """suppose we care about the angular momentum of the planet,
    and that `cross` exists"""
 r, v = context.state(planet, epoch)
 return cross(r, v)

# a second alternative, a "Calculator" class that contains the context
class Calculator(object):

 def __init__(self, context):
  self._ctx = context

 def angular_momentum(self, planet, epoch):
  r, v = self._ctx.state(planet, epoch)
  return cross(r, v)

# use as follows:
my_context = DE405Context()
now = now() # assume this function returns an epoch
# first case:
print angular_momentum("Saturn", now, my_context)
# second case:
calculator = Calculator(my_context)
print calculator.angular_momentum("Saturn", now) 

もちろん、すべての操作を「コンテキスト」に直接追加することもできますが、それは適切ではありません。

実際には、Contextは惑星の位置を計算するだけではありません! より多くのことを計算し、多くのデータへのアクセス ポイントとして機能します。

では、私の質問をより簡潔にするために、多くのクラスからアクセスする必要があるオブジェクトをどのように処理しますか?

私は現在探索しています: python のコンテキスト マネージャーですが、あまり運がありません。また、プロパティ「コンテキスト」をすべての関数に直接動的に追加することも考えました(関数はオブジェクトであるため、任意のオブジェクトへのアクセスポイントを持つことができます)。

def angular_momentum(self, planet, epoch):
 r, v = angular_momentum.ctx.state(planet, epoch)
 return cross(r, v)

# somewhere before calling anything...
import angular_momentum
angular_momentum.ctx = my_context

編集

素晴らしいことは、次のようなwithステートメントで「計算コンテキスト」を作成することです。

 with my_context:
  h = angular_momentum("Earth", now)

もちろん、単純に次のように記述すれば、すでにそれを実行できます。

 with my_context as ctx:
  h = angular_momentum("Earth", now, ctx) # first implementation above

たぶん、戦略パターンでこれのバリエーションですか?

4

2 に答える 2

4

通常、Python では何も「隠し」たくありません。人間の読者に「プライベート」として扱うように伝えたい場合がありますが、これは実際には「このオブジェクトを無視しても、API を理解できるはずです」という意味であり、「これにアクセスできない」という意味ではありません。

Python でこれを行う慣用的な方法は、前にアンダースコアを付けることです。また、モジュールが で使用される可能性がある場合は、すべてのパブリック エクスポートをリストfrom foo import *する明示的なグローバルを追加します。__all__繰り返しますが、これらのどちらも、実際には誰もあなたの変数を見ることを妨げたり、import foo.

詳細については、グローバル変数名に関するPEP 8を参照してください。

一部のスタイル ガイドでは、特別なプレフィックス、すべて大文字の名前、またはその他の特別な識別マークをグローバルに提案していますが、PEP 8 では、__all__および/または先頭のアンダースコアを除いて、規則は同じであると具体的に述べています。

一方、必要な動作は明らかにグローバル変数の動作です。つまり、誰もが暗黙的に共有および参照する単一のオブジェクトです。lint合格すべきでないチェックやコードレビューに合格する可能性がある場合を除いて、それが何であるか以外のものとして偽装しようとしても、何の役にも立ちません。グローバル変数に関するすべての問題は、ディクショナリなどに直接存在することではなく、誰もが暗黙のうちに共有および参照する単一のオブジェクトであることから生じるglobals()ため、まともな偽のグローバルは本物のグローバルと同じくらい悪いものです。それが本当に必要な動作である場合は、グローバル変数にします。

それを一緒に入れて:

# do not include _context here
__all__ = ['Context', 'DE405Context', 'Calculator', …

_context = Context()

_global_contextまた、もちろん、_private_global_context単に_context.

contextただし、グローバルは依然としてモジュールのメンバーであり、ユニバース全体のメンバーではないことに注意してください。そのためfoo.context、クライアント コードがimport foo. そして、これはまさにあなたが望むものかもしれません。クライアントスクリプトがモジュールをインポートしてその動作を制御する方法が必要な場合は、おそらくそれfoo.context = foo.Context(…)が正しい方法です。もちろん、これはマルチスレッド (または gevent/coroutine/など) コードでは機能しません。また、他のさまざまなケースでは不適切ですが、それが問題にならない場合、場合によってはこれで問題ありません。

Contextコメントでマルチスレッドを取り上げたので、実行時間の長いジョブがあるマルチスレッドの単純なスタイルでは、グローバル スタイルは実際には完全に正常に機能しthreading.localますContext。小さなジョブをスレッド プールで処理するスタイルでも、それほど複雑ではありません。各ジョブにコンテキストをアタッチすると、ワーカーがジョブをキューから取り出すと、スレッド ローカル コンテキストがそのジョブのコンテキストに設定されます。

ただし、とにかくマルチスレッドがアプリに適しているかどうかはわかりません。Python では、タスクが IO のためにブロックする必要があり、他のタスクを停止せずにそれを実行できるようにしたい場合、Python でのマルチスレッド化が優れています。しかし、GIL のおかげで、CPU 作業の並列化にはほとんど役に立ちません。を探しています。マルチプロセッシング(multiprocessingモジュールを介しているかどうかにかかわらず)は、あなたが求めているものよりも多いかもしれません。また、個別のプロセスを使用すると、個別のコンテキストを維持することがさらに簡単になります。(または、スレッドベースのコードを記述してマルチプロセッシングに切り替え、threading.local変数をそのままにして、新しいタスクを生成する方法のみを変更しても、すべてが正常に機能します。)

decimal標準ライブラリのモジュールの外部バージョンが行ったように、コンテキストマネージャーの意味で「コンテキスト」を提供することは理にかなっているので、誰かが書くことができます:

with foo.Context(…):
    # do stuff under custom context
# back to default context

ただし、そのための適切なユースケースを実際に考えることができる人は誰もいなかったので (特に、少なくとも素朴な実装では、スレッド化などの問題を実際に解決しないため)、標準ライブラリに追加されませんでした。あなたもそれを必要としないかもしれません。

あなたがこれをしたいのなら、それはかなり簡単です。プライベート グローバルを使用している場合は、これをContextクラスに追加するだけです。

def __enter__(self):
    global _context
    self._stashedcontext = _context
    _context = self
def __exit__(self, *args):
    global context
    _context = self._stashedcontext

そして、これをパブリック、スレッドローカルなどの代替に調整する方法は明らかです。

もう 1 つの方法は、すべてをContextオブジェクトのメンバーにすることです。最上位のモジュール関数は、適切なデフォルト値を持つグローバル コンテキストに委譲するだけです。これはまさに標準ライブラリrandomモジュールの動作方法です。 を作成してそれrandom.Random()を呼び出すrandrangeことも、単にrandom.randrange()を呼び出して、グローバルな既定のrandom.Random()オブジェクトで同じものを呼び出すこともできます。

インポート時に を作成するのContextが重すぎて実行できない場合、特に使用されない可能性がある場合 (誰もグローバル関数を呼び出さない可能性があるため)、最初のアクセス時にシングルトン パターンを使用して作成できます。しかし、それが必要になることはめったにありません。そうでない場合、コードは簡単です。たとえば、ソースtorandomの 881 行目からは、次のようになります。

_inst = Random()
seed = _inst.seed
random = _inst.random
uniform = _inst.uniform
…

それだけです。

そして最後に、あなたが提案したように、すべてをCalculatorオブジェクトを所有する別のオブジェクトのメンバーにすることができContextます。これは従来の OOP ソリューションです。使いすぎると、Python が Java のように感じてしまう傾向がありますが、適切な場合に使用することは悪いことではありません。

于 2013-01-03T01:46:30.927 に答える