47

Is there a shortcut for python pathlib.Path objects to write_text() in append mode?

The standard open() function has mode="a" to open a file for writing and appending to the file if that file exists, and a Paths .open() function seems to have the same functionality (my_path.open("a")).

But what about the handy .write_text('..') shortcut, is there a way to use pathlib to open and append to a file with just doing the same things as with open()?

For clarity, I can do

with my_path.open('a') as fp:
    fp.write('my text')

but is there another way?

my_path.write_text('my text', mode='a')

danodonovan
  • 19,636
  • 10
  • 70
  • 78

7 Answers7

29

Not really, as you can see in the pathlib module exist 2 types of path classes:

  • pure path classes {PurePath, PurePosixPath, PureWindowsPath}
  • concrete path classes {Path, PosixPath, WindowsPath}.

Parameters of theses classes constructors will be just *pathsegments.

And if you look at the available read/write methods (read_text/read_bytes and write_text/write_bytes) you'll also see mode won't be available neither

So, as you've already discovered, the only way you can use mode with these pathlib classes is by using open method, ie:

with my_path.open("a") as f:
    f.write("...")

This is by design and that way the pathlib classes have become really "clean". Also, the above snippet is already canonical so it can't be simplified any further. You could use open method outside the context manager though:

f = my_path.open("a")
f.write("...")
BPL
  • 9,632
  • 9
  • 59
  • 117
  • 11
    I do not understand this statement: _This is by design and that way the pathlib classes have become really "clean"._ --- How does this make the classes _clean_? They already contain the `write_*` methods which are just not flexible enough. If the methods (or classes) are not _dirty_ how would e.g. adding an optional parameter `append=False` to them make them _dirty_? – pabouk - Ukraine stay strong Mar 19 '21 at 13:16
  • 3
    @pabouk It's about protecting beginners... See discussions in https://bugs.python.org/issue46554 – Keelung Feb 06 '22 at 03:06
6

There is no "append mode" for write_text, nor is there a corresponding append_text method. If you need it, you can write a function for it yourself pretty easily:

def append_text(path, text, encoding=None, errors=None):
   with path.open("a", encoding=encoding, errors=errors) as f:
       f.write(text)

You might be wondering why such a thing isn't built directly into pathlib as a method. One reason is precisely because it is so easy to implement yourself that it's not really worth adding, but clearly that's not the whole story, because read_text and write_text would be similarly easy to implement yourself.

I think a major reason why pathlib.Path objects don't (and, indeed, shouldn't) have a append_text method is because it creates a hole for inexperienced users to fall into, which is a huge sin in API design.

Specifically, the hole I'm referring to is using append_text on the same file repeatedly in a loop. Because you're continually opening and closing the file, it is slow. Plus, doing so many unnecessary writes is probably not great for the health of your hard drive.

Worse, because the program will actually behave correctly (e.g. the file will have the contents they intended), they may not even notice anything is wrong, because they don't necessarily have a mental gauge on how long writing to a file "should" take.

This is exactly the sort of code a naïve programmer would write:

from pathlib import Path

N = 100

path = Path("numbers.txt")

path.write_text(f"{N}\n")
for i in range(N):
    path.append_text(f"{i}\n")
Kevin
  • 1,870
  • 2
  • 20
  • 22
4

The pathlib methods Path().write_text() and Path().write_bytes() close the file connection on exit.

from pathlib import Path

Path('file.txt').write_text('my text')
Path('file1.txt').write_bytes(b'my text')

Using append mode i.e open('a', ...) will instanciate a TextIOWrapper which is also closed by write_text / write_bytes on exit.

f = Path('file.txt')
f.open("a")
f.write_text('my text')
# or
f.write_bytes(b'my text')

Otherwise must close it manually

f = Path('file1.txt').open('a')
f.write('my text')
f.close()

but can be this way:

fp = Path('test.txt').open('a')
<_io.TextIOWrapper name='test.txt' mode='a' encoding='UTF-8'>
fp.write('my text')

fq = Path('test1.txt').open('ab', encoding='iso8859-1')
<_io.TextIOWrapper name='test1.txt' mode='a' encoding='iso8859-1'>
fq.write(b'my text')
DannyDannyDanny
  • 838
  • 9
  • 26
britodfbr
  • 1,747
  • 14
  • 16
  • 1
    I'm sorry, I've tried to read your first sentence slowly and carefully about five times, but I still can't make any sense of it. _.... the methods [...] close startment on finalize it_ ?!? All your base are belong to us ?!? – ssc Dec 15 '21 at 18:35
  • You can learn more about it at https://docs.python.org/3/library/pathlib.html – britodfbr Dec 16 '21 at 03:22
4
  • Is it a nice solution? No
  • Is it memory efficient? No
  • Is it a one-liner? Yes
from pathlib import Path

my_path = Path('/shopping_list.txt')

my_str = '\nFresh Spinach\nFrozen Strawberries'

my_path.write_text(my_path.read_text() + my_str)
DannyDannyDanny
  • 838
  • 9
  • 26
1

If its just too much trouble to use the WITH structure, this might provide a sort of work around:

from pathlib import Path as p
t1 = "The quick brown fox" 
t2 = "just jumped over the fence"
t3 = "to greet the lazy poodle."
mypath = p("D:\Try_this")
myfile = p("fox.txt")
if not(mypath.is_dir()):
    mypath.mkdir()
wholepath = p(mypath / myfile)
wholepath.write_text("\n".join([t1,t2,t3]))
BigDaddy
  • 41
  • 4
1

Create a function and bind to pathlib.Path

from pathlib import PosixPath
from pathlib import Path as p

def _append_text(self:PosixPath, target:str) -> None:
    with self.open('a') as f:
        f.write(target)

p.append_text = _append_text
Path = p

Create a new path object

sample_text = Path('/tmp/sample.txt')

Write mode

sample_text.write_text('hello world')

Append mode

sample_text.append_text('hello world, appended!')
sinestandly
  • 131
  • 2
  • 8
  • IMHO, "binding" new function to built-in class will be confuse to people. If we go this route, I think it is clearer to inheritance from `Path` to add new method. – bizi Jul 30 '23 at 04:43
1

It seems to me that "with/open" exposes just as big a "hole" for a naïve programmer as having an append qualifier added to pathlib.

N = 100
for i in range(N):
    with my_path.open('a') as fp
        fp.write_text(f"{N}\n")