5
\$\begingroup\$

For the purposes of unit-testing in Python, I've written a very simple function for generating variable-length lists populated with random objects:

constructors = [
    object,
    dict,
    lambda: bool(randint(0, 1)),
    lambda: randint(1, 1000),
    lambda: ''.join(choice(digits) for _ in range(randint(1, 10)))
]

choices = (
    lambda _: choice(constructors)(),
    lambda off: fake_list(off),
    lambda off: tuple(fake_list(off))
)

def fake_list(off = None):
    '''Creates a list populated with random objects.'''
    off = (off or 0) + 1
    return [choice(choices)(off) for _ in range(max(randint(1, 10) - off, 0))]

List generated with this function typically look something like this:

[(269, '6'), [{}, 990, 347, <object object at 0x7f51b230b130>, {}, [{}, '91921063', 302, '0047953', {}, ()], True], '70262', True]

Do you see any problems with it? Is there a more efficient/elegant way of doing this that you could suggest?

Quality of the generated primitives (strings, integers) is not of priority (i.e. string.digits should be enough).

\$\endgroup\$
  • \$\begingroup\$ off reduces readability - it is a bit hard to follow what (None or 0) + 1 might be. it is not documented in the docstring either. what is the intent of off? \$\endgroup\$ – Evgeny Jun 13 '18 at 13:03
  • \$\begingroup\$ off serves as an "annealing factor". That is, because the function may call itself, I need to ensure that I'm not calling forever. off is subtracted from randomly generated length of the list, so that it's shorter on each call. \$\endgroup\$ – Siegmeyer Jun 13 '18 at 13:15
  • 3
    \$\begingroup\$ that is quite smart, but would a name recursion_depth suit better? off is very generic to understand its meaning easily \$\endgroup\$ – Evgeny Jun 13 '18 at 13:38
  • 1
    \$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Jun 13 '18 at 16:00
4
\$\begingroup\$

Requirements may vary so I find it hard to use with the "hardcoded" constructors. Instead I would define a function that accept such constructors as an argument.

I would also define the function returning the list-comprehension as an inner function so the off parameter is hidden from the public interface (the end-user does not need to know such implementation details). Besides, off is a terrible name for such variable when all it does it to track the recursion depth, better call it depth, then.

An other thing that could be worth taking as parameter (even though a sensible default would make sense most of the time) is the maximum length of the inner lists (and thus the maximum allowed recursion level):

import random
from string import digits


def fake_list(items, max_size=10):
    """Create a list populated with random objects constructed from `items`"""

    choices = (
            lambda _: random.choice(items)(),
            lambda depth: random_list(depth),
            lambda depth: tuple(random_list(depth)),
    )

    def random_list(depth):
        return [
                random.choice(choices)(depth + 1)
                for _ in range(max(random.randint(1, max_size) - depth, 0))
        ]

    if max_size < 1:
        raise ValueError('minimal size of output should be at least 1')

    return random_list(0)


def test():
    constructors = [
        object,
        dict,
        lambda: bool(random.randint(0, 1)),
        lambda: random.randint(1, 1000),
        lambda: ''.join(
            random.choice(digits)
            for _ in range(random.randint(1, 10))),
    ]

    print(fake_list(constructors))


if __name__ == '__main__':
    test()

That way I can call:

fake_list([lambda: float('-inf'), lambda: range(random.randint(-100, -10), random.randint(10, 100)), set])

if I need to.

\$\endgroup\$
  • \$\begingroup\$ nice refactoring! \$\endgroup\$ – Evgeny Jun 13 '18 at 14:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.