同じセッションで繰り返し実行されるスクリプトを作成するのは、奇妙なことです。
あなたがそれをしたい理由はわかりますが、それでも奇妙です。コードが少し奇妙に見え、それを説明するコメントを付けて、その奇妙さを明らかにすることは不合理ではないと思います。
ただし、必要以上に醜いものを作成しました。
まず、これを行うことができます:
@functools32.lru_cache()
def _square(x):
print "Squaring", x
return x*x
try:
safe_square_2
except NameError:
safe_square_2 = _square
_square
新しい定義にキャッシュをアタッチしても害はありません。時間や数バイト以上のストレージを無駄にすることはありません。また、最も重要なことは、以前 _square
の定義のキャッシュに影響を与えないことです。それが閉鎖の要点です。
ここには、再帰関数に関する潜在的な問題があります。それはあなたの作業方法にすでに内在しており、キャッシュがそれに追加されることはありませんが、キャッシュがあるためにのみ気付くかもしれないので、それを説明し、修正する方法を示します. この関数を考えてみましょう:
@lru_cache()
def _fact(n):
if n < 2:
return 1
return _fact(n-1) * n
スクリプトを再実行すると、 old への参照があっても、グローバル名としてアクセスしているため_fact
、 new を呼び出すことになります。;とは何の関係もありません。それを削除すると、古い関数は引き続きnew を呼び出すことになります。_fact
_fact
@lru_cache
_fact
ただし、上記の名前変更のトリックを使用している場合は、名前を変更したバージョンを呼び出すことができます。
@lru_cache()
def _fact(n):
if n < 2:
return 1
return fact(n-1) * n
古い_fact
は を呼び出しますがfact
、これはまだ古い_fact
です。繰り返しますが、これはキャッシュ デコレータの有無にかかわらず同じように機能します。
その最初のトリックを超えて、そのパターン全体を単純なデコレーターに分解することができます。以下で順を追って説明するか、このブログ投稿を参照してください。
とにかく、見栄えの悪いバージョンでも、まだ少し見苦しく冗長です。そして、これを何十回も行っていると、私の「まあ、少し醜く見えるはずだ」という正当化はすぐに薄れてしまいます。したがって、これは、常に醜さを取り除くのと同じ方法で処理する必要があります。つまり、関数でラップします。
Python では、実際に名前をオブジェクトとして渡すことはできません。そして、これに対処するためだけに恐ろしいフレームハックを使用したくありません. そのため、名前を文字列として渡す必要があります。これを好き:
globals().setdefault('fact', _fact)
このglobals
関数は、現在のスコープのグローバル ディクショナリを返すだけです。dict
つまり、メソッドがあることを意味します。これは、値がまだない場合setdefault
はグローバル名fact
を値に設定することを意味し_fact
ますが、値がある場合は何もしません。これはまさにあなたが望んでいたものです。(現在のモジュールで使用することもできますがsetattr
、この方法は、スクリプトがモジュールとして使用されるのではなく、他の誰かのスコープで(繰り返し)実行されることを意図していることを強調していると思います。)
したがって、ここでは関数にまとめられています。
def new_bind(name, value):
globals().setdefault(name, value)
…これをほとんど簡単にデコレータに変えることができます:
def new_bind(name):
def wrap(func):
globals().setdefault(name, func)
return func
return wrap
次のように使用できます。
@new_bind('foo')
def _foo():
print(1)
しかし、待ってください。取得する には がありますfunc
よね?「プライベート」名は接頭辞付きの「パブリック」名でなければならないなど、命名規則に固執する場合、これを行うことができます。new_bind
__name__
_
def new_bind(func):
assert func.__name__[0] == '_'
globals().setdefault(func.__name__[1:], func)
return func
そして、これがどこに向かっているのかを見ることができます:
@new_bind
@lru_cache()
def _square(x):
print "Squaring", x
return x*x
小さな問題が 1 つあります。関数を適切にラップしない他のデコレータを使用すると、命名規則が破られます。だから… それをしないでください。:)
そして、これはすべてのエッジケースであなたが望むように正確に機能すると思います. 特に、ソースを編集して、新しいキャッシュで新しい定義を強制したい場合はdel square
、ファイルを再実行する直前に、それが機能します。
もちろん、これら 2 つのデコレーターを 1 つにマージしたい場合は、それを .xml と呼ぶのは簡単non_resetting_lru_cache
です。
しかし、私はそれらを別々に保ちます。彼らが何をしているのかは、より明白だと思います。また、別のデコレータを にラップしたい場合@lru_cache
でも、おそらく@new_bind
最も外側のデコレータになりたいと思うでしょう。
new_bind
インポートできるモジュールに入れたい場合はどうしますか? 現在書いているモジュールではなく、そのモジュールのグローバルを参照するため、機能しません。
globals
dict、モジュールオブジェクト、またはモジュール名を引数として明示的に渡すことで修正でき@new_bind(__name__)
ます。しかし、それは醜く、繰り返します。
醜いフレームハックで修正することもできます. 少なくとも CPython ではsys._getframe()
、呼び出し元のフレームを取得するために使用できframe objects
、グローバル名前空間への参照を持つことができます。
def new_bind(func):
assert func.__name__[0] == '_'
g = sys._getframe(1).f_globals
g.setdefault(func.__name__[1:], func)
return func
これが CPython にのみ適用される可能性がある「実装の詳細」であり、「内部および特殊な目的のみ」であることを示すドキュメントの大きなボックスに注意してください。これを真剣に考えてください。誰かが純粋な Python で実装できる標準ライブラリまたはビルトインのクールなアイデアを持っているときはいつでも_getframe
、それは通常、純粋な Python ではまったく実装できないアイデアとほぼ同じように扱われます。しかし、あなたが何をしているのかを知っていて、これを使いたいと思っていて、現在のバージョンの CPython だけを気にしているなら、それはうまくいくでしょう。