diff --git a/CHANGELOG.md b/CHANGELOG.md index af5fb3185bc3729f17d384c60fb0d12697332563..59656364974eb8f7d90b553a18dc0f9f816dd881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ # v2.22.1 + +* added length \ No newline at end of file diff --git a/docs/coding/sequences.rst b/docs/coding/sequences.rst index b374ca6fb05adcfb334c0a254b28486d9167df61..777cd9ad5ac2fb75e715e31fc114174e33a76fb7 100644 --- a/docs/coding/sequences.rst +++ b/docs/coding/sequences.rst @@ -11,6 +11,11 @@ Rolling averages Standard routines ================= +length +------ + +.. autofunction:: satella.coding.length + IteratorListAdapter ------------------- diff --git a/satella/__init__.py b/satella/__init__.py index 15ce3dd0b43eccbfd64a839f84fc376faae078dd..dc7612af3e4095e0a534014b11383a174b4ac77f 100644 --- a/satella/__init__.py +++ b/satella/__init__.py @@ -1 +1 @@ -__version__ = '2.22.1a1' +__version__ = '2.22.1' diff --git a/satella/coding/__init__.py b/satella/coding/__init__.py index 612957d321d6b21dc52a7360b7a4219211612f49..72369906bb3573798f10f2a4e46f1d937e6bf79d 100644 --- a/satella/coding/__init__.py +++ b/satella/coding/__init__.py @@ -16,7 +16,7 @@ from .metaclasses import metaclass_maker, wrap_with, dont_wrap, wrap_property, D from .misc import update_if_not_none, update_key_if_none, update_attr_if_none, queue_iterator, \ update_key_if_not_none, source_to_function, update_key_if_true, \ get_arguments, call_with_arguments, chain_callables, Closeable, contains, \ - enum_value + enum_value, length from .environment import Context from .overloading import overload, class_or_instancemethod from .recast_exceptions import rethrow_as, silence_excs, catch_exception, log_exceptions, \ @@ -25,7 +25,7 @@ from .expect_exception import expect_exception from .deep_compare import assert_equal, InequalityReason, Inequal __all__ = [ - 'EmptyContextManager', 'Context', + 'EmptyContextManager', 'Context', 'length', 'assert_equal', 'InequalityReason', 'Inequal', 'Closeable', 'contains', 'enum_value', 'reraise_as', 'expect_exception', diff --git a/satella/coding/misc.py b/satella/coding/misc.py index 5bb0092d383a9939f925df4d608355bf6145757a..da3a42d7dfe342923a40ba1e2a7978416fba723f 100644 --- a/satella/coding/misc.py +++ b/satella/coding/misc.py @@ -22,6 +22,19 @@ def enum_value(value): return value +def length(lenable) -> int: + """ + Return length of an item. If it is a generator, exhaust it and return it's length. + """ + try: + return len(lenable) + except TypeError: + i = 0 + for _ in lenable: + i += 1 + return i + + def contains(needle, haystack) -> bool: """ A syntactic sugar for the following: diff --git a/tests/test_coding/test_misc.py b/tests/test_coding/test_misc.py index 5826fbca5d159578521b1eb9061a1d14f866c9bb..05c89d34c29e81389a954aff4bb0dd769e2607d5 100644 --- a/tests/test_coding/test_misc.py +++ b/tests/test_coding/test_misc.py @@ -5,7 +5,7 @@ import unittest from satella.coding import update_key_if_not_none, overload, class_or_instancemethod, \ update_key_if_true, get_arguments, call_with_arguments, chain_callables, Closeable, \ - contains, enum_value, for_argument + contains, enum_value, for_argument, length from satella.coding.structures import HashableMixin, ComparableEnum from satella.coding.transforms import jsonify, intify @@ -99,6 +99,11 @@ class TestCase(unittest.TestCase): self.assertRaises(ValueError, lambda: intify(object())) self.assertEqual(intify([1, 2, 3]), 3) + def test_length(self): + y = [1, 2, 3] + x = (z for z in y) + self.assertEqual(length(x, 3)) + def test_execute_with_locals(self): def fun(a, b, *args, c=None, **kwargs): if len(kwargs): diff --git a/tests/test_coding/test_predicates.py b/tests/test_coding/test_predicates.py index 9538a8a20396354b45c25602780b2b778355f8dc..4f3a7d05a68bb4c101b9fe7b0dca8ac02a737a0a 100644 --- a/tests/test_coding/test_predicates.py +++ b/tests/test_coding/test_predicates.py @@ -124,7 +124,7 @@ class TestPredicates(unittest.TestCase): self.assertFalse(p([2, 2])) def test_len(self): - p = x.length() == 2 + p = x.len_able() == 2 self.assertTrue(p([1, 2])) self.assertFalse(p([]))