I'm looking for a pythonic way to move up n directories from a given directory.
Let's say we have the example path /data/python_env/lib/python3.6/site-packages/matplotlib/mpl-data. If we were to move up n=2 directories we should end up at /data/python_env/lib/python3.6/site-packages.
The following works to move up n directories:
up_n = lambda path, n: '/'.join(path.split('/')[:-n])
However, it's not very readable and fails for paths on windows machines. In essence, it doesn't feel a very pythonic solution.
Is there a better, more pythonic solution, maybe using the os module?
You can use the pathlib module of the standard library:
from pathlib import Path
path = Path('/data/python_env/lib/python3.6/site-packages/matplotlib/mpl-data')
levels_up = 2
print(path.parents[levels_up-1])
# /data/python_env/lib/python3.6/site-packages
how about iterating with os.path.dirname:
import os
s = "/data/python_env/lib/python3.6/site-packages/matplotlib/mpl-data"
for _ in range(2):
s = os.path.dirname(s)
print(s)
prints:
/data/python_env/lib/python3.6/site-packages
You can use python os.path module for this:
>>> p = '/data/python_env/lib/python3.6/site-packages/matplotlib/mpl-data'
>>> os.path.normpath(os.path.join(p, "..", ".."))
'/data/python_env/lib/python3.6/site-packages'
Or generically for n,
os.path.normpath(os.path.join(*([p]+[".."]*n)))
This way, you don't care it is Windows \ or UNIX / to be used. Python should handle this well. And, you got a one-liner.
One solution to this could take advantage of the shorthand that .. is the parent directory. So, using pathlib you could write
from pathlib import Path
up_n = lambda orig, n: Path(orig).joinpath('/'.join(['..']*n))
For Python 3.4+, pathlib is probably the best choice.
That said, here's something that will also work in older versions (including Python 2.x):
import os
def up_n(path, n):
components = os.path.normpath(path).split(os.sep)
return os.sep.join(components[:-n])
if __name__ == '__main__':
path = '/data/python_env/lib/python3.6/site-packages/matplotlib/mpl-data'
result = up_n(path, 2)
print(result) # -> \data\python_env\lib\python3.6\site-packages
Related
I need to get the location of the home directory of the current logged-on user. Currently, I've been using the following on Linux:
os.getenv("HOME")
However, this does not work on Windows. What is the correct cross-platform way to do this ?
You want to use os.path.expanduser.
This will ensure it works on all platforms:
from os.path import expanduser
home = expanduser("~")
If you're on Python 3.5+ you can use pathlib.Path.home():
from pathlib import Path
home = str(Path.home())
I found that pathlib module also supports this.
from pathlib import Path
>>> Path.home()
WindowsPath('C:/Users/XXX')
I know this is an old thread, but I recently needed this for a large scale project (Python 3.8). It had to work on any mainstream OS, so therefore I went with the solution #Max wrote in the comments.
Code:
import os
print(os.path.expanduser("~"))
Output Windows:
PS C:\Python> & C:/Python38/python.exe c:/Python/test.py
C:\Users\mXXXXX
Output Linux (Ubuntu):
rxxx#xx:/mnt/c/Python$ python3 test.py
/home/rxxx
I also tested it on Python 2.7.17 and that works too.
This can be done using pathlib, which is part of the standard library, and treats paths as objects with methods, instead of strings.
Path.expanduser()
Path.home()
from pathlib import Path
home: str = str(Path('~').expanduser())
This doesn't really qualify for the question (it being tagged as cross-platform), but perhaps this could be useful for someone.
How to get the home directory for effective user (Linux specific).
Let's imagine that you are writing an installer script or some other solution that requires you to perform certain actions under certain local users. You would most likely accomplish this in your installer script by changing the effective user, but os.path.expanduser("~") will still return /root.
The argument needs to have the desired user name:
os.path.expanduser(f"~{USERNAME}/")
Note that the above works fine without changing EUID, but if the scenario previously described would apply, the example below shows how this could be used:
import os
import pwd
import grp
class Identity():
def __init__(self, user: str, group: str = None):
self.uid = pwd.getpwnam(user).pw_uid
if not group:
self.gid = pwd.getpwnam(user).pw_gid
else:
self.gid = grp.getgrnam(group).gr_gid
def __enter__(self):
self.original_uid = os.getuid()
self.original_gid = os.getgid()
os.setegid(self.uid)
os.seteuid(self.gid)
def __exit__(self, type, value, traceback):
os.seteuid(self.original_uid)
os.setegid(self.original_gid)
if __name__ == '__main__':
with Identity("hedy", "lamarr"):
homedir = os.path.expanduser(f"~{pwd.getpwuid(os.geteuid())[0]}/")
with open(os.path.join(homedir, "install.log"), "w") as file:
file.write("Your home directory contents have been altered")
Is there a Pathlib equivalent of os.access()?
Without Pathlib the code would look like this:
import os
os.access('my_folder', os.R_OK) # check if script has read access to folder
However, in my code I'm dealing with Pathlib paths, so I would need to do this (this is just an example):
# Python 3.5+
from pathlib import Path
import os
# get path ~/home/github if on Linux
my_folder_pathlib = Path.home() / "github"
os.access(str(my_folder_pathlib), os.R_OK)
The casting to str() is kinda ugly.
I was wondering if there is a pure Pathlib solution for what I'm trying to achieve?
p.s. I'm aware of the principle "easier to ask for forgiveness", however this is part of a bigger framework, and I need to know as soon as possible if the script has the right permissions to a NAS stored folder.
From Python 3.6, os.access() accepts path-like objects, therefore no str() needed anymore:
https://docs.python.org/3/library/os.html#os.access
Although this is still not a pure Pathlib solution.
Use the stat() method on a Path object, then lookup the st_mode attribute.
Path().stat().st_mode
Ok...I dont know where module x is, but I know that I need to get the path to the directory two levels up.
So, is there a more elegant way to do:
import os
two_up = os.path.dirname(os.path.dirname(__file__))
Solutions for both Python 2 and 3 are welcome!
You can use pathlib. Unfortunately this is only available in the stdlib for Python 3.4. If you have an older version you'll have to install a copy from PyPI here. This should be easy to do using pip.
from pathlib import Path
p = Path(__file__).parents[1]
print(p)
# /absolute/path/to/two/levels/up
This uses the parents sequence which provides access to the parent directories and chooses the 2nd one up.
Note that p in this case will be some form of Path object, with their own methods. If you need the paths as string then you can call str on them.
Very easy:
Here is what you want:
import os.path as path
two_up = path.abspath(path.join(__file__ ,"../.."))
I was going to add this just to be silly, but also because it shows newcomers the potential usefulness of aliasing functions and/or imports.
Having written it, I think this code is more readable (i.e. lower time to grasp intention) than the other answers to date, and readability is (usually) king.
from os.path import dirname as up
two_up = up(up(__file__))
Note: you only want to do this kind of thing if your module is very small, or contextually cohesive.
The best solution (for python >= 3.4) when executing from any directory is:
from pathlib import Path
two_up = Path(__file__).resolve().parents[1]
For getting the directory 2 levels up:
import os.path as path
curr_dir=Path(os.path.dirname(os.path.abspath(__file__)))
two_dir_up_=os.fspath(Path(curr_dir.parent.parent).resolve())
I have done the following to go up two and drill down on other dir
default_config_dir=os.fspath(Path(curr_dir.parent.parent,
'data/config').resolve())
Personally, I find that using the os module is the easiest method as outlined below. If you are only going up one level, replace ('../..') with ('..').
import os
os.chdir('../..')
--Check:
os.getcwd()
More cross-platform implementation will be:
import pathlib
two_up = (pathlib.Path(__file__) / ".." / "..").resolve()
Using parent is not supported on Windows. Also need to add .resolve(), to:
Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under Windows)
(pathlib.Path('../../') ).resolve()
I have found that the following works well in 2.7.x
import os
two_up = os.path.normpath(os.path.join(__file__,'../'))
You can use this as a generic solution:
import os
def getParentDir(path, level=1):
return os.path.normpath( os.path.join(path, *([".."] * level)) )
Assuming you want to access folder named xzy two folders up your python file. This works for me and platform independent.
".././xyz"
100% working answer:
os.path.abspath(os.path.join(os.getcwd() ,"../.."))
There is already an accepted answer, but for two levels up I think a chaining approach is arguably more readable:
pathlib.Path(__file__).parent.parent.resolve()
Surprisingly it seems no one has yet explored this nice one-liner option:
import os
two_up = os.path.normpath(__file__).rsplit(os.sep, maxsplit=2)[0]
rsplit is interesting since the maxsplit parameter directly represents how many parent folders to move up and it always returns a result in just one pass through the path.
With Pathlib (recommended after Python 3.5, the/a general solution that works not only in file.py files, but also in Jupyter (or other kind of) notebook and Python shell is:
p = Path.cwd().resolve().parents[1]
You only need to substitute (__file__) for cwd() (current working directory).
Indeed it would even work just with:
p = Path().resolve().parents[1]
(and of course with .parent.parent instead of parents[1])
I don't yet see a viable answer for 2.7 which doesn't require installing additional dependencies and also starts from the file's directory. It's not nice as a single-line solution, but there's nothing wrong with using the standard utilities.
import os
grandparent_dir = os.path.abspath( # Convert into absolute path string
os.path.join( # Current file's grandparent directory
os.path.join( # Current file's parent directory
os.path.dirname( # Current file's directory
os.path.abspath(__file__) # Current file path
),
os.pardir
),
os.pardir
)
)
print grandparent_dir
And to prove it works, here I start out in ~/Documents/notes just so that I show the current directory doesn't influence outcome. I put the file grandpa.py with that script in a folder called "scripts". It crawls up to the Documents dir and then to the user dir on a Mac.
(testing)AlanSE-OSX:notes AlanSE$ echo ~/Documents/scripts/grandpa.py
/Users/alancoding/Documents/scripts/grandpa.py
(testing)AlanSE-OSX:notes AlanSE$ python2.7 ~/Documents/scripts/grandpa.py
/Users/alancoding
This is the obvious extrapolation of the answer for the parent dir. Better to use a general solution than a less-good solution in fewer lines.
I find myself needing to get a parent directory of a python file in a source tree that is multiple directories up with some regularity. Having to call dirname many times is clunky.
I looked around and was surprised to not find posts on this.
The general scenario is:
import os.path as op
third_degree_parent = op.dirname(op.dirname(op.dirname(op.realpath(__file__))))
Is there a more idiomatic way to do this that doesn't require nested dirname calls?
Normalize a relative path; os.pardir is the parent directory, repeat it as many times as needed. It is available via os.path.pardir too:
import os.path as op
op.abspath(op.join(__file__, op.pardir, op.pardir, op.pardir))
Since it has not been demonstrated yet, here is an answer using a recursive function.
Function
import os
def parent(path, level = 0):
parent_path = os.path.dirname(path)
if level == 0:
return parent_path
return parent(parent_path, level - 1)
Explaination
get the dirname of a path that is input
if the level is not 0, function calls itself, causing it to get the dirname of the dirname
process repeats recursively until level has reached 0
Example
>>> parent('/my/long/path/name/with/a/file.txt')
'/my/long/path/name/with/a'
>>> parent('/my/long/path/name/with/a/file.txt', 0)
'/my/long/path/name/with/a'
>>> parent('/my/long/path/name/with/a/file.txt', 4)
'/my/long'
Someone else added an answer in 2018, 4 years later, so why not add mine. The other answers are either long or become long if a larger number of parents are required. Let's say you need 7 parents. This is what I do
os.path.abspath(__file__ + 8 * '/..')
Note the extra (8=7+1) to remove 7 parents as well as the file name. No need for os.path.pardir as abspath understands /.. universally and will do the right thing. Also has the advantage that the number of parents can be dynamic, determined at run-time.
In comparison, the equivalent using the accepted answer (longer and less obvious):
import os.path as op
op.abspath(op.join(__file__, op.pardir, op.pardir, op.pardir, op.pardir, op.pardir, op.pardir, op.pardir))
def updir(d, n):
"""Given path d, go up n dirs from d and return that path"""
ret_val = d
for _ in range(n):
ret_val = os.path.dirname(ret_val)
return ret_val
Given a directory d = '/path/to/some/long/dir' you can use the above function to go up as many levels as you want. For example:
updir(d,0)
'/path/to/some/long/dir'
updir(d,1)
'/path/to/some/long'
updir(d,2)
'/path/to/some'
updir(d,3)
'/path/to'
updir(d,4)
'/path'
I will extend the accepted answer with N
import os.path as op
op.abspath(op.join(__file__, \*N\*[op.pardir]))
I would like to get just the folder path from the full path to a file.
For example T:\Data\DBDesign\DBDesign_93_v141b.mdb and I would like to get just T:\Data\DBDesign (excluding the \DBDesign_93_v141b.mdb).
I have tried something like this:
existGDBPath = r'T:\Data\DBDesign\DBDesign_93_v141b.mdb'
wkspFldr = str(existGDBPath.split('\\')[0:-1])
print wkspFldr
but it gave me a result like this:
['T:', 'Data', 'DBDesign']
which is not the result that I require (being T:\Data\DBDesign).
Any ideas on how I can get the path to my file?
You were almost there with your use of the split function. You just needed to join the strings, like follows.
>>> import os
>>> '\\'.join(existGDBPath.split('\\')[0:-1])
'T:\\Data\\DBDesign'
Although, I would recommend using the os.path.dirname function to do this, you just need to pass the string, and it'll do the work for you. Since, you seem to be on windows, consider using the abspath function too. An example:
>>> import os
>>> os.path.dirname(os.path.abspath(existGDBPath))
'T:\\Data\\DBDesign'
If you want both the file name and the directory path after being split, you can use the os.path.split function which returns a tuple, as follows.
>>> import os
>>> os.path.split(os.path.abspath(existGDBPath))
('T:\\Data\\DBDesign', 'DBDesign_93_v141b.mdb')
WITH PATHLIB MODULE (UPDATED ANSWER)
One should consider using pathlib for new development. It is in the stdlib for Python3.4, but available on PyPI for earlier versions. This library provides a more object-orented method to manipulate paths <opinion> and is much easier read and program with </opinion>.
>>> import pathlib
>>> existGDBPath = pathlib.Path(r'T:\Data\DBDesign\DBDesign_93_v141b.mdb')
>>> wkspFldr = existGDBPath.parent
>>> print wkspFldr
Path('T:\Data\DBDesign')
WITH OS MODULE
Use the os.path module:
>>> import os
>>> existGDBPath = r'T:\Data\DBDesign\DBDesign_93_v141b.mdb'
>>> wkspFldr = os.path.dirname(existGDBPath)
>>> print wkspFldr
'T:\Data\DBDesign'
You can go ahead and assume that if you need to do some sort of filename manipulation it's already been implemented in os.path. If not, you'll still probably need to use this module as the building block.
The built-in submodule os.path has a function for that very task.
import os
os.path.dirname('T:\Data\DBDesign\DBDesign_93_v141b.mdb')
Here is the code:
import os
existGDBPath = r'T:\Data\DBDesign\DBDesign_93_v141b.mdb'
wkspFldr = os.path.dirname(existGDBPath)
print wkspFldr # T:\Data\DBDesign
Here is my little utility helper for splitting paths int file, path tokens:
import os
# usage: file, path = splitPath(s)
def splitPath(s):
f = os.path.basename(s)
p = s[:-(len(f))-1]
return f, p
Anyone trying to do this in the ESRI GIS Table field calculator interface can do this with the Python parser:
PathToContainingFolder =
"\\".join(!FullFilePathWithFileName!.split("\\")[0:-1])
so that
\Users\me\Desktop\New folder\file.txt
becomes
\Users\me\Desktop\New folder
I use this to change the current working directory to a folder;
from os import chdir
from os.path import realpath
from os.path import dirname
chdir(realpath(dirname(argv[0])))
chdir changes the working directory. I doubt you'll need this.
realpath follows symlinks.
dirname returns just the path
argv is the command line used to execute the program