Skip to content

properly define what "canonical" means in os.path.realpath #134639

Open

Description

@calestyo

Documentation

The documentation says:

Return the canonical path of the specified filename, eliminating any symbolic
links encountered in the path (if they are supported by the operating
system). On Windows, this function will also resolve MS-DOS (also called 8.3)
style names such as ``C:\\PROGRA~1`` to ``C:\\Program Files``.

So it merely mentions symlink resolution, but not what else it means (if anything).

It does however mention that whatever it does is OS dependent:

This function emulates the operating system's procedure for making a path
canonical, which differs slightly between Windows and UNIX with respect
to how links and subsequent path components interact.

IMO, it should be clearly documented what it does or at least:

  • what it guarantees to the least (For example will the pathname be absolute? Will it be normalised?)
    and/or:
  • which OS function it’s identical to, like on POSIX where that would probably realpath(), and whether there are differences to that

Especially since IMO "canonical" pathname (though I think it has no formal definition, or is there anything in POSIX?) means rather just that there are no symlinks left, i.e. that the file is "reached" by its true (=canonical) name.

But that could still be a relative pathname, and perhaps even one that is not normalised.

Cheers,
Chris.

Linked PRs

Activity

  1. terryjreedy commented on May 24, 2025

    @terryjreedy
    Member

    abspath returns "a normalize absolutized path", implying that there can be more than one for a given file. I undestand realpath to return the canonical abspath, so that path-a and path-b are the same file iff realpath(path-a) == realpath(path-b).

  2. calestyo commented on May 24, 2025

    @calestyo
    ContributorAuthor

    Hmm. I think even abspath will always return the same result (for the same string), so the a is perhaps also a bit misleading as there are not more than one possible result.

    It does however not resolve symbolic links, so the result may simply point to another pathname (as documented).

    Your example is true, though IMO canonical does not mean whether or not the resulting paths are then relative (I would however say the word implies normalised).

    In your example, assuming a cwd of /home and a path-a of /tmp and a path-b of ../tmp, the equality would still be true if realpath would return ../tmp or /../tmp.

    From my testing, realpath always returns absolute and normalised pathnames (as well as any symlinks being resolved, which is however already documented).


    What I'm a bit unsure about how to deal with is, that it also normalises absolute pathnames with exactly two leading /:

    >>> os.path.realpath("//tmp",strict=True)
    '/tmp'
    

    Which in pure POSIX is IMO wrong, as // has a special meaning (though Linux - and probably all others? - handles it like normal paths).

    Some Python library parts do in fact account for this, e.g.

    >>> import pathlib
    >>> pathlib.Path("//tmp").parts
    ('//', 'tmp')
    
    >>> pathlib.Path("/tmp").parts
    ('/', 'tmp')
    
    >>> pathlib.Path("///tmp").parts
    ('/', 'tmp')
    

    So I'm tempted to say, that os.path.realpath("//tmp",strict=True) should yield '//tmp'.

    Unless anyone strongly disagrees, I'd open a separate issue for that particular aspect.

  3. added a commit that references this issue on May 26, 2025
  4. picnixz commented on Aug 25, 2025

    @picnixz
    Member

    I undestand realpath to return the canonical abspath, so that path-a and path-b are the same file iff realpath(path-a) == realpath(path-b).

    Yes, or more geneally, if you take two paths, say $p_a$ and $p_b$ that point to file system objects $f_a$ and $f_b$ respectively, then their canonical form $c_a$ and $c_b$ respectively compare equal if and only if $f_a$ and $f_b$ are the same.

  5. picnixz commented on Aug 25, 2025

    @picnixz
    Member

    What I'm a bit unsure about how to deal with is, that it also normalises absolute pathnames with exactly two leading /:

    For that, please open a separate issue indeed. Changing it is a breaking change so we really need to be careful.

  6. picnixz commented on Aug 25, 2025

    @picnixz
    Member

    FTR, realpath is meant to emulate the realpath from coreutils. Note that canonicalization has a manpage: https://man7.org/linux/man-pages/man3/canonicalize_file_name.3.html which says:

    In the returned string, symbolic links are resolved, as are . and .. pathname components.
    Consecutive slash (/) characters are replaced by a single slash.

    In particular:

    #define _GNU_SOURCE
    #include <stdlib.h>
    #include <stdio.h>
    
    int main()
    {
        char *path = canonicalize_file_name("//tmp");
        printf("%s\n", path);
        free(path);
    
        return 0;
    }

    prints /tmp and not //tmp.

  7. calestyo commented on Aug 25, 2025

    @calestyo
    ContributorAuthor

    For that, please open a separate issue indeed.

    Done so in #138139. :-)

    FTR, realpath is meant to emulate the realpath from coreutils.

    Even then it should be documented what exactly that (i.e. emulate) means... like is it really meant to be the exact same behaviour or just "similar" in 99,9% cases.

    As for your argument (I think you mean realpath() from libc? not realpath from coreutils (and meanwhile also POSIX)), one should then probably check whether that behaviour of glibc is actually even intended or also merely "ignorant" of the standard.
    And, as I've laid out in #138139, even if glibc does it on purpose, there might be still other platforms that don't (and which os.path.realpath should then handle like that platform... but as I've also written there, even then it would introduce some nasty incompatibility with other Python stdlib code (e.g pathlib.Path)... and what if someone compares e.g. the results of the two?

  8. picnixz commented on Aug 25, 2025

    @picnixz
    Member

    I think you mean realpath() from libc? not realpath from coreutils (and meanwhile also POSIX)

    Both of them. They behave similarly (see https://manpages.debian.org/testing/coreutils/realpath.1.en.html). And both of them output /tmp when given //tmp.

  9. moved this from Todo to In Progress in OS issues 💻on Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsDocumentation in the Doc dir

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions