Separating data from string representation of objects, with added extras Given a string representation of data, I want to extract the information into its corresponding object.
However,
If the string has &quot;|&quot; separators then these should be considered options and need to be picked at random.
If the string data has numbers shown as a range &quot;1-10&quot; then a random value should be chosen between the range. It should also preserve the numerical datatype i.e int or float
I.e
&quot;(1-3,1,1)&quot; returns either (1, 1, 1), (2, 1, 1) or (3, 1, 1)
&quot;(0.2-0.4,1,1)&quot; returns either (0.2, 1, 1), (0.3, 1, 1) or (0.4, 1, 1)
&quot;foo|bar|foobar&quot; returns either &quot;foo&quot;, &quot;bar&quot; or &quot;foobar&quot;
&quot;[1-2,1,2]|foo|bar|[1,8-10,99]&quot; could return :
&quot;foo&quot;,&quot;bar&quot;, [1, 1, 2], [2, 1, 2], [1, 8, 99], [1, 9, 99] or [1, 10, 99]
This is what I have and it works well. But I cant help think it could be achieved in a more concise way. Let me know what I could have done better.
import re
import random
import ast

def randomize_by_pipe(st_value):
    &quot;&quot;&quot;
    Used to split strings with the pipe character and randomly choose and option.

    :param: st_value - (str)

    &quot;&quot;&quot;
    if not st_value is None:
        st_arr = st_value.split(&quot;|&quot;)
        random.shuffle(st_arr)
        return st_arr[0]
    else:
        return st_value

def randomise_range(text):
    if text is None:
        return text
    else:
        matches = re.findall(&quot;\d*\.*\d*-{1}\d*\.*\d*&quot;,text)
        
        for match in matches:
            startingPos = 0
            position = text.find(match, startingPos)
            while True:
                position = text.find(match, startingPos)
                if position &gt; -1:
                    txt = text[position:position+len(match)]
                    txt = rand_no_from_string(txt)

                    new_text = text[0:position+len(match)].replace(match,str(txt))
                    text = new_text + text[position+len(match):]
                else:
                    break

        try:
            return ast.literal_eval(text)
        except ValueError:
            return text

def rand_no_from_string(txt):
    
    is_int = False
    txt_arr = txt.split(&quot;-&quot;)
    num_arr = [float(x) for x in txt_arr]
    if int(num_arr[0]) == num_arr[0]:
        mul = 1
        is_int = True
    else:
        #new section to deal with the decimals
        mul = 10 ** len(str(num_arr[0]).split(&quot;.&quot;)[1])
        num_arr = [x*mul for x in num_arr]
    
    if num_arr[0] &gt; num_arr[1]:
        num_arr[1], num_arr[0] = num_arr[0], num_arr[1]
        
    val = random.randint(num_arr[0],num_arr[1])/mul
    return int(val) if is_int else val
    

Run with:
text=&quot;(108-100,0.25-0.75,100)|Foo|Bar|[123,234,234-250]&quot;
randomise_range(randomize_by_pipe(text))
Type hinting
Instead of having helpdocs declare the types of function parameters, why not go with type hinting?
Complexity
Your code currently has too many moving parts. You define 2 different functions to parse the data, and they both need to be called in chain. This should be done by a single parsing function.
Let the parser get data text, then the parser should be handling first parsing using pipe and later using the numerical ranges.
Selection from a list
Your randomize_by_pipe shuffles the list, and selects the 0th value. You can instead let random.choice do the job.
range parsing
I think range parsing can be improved a little. How about the following flow:

Remove [ and ] from the given text.
Split from ,.
For each section of the split, try parsing as float (or int, depending on your dataset)
In case of float conversion error, let the rand_no_from_string get a value.

regex
You have a regex, but you're not making full/elegant use of it. Instead of matches, you can group the results, and operate on those groups. The pattern itself can also be a little optimised:
\d+(?:\.\d+)?-\d+(?:\.\d+)?


A rewrite, for eg:
from re import sub, Match
from random import choice, randint


def randomise_range(match: Match):
    given_range = match.group(0).split(&quot;-&quot;)
    low, high = map(float, given_range)
    if low &gt; high:
        low, high = high, low
    if low.is_integer():
        return str(randint(int(low), int(high)))
    multiplier = 10 ** len(given_range[0].split(&quot;.&quot;)[-1])
    low = int(low * multiplier)
    high = int(high * multiplier)
    return str(randint(low, high) / multiplier)


def extract_range(text: str = None):
    if not text:
        return text
    return sub(r&quot;\d+(?:\.\d+)?-\d+(?:\.\d+)?&quot;, randomise_range, text)


def parse(text: str = None):
    if not text:
        return text
    selection = choice(text.split(&quot;|&quot;))
    if selection[0] in ('[', '('):
        return extract_range(selection)
    return selection


if __name__ == &quot;__main__&quot;:
    examples = (
        &quot;(1-3,1,1)&quot;,
        &quot;(0.2-0.4,1,1)&quot;,
        &quot;foo|bar|foobar&quot;,
        &quot;(108-100,0.25-0.75,100)|Foo|Bar|[123,234,234-250]&quot;,
        &quot;[1-2,1,2]|foo|bar|[1,8-10,99]&quot;,
    )
    for text in examples:
        print(parse(text))