15

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?

Tim Wilder
  • 1,607
  • 1
  • 18
  • 26

5 Answers5

16

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))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That's exactly what I was looking for. As a follow up, what if any corner cases does that approach suffer from? – Tim Wilder Sep 12 '14 at 19:05
  • @TimWilder: None that I can think of right now; if you find any, let me know and I'll see how we could tackle those. – Martijn Pieters Sep 12 '14 at 19:11
  • I'm thinking of issues with symlinks; I'd need to think about the interaction between the op.join approach and paths/do some reading to explore that one more fully. The general concern is along the lines of os.path.abspath vs. os.path.realpath. – Tim Wilder Sep 12 '14 at 19:15
  • @TimWilder: `realpath` uses `abspath` under the hood, so feel free to replace the call. – Martijn Pieters Sep 12 '14 at 19:17
  • Good to know. I feel like this is an improvement on clarity and conciseness from the dirname calls, so thanks. – Tim Wilder Sep 12 '14 at 19:22
  • Wouldn't using os.pardir be more portable? Something like op.abspath(op.join(__file__, os.pardir, os.pardir, os.pardir)) – Mark E. Hamilton Apr 12 '16 at 17:47
6

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'
Ben Zenker
  • 153
  • 2
  • 7
6

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))
BareNakedCoder
  • 3,257
  • 2
  • 13
  • 16
4
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'
cs01
  • 5,287
  • 1
  • 29
  • 28
0

I will extend the accepted answer with N

import os.path as op

op.abspath(op.join(__file__, \*N\*[op.pardir]))
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140