os.symlink
Argument order is like ln: the existing target first, the new name second. A symlink stores a path (it may dangle); a hard link is a second name for the same file data. All three are available on Unix and Windows - but creating symlinks on Windows needs Developer Mode or an administrator, which is why the examples here use hard links and never create a symlink.
Common call
os.symlink('releases/v2', 'current')
Returns
None (readlink → the target string)
Replaces
subprocess.run(["ln", "-s", target, name])
Watch out
src is the TARGET, dst is the new link name
os.symlink(srcsrc — symlink/link: the existing target the link points to. For symlink it is stored as written and need not exist.type: str | bytes | PathLike · required, dstdst — symlink/link: the name of the link to create; must not exist yet.type: str | bytes | PathLike · required, target_is_directorytarget_is_directory — symlink, Windows only: create a directory symlink when the target does not exist (yet). Ignored elsewhere.type: bool · default: False=False, *, dir_fddir_fd — Resolve relative paths against this open directory (Unix). os.link has src_dir_fd and dst_dir_fd instead.type: int · default: None=None) / os.link(src, dst) / os.readlink(path)
→ None | str | bytes
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| src | str | bytes | PathLike | yes | symlink/link: the existing target the link points to. For symlink it is stored as written and need not exist. |
| dst | str | bytes | PathLike | yes | symlink/link: the name of the link to create; must not exist yet. |
| target_is_directory | bool | no (False) | symlink, Windows only: create a directory symlink when the target does not exist (yet). Ignored elsewhere. |
| path | str | bytes | PathLike | yes | readlink: the symbolic link to read. |
| dir_fd | int | no (None) | Resolve relative paths against this open directory (Unix). os.link has src_dir_fd and dst_dir_fd instead. |
Return value
None | str | bytes — symlink and link return None; readlink returns the stored link target (bytes for a bytes path).
Common patterns
Atomically switch a "current" link (Unix)
Create the new link under a temporary name, then rename over the old one.
import os os.symlink('releases/v2', 'current.tmp') os.replace('current.tmp', 'current')
Resolve a relative link target
readlink returns the path as stored; a relative one is relative to the link folder.
import os target = os.readlink(link) if not os.path.isabs(target): target = os.path.join(os.path.dirname(link), target)
Symlink with a fallback on Windows
Without the privilege, symlink raises OSError; fall back to a copy.
import os import shutil try: os.symlink(src, dst) except OSError: shutil.copy2(src, dst)
Examples
1. A hard link is a second name
import os
with open('a.txt', 'w') as f:
f.write('hi')
os.link('a.txt', 'b.txt')
(os.stat('a.txt').st_nlink, os.path.samefile('a.txt', 'b.txt'))
Returns
(2, True)2. Both names share the data
import os
with open('a.txt', 'w') as f:
f.write('hi')
os.link('a.txt', 'b.txt')
with open('b.txt', 'a') as f:
f.write('!')
open('a.txt').read()
Returns
'hi!'3. Removing one name keeps the data
import os
with open('a.txt', 'w') as f:
f.write('hi')
os.link('a.txt', 'b.txt')
os.remove('a.txt')
(open('b.txt').read(), os.stat('b.txt').st_nlink)
Returns
('hi', 1)4. A hard link is not a symlink
import os
open('a.txt', 'w').close()
os.link('a.txt', 'b.txt')
os.path.islink('b.txt')
Returns
False5. readlink on a regular file
import os
open('a.txt', 'w').close()
try:
os.readlink('a.txt')
except OSError as e:
result = (type(e).__name__, e.errno)
result
Returns
('OSError', 22)6. The new name must not exist
import os
open('a.txt', 'w').close()
open('b.txt', 'w').close()
try:
os.link('a.txt', 'b.txt')
except OSError as e:
result = type(e).__name__
result
Returns
'FileExistsError'7. No hard links to directories
import os
os.mkdir('d')
try:
os.link('d', 'd2')
except OSError as e:
result = type(e).__name__
result
Returns
'PermissionError'Pitfalls
1. Swapping the arguments
The first argument is the existing file, the second the new name - the same order as ln and cp.
link(new, old)
import os open('old.txt', 'w').close() try: os.link('new.txt', 'old.txt') except OSError as e: result = type(e).__name__ result
'FileNotFoundError'
link(old, new)
import os open('old.txt', 'w').close() os.link('old.txt', 'new.txt') os.path.samefile('old.txt', 'new.txt')
True
2. Using a relative readlink result against the cwd
A relative target is relative to the folder that holds the link, not to the current directory. Shown with posixpath and a fixed link so it runs anywhere.
target as is
import posixpath link, target = '/srv/app/current', 'releases/v2' posixpath.normpath(target)
'releases/v2'
join with dirname
import posixpath link, target = '/srv/app/current', 'releases/v2' posixpath.normpath(posixpath.join(posixpath.dirname(link), target))
'/srv/app/releases/v2'
When to use
Use it
- Deploy layouts ("current" pointing at a release), shortcuts to config files (symlink)
- Space-free snapshots and backups of files that do not change in place (link)
- Inspecting where a link points without following it (readlink)
Reach for something else
- Fully resolving chains of links → os.path.realpath / Path.resolve
- Portable code that must run unprivileged on Windows → copy instead of symlink
- Linking directories with link → hard links to directories are not allowed
Notes
CPython impl
Wrappers over the symlink, link and readlink system calls on Unix and the Win32 link APIs on Windows (Modules/posixmodule.c)
Availability
symlink, link, readlink: Unix, Windows. symlink is limited on WASI
Windows symlinks
Need Developer Mode (3.8+ uses unprivileged creation) or the SeCreateSymbolicLinkPrivilege / administrator rights; otherwise OSError. A Windows symlink is either a file or a directory link: matched to the target when it exists, else chosen by target_is_directory
Windows readlink
Since 3.8 also reads directory junctions and returns the substitution path, which typically starts with \\?\
Hard links
Work on NTFS and on Linux filesystems (verified on both); st_nlink counts the names. Hard links to a directory raise PermissionError on both
FAQ
Creating symbolic links on Windows needs administrator rights or Developer Mode, and link behaviour depends on the file system, so a live result would depend on the machine. The examples only show outcomes that are the same on Linux and Windows.