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?
-
\$\begingroup\$ Why not use "..." (U+2026, horizontal ellipsis) instead of "..."? \$\endgroup\$Konrad Rudolph– Konrad Rudolph2017年07月14日 14:35:23 +00:00Commented Jul 14, 2017 at 14:35
3 Answers 3
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)
-
\$\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\$Loki Astari– Loki Astari2017年07月14日 16:00:03 +00:00Commented Jul 14, 2017 at 16:00
-
\$\begingroup\$ @LokiAstari Oh my bad. Thank you for telling me, I'll look into fixing that now, :) \$\endgroup\$2017年07月14日 16:04:46 +00:00Commented Jul 14, 2017 at 16:04
Rather than hardcoding a /
it would be more os agnostic to use os.path.sep
for the separator.
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.