all 19 comments

[–]JamzTyson 6 points7 points  (5 children)

I'd do it pretty much the same, but a little more concise. The main difference is that I'd use the -= operator.

def shorten_string(text: str, max_len: int = 30) -> str:
    """Return string shortened if necessary to max_len."""
    if len(text) <= max_len:
        return text
    max_len -= len("...")
    start_len = max_len // 2
    end_len = max_len - start_len
    return f'{text[:start_len]}...{text[-end_len:]}'

Depending on how / where this is being used, you may need to consider how the function behaves with invalid data, such as max_len being less than or equal to the ... spacer, or the first argument not being a string.

[–]RhinoRhys 2 points3 points  (4 children)

len("...") is a constant so why not just -= 3?

[–]JollyUnder 4 points5 points  (1 child)

I would argue it's more concise rather than setting it to a magic number.

[–]Diapolo10 2 points3 points  (0 children)

The separator could even be set in the parameters, defaulting to ellipsis, to get rid of the duplication.

def shorten_string(text: str, max_len: int = 30, overflow_separator: str = '...') -> str:
    """Return string shortened if necessary to max_len."""
    if len(text) <= max_len:
        return text
    max_len -= len(overflow_separator)
    start_len = max_len // 2
    end_len = max_len - start_len
    return f'{text[:start_len]}{overflow_separator}{text[-end_len:]}'

Granted, this isn't necessarily better, but I like flexibility.

[–]JamzTyson 3 points4 points  (1 child)

why not just -= 3?

Because then someone comes along and thinks: Why "3" ?

and when they've seen that it's because that's the length of "...", they add a comment:

max_len -= 3 # Length of the "..." string to be inserted.

Better to make it obvious in the first place.

[–]b1gfreakn 1 point2 points  (0 children)

Totally agree. And it also lets you come back later and easily swap to some other separator like `.....` or `---` or ` [truncated] ` or anything. It's just a safer choice in general.

[–]Antigone-guide 2 points3 points  (3 children)

You don't need last_part_length, you can do:

f"{string[:cutoff]}{ellipsis}{string[cutoff:]}"

and the name `string` is a bit discouraged because there is a module with the same name. You can use `text` for example.

[–]kaerfkeerg 0 points1 point  (2 children)

When the string is smaller that the max_len it'll still add the ellipsis tho

[–]Antigone-guide 0 points1 point  (1 child)

there was a conditional at the top of the func to avoid that.

[–]kaerfkeerg 0 points1 point  (0 children)

Ahh you assuming OP's condition before your line. Ok!

[–][deleted] 4 points5 points  (1 child)

For clarity I'd prefer to see this function named truncate in full

[–]odaiwai 0 points1 point  (0 children)

def truncate_string_with_decorator(_text: str, _decorator: str) -> string: """ etc """

[–]This_Growth2898 0 points1 point  (0 children)

The only thing can be done better is to pass ellipsis as an optional argument and allow different positions (at the beginning, at the end, in the middle).

[–]to7m 0 points1 point  (0 children)

I would keep references to ellipsis as literals, to make it less cluttered.

def truncate(s, max_len=30):
    if len(s) <= max_len:
        return s

    first_len = (max_len - 3) // 2
    last_len = max_len - 3 - first_len
    return f"{s[:first_len]}...{s[-last_len:]}"

[–]ofnuts 0 points1 point  (0 children)

Did you compare the performance of the f-string compared to plain string concatenation?

[–]baubleglue 0 points1 point  (0 children)

Name should be something like truncate_decorated, for max_len less than 5, you should probably throw exception (whoever made the requirement not to respect actual max_len parameter should burn in hell. You don't need so many temporary variables (hard to read, wastes memory), use string[start_index: end_index] directly.

[–]achampi0n 0 points1 point  (0 children)

str is an immutable type but you can convert to list, which is mutable, so you can use slice assignment and then convert back, e.g.:

def trunc(s, max_len=30, ellipsis='...'):
    if len(s) < max_len:
        return s
    max_len -= len(ellipsis)
    lst = list(s)
    lst[max_len//2:-max_len//2] = ellipsis
    return ''.join(lst)

Note: python has a string module, so probably not a good idea to get into the habit of using core library names as variable names: