diff --git a/python_ls/_ls.py b/python_ls/_ls.py index d5fe516..c92d4c0 100644 --- a/python_ls/_ls.py +++ b/python_ls/_ls.py @@ -1,4 +1,5 @@ from collections import Container +from numbers import Number try: import pandas as pd @@ -21,16 +22,18 @@ def ls(obj, attr=None, depth=None, dunder=False, under=True): :param under: If True single underscore prefixed attributes are ignored, default is enabled :return: None """ - if depth is None: - depth = 1 - for attr, value in iter_ls(obj, attr=attr, depth=depth, dunder=dunder, under=under): size = '' if has_pandas and isinstance(value, pd.DataFrame): size = '{0}x{1}'.format(*value.shape) elif hasattr(value, '__len__'): - size = len(value) + try: + size = len(value) + except TypeError as exc: + # certain constructor object such as dict, list have a + # __len__ method but it throws a TypeError + pass type_name = type(value).__name__ print('{:<60}{:>20}{:>7}'.format(attr, type_name, size)) @@ -43,10 +46,16 @@ def xdir(obj, attr=None, depth=None, dunder=False, under=True): def iter_ls(obj, attr=None, depth=1, dunder=False, under=True, - visited=None, current_depth=1, path=''): + visited=None, numbers=None, current_depth=1, path=''): visited = visited or set() + numbers = numbers or set() if (depth is None) or (current_depth <= depth): + if isinstance(obj, Number): + if obj in numbers: + return + else: + numbers.add(obj) if id(obj) not in visited: visited.add(id(obj)) @@ -109,6 +118,6 @@ def attr_filter_callback(a): if val is not BAD and not a.startswith('__'): for sub_a, sub_val in iter_ls(val, attr=attr, depth=depth, dunder=dunder, - under=under, visited=visited, + under=under, visited=visited, numbers=numbers, current_depth=current_depth + 1, path=new_path): yield sub_a, sub_val diff --git a/tests/test_ls.py b/tests/test_ls.py index 1a71368..7fc31c8 100644 --- a/tests/test_ls.py +++ b/tests/test_ls.py @@ -1,4 +1,4 @@ -from python_ls import iter_ls +from python_ls import iter_ls, ls import pytest @@ -14,7 +14,8 @@ def test_obj(): o.foo.bar.something = Object() o.foo.bar.aaa = Object() o.foo.bar.bbb = Object() - o.foo.bar._something_else = dict # a callable (lambda recurses infinitely in Python 2.7 when depth=None) + o.foo.bar._something_else = lambda: None + o.foo.bar.someconstructor_obj = dict o.foo.baz = {'something_weird': 'going on', 'blah': 'bleh'} o.lala = Object() o.lala.lele = Object() @@ -47,7 +48,31 @@ def test_depth_is_None(test_obj): "foo.baz['something_weird']", 'lala.something', ] - actual = [x[0] for x in iter_ls(test_obj, 'something', depth=None)] assert actual == expected + +def test_iter_ls_constructor_obj(test_obj): + expected = ['foo.bar.someconstructor_obj()'] + actual = [x[0] for x in iter_ls(test_obj, 'someconstructor', depth=None)] + assert actual == expected + + +def test_basic_ls_usage(test_obj, capsys): + ls(test_obj, 'something') + out, err = capsys.readouterr() + expect = [ + ['foo.bar._something_else()', 'function'], + ['foo.bar.something', 'Object'], + ["foo.baz['something_weird']", 'str', '8'], + ['lala.something', 'Object'] + ] + assert expect == [line.split() for line in out.splitlines()] + + +def test_ls_constructor_obj(test_obj, capsys): + ls(test_obj, 'someconstructor') + out, err = capsys.readouterr() + expect = [['foo.bar.someconstructor_obj()', 'type']] + assert expect == [line.split() for line in out.splitlines()] +