diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/python/func | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/python/func')
-rw-r--r-- | library/python/func/__init__.py | 170 | ||||
-rw-r--r-- | library/python/func/ut/test_func.py | 162 | ||||
-rw-r--r-- | library/python/func/ut/ya.make | 11 | ||||
-rw-r--r-- | library/python/func/ya.make | 11 |
4 files changed, 354 insertions, 0 deletions
diff --git a/library/python/func/__init__.py b/library/python/func/__init__.py new file mode 100644 index 0000000000..7424361635 --- /dev/null +++ b/library/python/func/__init__.py @@ -0,0 +1,170 @@ +import functools +import threading +import collections + + +def map0(func, value): + return func(value) if value is not None else value + + +def single(x): + if len(x) != 1: + raise Exception('Length of {} is not equal to 1'.format(x)) + return x[0] + + +class _Result(object): + pass + + +def lazy(func): + result = _Result() + + @functools.wraps(func) + def wrapper(*args): + try: + return result.result + except AttributeError: + result.result = func(*args) + + return result.result + + return wrapper + + +def lazy_property(fn): + attr_name = '_lazy_' + fn.__name__ + + @property + def _lazy_property(self): + if not hasattr(self, attr_name): + setattr(self, attr_name, fn(self)) + return getattr(self, attr_name) + + return _lazy_property + + +class classproperty(object): + def __init__(self, func): + self.func = func + + def __get__(self, _, owner): + return self.func(owner) + + +class lazy_classproperty(object): + def __init__(self, func): + self.func = func + + def __get__(self, _, owner): + attr_name = '_lazy_' + self.func.__name__ + + if not hasattr(owner, attr_name): + setattr(owner, attr_name, self.func(owner)) + return getattr(owner, attr_name) + + +def memoize(limit=0, thread_local=False): + assert limit >= 0 + + def decorator(func): + memory = {} + lock = threading.Lock() + + if limit: + keys = collections.deque() + + def get(args): + try: + return memory[args] + except KeyError: + with lock: + if args not in memory: + fargs = args[-1] + memory[args] = func(*fargs) + keys.append(args) + if len(keys) > limit: + del memory[keys.popleft()] + return memory[args] + + else: + + def get(args): + if args not in memory: + with lock: + if args not in memory: + fargs = args[-1] + memory[args] = func(*fargs) + return memory[args] + + if thread_local: + + @functools.wraps(func) + def wrapper(*args): + th = threading.current_thread() + return get((th.ident, th.name, args)) + + else: + + @functools.wraps(func) + def wrapper(*args): + return get(('', '', args)) + + return wrapper + + return decorator + + +# XXX: add test +def compose(*functions): + def compose2(f, g): + return lambda x: f(g(x)) + + return functools.reduce(compose2, functions, lambda x: x) + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +def stable_uniq(it): + seen = set() + res = [] + for e in it: + if e not in seen: + res.append(e) + seen.add(e) + return res + + +def first(it): + for d in it: + if d: + return d + + +def split(data, func): + l, r = [], [] + for e in data: + if func(e): + l.append(e) + else: + r.append(e) + return l, r + + +def flatten_dict(dd, separator='.', prefix=''): + return ( + { + prefix + separator + k if prefix else k: v + for kk, vv in dd.items() + for k, v in flatten_dict(vv, separator, kk).items() + } + if isinstance(dd, dict) + else {prefix: dd} + ) diff --git a/library/python/func/ut/test_func.py b/library/python/func/ut/test_func.py new file mode 100644 index 0000000000..3c4fad1a07 --- /dev/null +++ b/library/python/func/ut/test_func.py @@ -0,0 +1,162 @@ +import pytest +import threading + +import library.python.func as func + + +def test_map0(): + assert None is func.map0(lambda x: x + 1, None) + assert 3 == func.map0(lambda x: x + 1, 2) + assert None is func.map0(len, None) + assert 2 == func.map0(len, [1, 2]) + + +def test_single(): + assert 1 == func.single([1]) + with pytest.raises(Exception): + assert 1 == func.single([]) + with pytest.raises(Exception): + assert 1 == func.single([1, 2]) + + +def test_memoize(): + class Counter(object): + @staticmethod + def inc(): + Counter._qty = getattr(Counter, '_qty', 0) + 1 + return Counter._qty + + @func.memoize() + def t1(a): + return a, Counter.inc() + + @func.memoize() + def t2(a): + return a, Counter.inc() + + @func.memoize() + def t3(a): + return a, Counter.inc() + + @func.memoize() + def t4(a): + return a, Counter.inc() + + @func.memoize() + def t5(a, b, c): + return a + b + c, Counter.inc() + + @func.memoize() + def t6(): + return Counter.inc() + + @func.memoize(limit=2) + def t7(a, _b): + return a, Counter.inc() + + assert (1, 1) == t1(1) + assert (1, 1) == t1(1) + assert (2, 2) == t1(2) + assert (2, 2) == t1(2) + + assert (1, 3) == t2(1) + assert (1, 3) == t2(1) + assert (2, 4) == t2(2) + assert (2, 4) == t2(2) + + assert (1, 5) == t3(1) + assert (1, 5) == t3(1) + assert (2, 6) == t3(2) + assert (2, 6) == t3(2) + + assert (1, 7) == t4(1) + assert (1, 7) == t4(1) + assert (2, 8) == t4(2) + assert (2, 8) == t4(2) + + assert (6, 9) == t5(1, 2, 3) + assert (6, 9) == t5(1, 2, 3) + assert (7, 10) == t5(1, 2, 4) + assert (7, 10) == t5(1, 2, 4) + + assert 11 == t6() + assert 11 == t6() + + assert (1, 12) == t7(1, None) + assert (2, 13) == t7(2, None) + assert (1, 12) == t7(1, None) + assert (2, 13) == t7(2, None) + # removed result for (1, None) + assert (3, 14) == t7(3, None) + assert (1, 15) == t7(1, None) + + class ClassWithMemoizedMethod(object): + def __init__(self): + self.a = 0 + + @func.memoize(True) + def t(self, i): + self.a += i + return i + + obj = ClassWithMemoizedMethod() + assert 10 == obj.t(10) + assert 10 == obj.a + assert 10 == obj.t(10) + assert 10 == obj.a + + assert 20 == obj.t(20) + assert 30 == obj.a + assert 20 == obj.t(20) + assert 30 == obj.a + + +def test_first(): + assert func.first([0, [], (), None, False, {}, 0.0, '1', 0]) == '1' + assert func.first([]) is None + assert func.first([0]) is None + + +def test_split(): + assert func.split([1, 1], lambda x: x) == ([1, 1], []) + assert func.split([0, 0], lambda x: x) == ([], [0, 0]) + assert func.split([], lambda x: x) == ([], []) + assert func.split([1, 0, 1], lambda x: x) == ([1, 1], [0]) + + +def test_flatten_dict(): + assert func.flatten_dict({"a": 1, "b": 2}) == {"a": 1, "b": 2} + assert func.flatten_dict({"a": 1}) == {"a": 1} + assert func.flatten_dict({}) == {} + assert func.flatten_dict({"a": 1, "b": {"c": {"d": 2}}}) == {"a": 1, "b.c.d": 2} + assert func.flatten_dict({"a": 1, "b": {"c": {"d": 2}}}, separator="/") == {"a": 1, "b/c/d": 2} + + +def test_memoize_thread_local(): + class Counter(object): + def __init__(self, s): + self.val = s + + def inc(self): + self.val += 1 + return self.val + + @func.memoize(thread_local=True) + def get_counter(start): + return Counter(start) + + def th_inc(): + assert get_counter(0).inc() == 1 + assert get_counter(0).inc() == 2 + assert get_counter(10).inc() == 11 + assert get_counter(10).inc() == 12 + + th_inc() + + th = threading.Thread(target=th_inc) + th.start() + th.join() + + +if __name__ == '__main__': + pytest.main([__file__]) diff --git a/library/python/func/ut/ya.make b/library/python/func/ut/ya.make new file mode 100644 index 0000000000..5ec6c1225e --- /dev/null +++ b/library/python/func/ut/ya.make @@ -0,0 +1,11 @@ +OWNER(g:yatool) + +PY23_TEST() + +TEST_SRCS(test_func.py) + +PEERDIR( + library/python/func +) + +END() diff --git a/library/python/func/ya.make b/library/python/func/ya.make new file mode 100644 index 0000000000..9d414a976e --- /dev/null +++ b/library/python/func/ya.make @@ -0,0 +1,11 @@ +OWNER(g:yatool) + +PY23_LIBRARY() + +PY_SRCS(__init__.py) + +END() + +RECURSE_FOR_TESTS( + ut +) |