Created
September 3, 2014 07:37
-
-
Save yassu/ecd6153fdf2d5c63e92c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from random import randint | |
from inspect import isfunction | |
def get_random(): | |
return randint(0, 10**10) | |
class Cacher: | |
pass | |
DEFAULT_CACHER = Cacher() | |
def memoize(func, cacher=DEFAULT_CACHER): | |
""" | |
cacher: instance that has no __slot__ | |
""" | |
def _cache(*args, **kw): | |
cached_varname = '__cache{}'.format(id(func.__call__)) | |
cached_varname += '__' | |
## several-times computation | |
if hasattr(cacher, cached_varname) is True: | |
return getattr(cacher, cached_varname) | |
## first computation | |
result = func(*args, **kw) | |
setattr(cacher, cached_varname, result) | |
return result | |
return _cache | |
def cached_property(func, cacher=DEFAULT_CACHER): | |
@property | |
@memoize | |
def _cache_property(*args, **kw): | |
return func(*args, **kw) | |
return _cache_property | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sys import path | |
path.append('src') | |
from cache import memoize, cached_property | |
def only_func_test(): | |
global x | |
x = 1 | |
@memoize | |
def multiple(): | |
global x | |
x *= 2 | |
return x | |
for i in range(10): | |
res = multiple() | |
print('res: ' + str(res)) | |
assert(res == 2) | |
def class_simple_attr_test(): | |
class TwoTimes(): | |
def __init__(self): | |
self._value = 1 | |
@memoize | |
def multiple(self): | |
self._value *= 2 | |
return self._value | |
cls = TwoTimes() | |
for _ in range(10): | |
assert(cls.multiple() == 2) | |
def cached_property_simple_class_test(): | |
class TwoTimes(): | |
def __init__(self): | |
self._value = 1 | |
@cached_property | |
def multiple(self): | |
self._value *= 2 | |
return self._value | |
cls = TwoTimes() | |
for _ in range(10): | |
assert(cls.multiple == 2) | |
def cached_property_same_cacher_and_method_test(): | |
class TwoTimes(): | |
def __init__(self, value): | |
self._value = value | |
@cached_property | |
def multiple(self): | |
self._value *= 2 | |
return self._value | |
t1 = TwoTimes(1) | |
assert(t1.multiple == 2) | |
# multiple of default casher is equal to 2 | |
t2 = TwoTimes(5) | |
assert(t2.multiple == 10) | |
def cached_property_different_class_and_same_method_test(): | |
class TwoTimes(): | |
def __init__(self, value): | |
self._value = value | |
@cached_property | |
def multiple(self): | |
self._value *= 2 | |
return self._value | |
class ThreeTimes(): | |
def __init__(self, value): | |
self._value = value | |
@cached_property | |
def multiple(self): | |
self._value *= 3 | |
return self._value | |
t1 = TwoTimes(1) | |
assert(t1.multiple == 2) | |
# multiple of default casher is equal to 2 | |
t2 = ThreeTimes(5) | |
assert(t2.multiple == 15) | |
def cached_property_same_class_and_method_test(): | |
class TwoTimes(): | |
def __init__(self, value): | |
self._value = value | |
@cached_property | |
def multiple(self): | |
self._value *= 2 | |
return self._value | |
t1 = TwoTimes(1) | |
assert(t1.multiple == 2) | |
del(t1) | |
t2 = TwoTimes(5) | |
assert(t2.multiple == 10) |
デバッグしてみましたが、やはり _cache_property()
が @memoize
されているようなので、
@memoize
を使うことを諦めて作りなおしてみました。
https://gist.github.com/1bd5b7623f77115923ff
memoize() の変更点
- 引数が同じときだけキャッシュし、引数が異なる場合は計算するようにした (よく用いられる memoize 手法)
- キャッシュキーを関数名にした (ぶつかる可能性が大いにあるので、
id(func)
の方がよかったかも...) - キャッシュ先をシンプルな辞書に変更
functools.wraps()
を使って、関数の属性(doc や name など) を維持するようにした
cached_property() の変更点
- キャッシュ先を
self
の属性に変更 (インスタンスが消滅すると一緒に GC されるので省メモリ) functools.wraps()
を使って、関数の属性(doc や name など) を維持するようにした
ちなみに、作ってもらった memoize()
と cached_property()
には cacher
という引数が定義されていますが、
普通のデコレータの呼び方 (@memoize
) の場合はここを指定することが難しそうです。
引数を取るデコレータを作る場合は
def decorator(*dargs, **dkwargs):
def internal_decorator(func):
def wrapper(*args, **kwargs):
# some process
return wrapper
return internal_decorator
のように「デコレータを返す関数」を用意することになります(やたらとややこしいですね)。
参考まで。
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
テストコードを実行してみたところ、エラーが出ました。
python2.7 で実行しているのが問題かと気になり、python3.3 で実行してみたところ、
エラー件数は減るものの、依然としてエラーが出ます。
確認してみたところ、
cached_property()
で@memoize
を呼んでいると、memoize()
関数の引数は常に_cache_property()
が渡ってきているように見えます。そのため、
id(func.__call__)
は常に同じ値を返しているようです。こちらの環境だけで発生する問題でしょうか? 確認してみてください。