11
\$\begingroup\$

I need to display a path within a GUI application. Displaying the whole path would be an overkill, so I made a custom path shortener.

def cut_path(path):
 ''' This function takes a path and outputs the path cut with the ... after the 
 the first folder after the first character and completes the name with the filename or the last folder
 Example: 
 C:/Users/Mauro/Desktop/DenoiseAvg/DenoiseAverage/test_cut_path.py
 C:/Users/.../test_cut_path.py
 ''' 
 endidx = 5
 char = path[endidx] 
 while char != '/' and endidx < len(path):
 endidx += 1
 char = path[endidx]
 inpath = path[:endidx + 1 ]
 rpath = path[::-1] 
 idx = 1 
 char = rpath[idx] 
 while char != "/" and idx < len(rpath):
 idx += 1
 char = rpath[idx]
 endpath = path[len(path) - idx - 1 : ]
 outpath = inpath + "..." + endpath
 return outpath

Pastebin for syntax highlighting

Is there a shorter, more readable, pythonic way to do it? Is there a way to limit how many characters the path would be long? How would you do it?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jul 14, 2017 at 5:08
\$\endgroup\$
1
  • \$\begingroup\$ Why not use "..." (U+2026, horizontal ellipsis) instead of "..."? \$\endgroup\$ Commented Jul 14, 2017 at 14:35

3 Answers 3

18
\$\begingroup\$

Python is 'batteries included', this means offloading code to the core library is a good thing, and often possible. In this case, if you're running Python 3.4+ you could make use of pathlib. All you need is to know the pathlib.PurePath constructor and PurePath.parts.

And so you can change your code to:

import pathlib
def cut_path(path):
 parts = list(pathlib.PurePath(path).parts)
 if len(parts) >= 4:
 parts [2:-1] = ['...']
 return pathlib.PurePath(*parts)

However, as you're doing it on how long the path is, you could dynamically build the new path. And so could use something like:

def cut_path(path, max_size=5):
 if not path:
 return path
 parts = list(pathlib.PurePath(path).parts)
 path = pathlib.PurePath(parts[0])
 for part in parts[1:-1]:
 path /= part
 if len(str(path)) >= max_size:
 path /= '...'
 break
 if len(parts) > 1:
 path /= parts[-1]
 return path

If however you're using Python 2 or Python <3.4 then you can't use the nice new interface, and would have to use the old interface, os.path. Which doesn't support splitting and joining the paths very well. First you may want to use os.path.normcase to normalize the path. Which may destroy the path if you have a Posix path on Windows. After this you'd want to take the drive with os.path.splitdrive, and then split the rest of the parts using os.sep. Then you want to do the same as above, and replace the middle section of the path, and finally os.path.join them all together.

Which could leave you with something like:

def cut_path(path):
 path = os.path.normcase(path)
 drive, path = os.path.splitdrive(path)
 parts = path.split(os.sep)
 parts[0] = drive + parts[0]
 # Join plays funny, so you have to add a separator in these cases.
 if parts[0][-1] == ':':
 parts[0] += os.sep
 if len(parts) >= 4:
 parts [2:-1] = ['...']
 return os.path.join(*parts)
answered Jul 14, 2017 at 8:36
\$\endgroup\$
2
  • \$\begingroup\$ Though I prefer your solution. I would point out it generated a different result to the original code. Yours has a minimum of 4 segments. The original had a minimum of 4 characters. \$\endgroup\$ Commented Jul 14, 2017 at 16:00
  • \$\begingroup\$ @LokiAstari Oh my bad. Thank you for telling me, I'll look into fixing that now, :) \$\endgroup\$ Commented Jul 14, 2017 at 16:04
7
\$\begingroup\$

Rather than hardcoding a / it would be more os agnostic to use os.path.sep for the separator.

alecxe
17.5k8 gold badges52 silver badges93 bronze badges
answered Jul 14, 2017 at 16:38
\$\endgroup\$
3
\$\begingroup\$

No I don't think that is idiomatic python (though I am not the perfect person to answer the question).

I think it is more idiomatic to split the string (into an array). Then use the parts of the array to build the result.

There also seems to be a bug in your for short paths:

cut_path("c:/a/a.y") 

This is how I would do it

def cut_path2(path):
 segments = path.split('/')
 siz = 0
 pat = 0
 output = ""
 # Your code assumes the prefix must be at least 
 # 5 characters long I record this in siz
 while (siz < 5) and (pat < len(segments)):
 output += segments[pat]
 output += "/"
 siz += len(segments[pat]) + 1
 pat += 1
 # You don't want to shorten if the
 # the first loop prints the whole path.
 if pat < (len(segments)):
 output += ".../"
 if pat <= (len(segments)):
 output += segments[len(segments) - 1] 
 return output

It tested this with the unit test you provided but I can guarantee that it will work for all paths.

answered Jul 14, 2017 at 5:41
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.