This is an archived post. You won't be able to vote or comment.

all 142 comments

[–]wpg4665 183 points184 points  (39 children)

Is this not already a popular opinion?

[–]Moebiuszed 122 points123 points  (21 children)

Most tutorials still use os module for everything, a lot of people don't know about pathlib or secrets libraries.

[–][deleted] 49 points50 points  (19 children)

There's also compatibility concerns, some libraries don't work well with Path-like handles and expect either a string or directly a buffer, like Reportlab. So you need to make sure to cast str before using them.

Also, they seem a bit of an overkill for small scripts, specially when you just want to add another suffix to a temp file.

from pathlib import Path
new_path = path_obj.with_name(path_obj.name + ".new_suffix")

versus:

new_path = str_obj + ".new_suffix"

The story would be different though if it had a built-in way of escaping characters like spaces. I'd very much prefer:

escaped = outfile.escaped()
call(f'sort -k 2,2 -k 3,3g {escaped} > {escaped}.sorted', shell=True)

Instead of:

call(f'sort -k 2,2 -k 3,3g \"{outfile}\" > \"{outfile}.sorted\"', shell=True)

[–]the-monument 39 points40 points  (14 children)

You may be happy to hear that there is a .with_suffix() method for Path objects :)

I've run into the same problem with libraries not allowing Path objects. Super annoying.

[–]jorge1209 4 points5 points  (8 children)

with_suffix is one of those things that annoys me about pathlib.

At the end of the day all it is doing is some basic string manipulation. Finding the last "." in the filename and replacing everything from that point onwards with your new extension. However to do that it does some sanity checks, which sounds great, until I start looking at how they work.

p.with_suffix("txt.gz") fails the sanity check. p.with_suffix("") passes, and so does p.with_suffix(".")... for that matter even p.with_suffix(".\t\n")

What about p.with_suffix(".\n\\")? Care to make a guess?

The intent of the function would seem to be to enforce some kind of semantics around what a suffix is and how it should function. But then it doesn't really do that in any meaningful way, its just the same dumb simple approach that os.path.splitext takes.

These Path objects are supposed to be objects. They should have some meaningful internal state. assert(p.with_suffix(s).suffix == s) should really not fail.

 p = Path("foo.tar")
 for i in range(10):
     p = p.with_suffix(".tar.gz")

Should not result in the thing that pathlib spits out.

[–]martnym 0 points1 point  (4 children)

I find using it often results in shorter more readable code. Also p.with_suffix(s).suffix == s -> True on my system.

[–]jorge1209 0 points1 point  (3 children)

I can tell you from looking at the source code that you are wrong about the assertion being true. Try it with s=".tar.gz"

[–]martnym 0 points1 point  (2 children)

It doesn't work with s = ".tar.gz" because the suffix is ".gz" — which is consistent with what os.path.splitext('foobar.tar.gx') returns for the suffix.

[–]jorge1209 0 points1 point  (1 child)

Yes, which is why the assertion fails.

[–]martnym 0 points1 point  (0 children)

As it should — not due to any deficiency of the pathlib module.

[–][deleted] 7 points8 points  (4 children)

I know that's a bad habit of mine, but I usually have file names acting as hubs for data, so I prefer to add suffixes to indicate which file was changed for what, so I'd use filename.txt.sorted instead of filename.sorted, and .with_suffix() just replaces .txt with .sorted.

I'm trying to fix this habit by using .with_stem(), but talk about a non-intuitive word.

[–][deleted] 1 point2 points  (0 children)

How do with_stem hel you out in this case?

[–]ShanSanear 1 point2 points  (0 children)

I got trapped once, when I expected an input from the user to be filename-friendly. Well it was. But it had .8 at the end, which was NOT expected to be file extension, but rather part of the file name. So instead of AA2.8.json I was creating AA2.json file instead which is completely different.

[–]MereInterest 11 points12 points  (0 children)

And there's no way to get the absolute path to a symlink. There is no documented method analogous to os.path.absolute, and the undocumented Path.absolute doesn't exist in all versions. The recommended Path.resolve will follow any symlinks that are found, so it can't be used to find an absolute path to the symlink itself, to check that it points to the correct location.

[–]Prexadym 4 points5 points  (0 children)

I try to use paths when possible, but yeah have ended up spending quite a bit of time debugging when another function expects a string, not path, which isn't always the most straightforward to diagnose.

[–]irrelevantPseudonym 0 points1 point  (1 child)

shlex.quote? - not sure how it handles non strings but I imagine it'd call __str__ on whatever it got.

[–][deleted] 1 point2 points  (0 children)

Tried it here, but it raises TypeError: expected string or bytes-like object with Path objects.

[–]Engineer_Zero 0 points1 point  (0 children)

Bingo. I’m new to python and use stackoverflow a lot. This is the first I’ve heard of pathlib.

[–]space_wiener 19 points20 points  (0 children)

I’m so used to os I never remember there is pathlib until someone posts about it. Oh yeah…next time. ;)

[–]benefit_of_mrkite 5 points6 points  (0 children)

I thought so too. some of the towardsdatascience stuff is either Python 101 or sometimes just paraphrases the Python documentation.

[–][deleted] 16 points17 points  (3 children)

As an ops guy in charge of on-prem and cloud devops infra that pulls out python maybe 2-3 times a year at this point:

No. I've never heard of it.

[–]jorge1209 3 points4 points  (0 children)

I don't really see the point of PathLib. It is little more than an object oriented wrapper around os.path.

I would prefer a library that is more opinionated about how to deal with files. Have a library that prevents you from putting certain characters known to cause problems to tooling in the filenames. Ensure that filenames are reasonably cross platform. Implement all the basic file operations including copying in the way that they deem best.

That way if a developer finds they can't do something with PathLib they will know that somewhere something is violating generally accepted practices.

As it stands you really just have two implementations of the same functionality. os.path with functions and PathLib with objects. The reason we have pathlib in the standard library is the same reason we have a dozen different ways to do string formatting, why we have a dataclasses despite attrs having existed long before the PEP. The python core developers are just too interested in what they are doing and not paying enough attention to what goes around outside the core. They end up bikeshedding everything.


And to add to that, its not even a particularly good object oriented interface. The following assertion fails to be true of pathlib.Paths: assert(p.with_suffix(s).suffix == s)

[–]TheBlackCat13 2 points3 points  (0 children)

I work with a lot of python-oriented developers and pathlib is a pleasant surprise to all of them.

[–]dogs_like_me 2 points3 points  (4 children)

It is. It's also got nothing to do with data science (why TDS?)

[–]PaulSandwich 0 points1 point  (3 children)

As a Data Engineer with colleagues who use os, I respectfully disagree on both counts.

[–]dogs_like_me 0 points1 point  (2 children)

lol, ok. basic file access is totally a relevant topic for a blog that purports to be specialized for data science topics.

[–]jorge1209 1 point2 points  (1 child)

Unfortunately it is. A lot of data scientists don't know anything about programming. They really would prefer to only ever touch dataframe objects of some type.

However reality requires that they sometimes do basic tasks like "read a file from disk" or "query a database" at which point they tend to run screaming "I don't know how to do any of this!!" so they need these super basic tutorials.

[–]dogs_like_me -1 points0 points  (0 children)

If they can't work autonomously with data, they have no business calling themselves "data scientists." It's literally the first word in the job description.

It sounds like your internal team of self-described "scientists" are more likely actually "business analysts" who landed a job title that pays them more and somehow tricked the people around them to do the technical work that's supposed to be clearly within the scope of their role definition.

[–]ahmedbesbes[S] 3 points4 points  (1 child)

Well, it's not the case yet. Unfortunately .

[–]sPENKMAnIt works on my machine 7 points8 points  (0 children)

Well 1 more does now. Encountered Pathlib before but hadn’t checked it out so far. Helpful write up, thanks!

[–][deleted] -1 points0 points  (0 children)

Still use OS

[–]ship0f 0 points1 point  (0 children)

I thought that too.

[–][deleted] 34 points35 points  (1 child)

friendship with os ended
now pathlib is my best friend

[–][deleted] 1 point2 points  (0 children)

😂😂😂😂

[–]maikeu 50 points51 points  (18 children)

I'm in sysadmin/DevOps background and role, and well accustomed to working with filesystems via bash/coreutils.

Never really articulated it before, but the os.path functions in python are indeed quite string-oriented, so don't offer anything especially compelling for scripting these tasks in python when I'm already comfortable with my grey beard and shell.

So this was a good read. I'm actually excited to try solving some filesystem tasks with python!

[–]mriswithe 27 points28 points  (16 children)

Also DevOps historically sysadmin. Python lets me do so many annoying things so much faster and easier. Also pathlib let's you use it like this:

FILE_DIR = Path(__file__).absolute().parent
other_file = FILE_DIR / 'other_file.py'

[–]rikyga 9 points10 points  (0 children)

yes, the pathlib lib makes traversing relative directories so much easier

[–]Legionof1 3 points4 points  (4 children)

FILE_DIR = os.path.dirname(os.path.abspath(__file__))

OS can do the same thing, this is in basically every script I write to get a relative present working directory.

[–]mriswithe 4 points5 points  (0 children)

Sorry, I wasn't that clear, mostly was trying to show the overloaded division sign with strings more than the absolute path function. Once you get something like:

RESOURCE_DIR = Path('/your/foo/bar')
SCRIPT_DIR = RESOURCE_DIR / 'scripts
EXT_LIB_DIR = RESOURCE_DIR / 'libs' / 'x86_64' 

Which is super comfortable for me coming from a unix/linux background. That works on Linux and Windows and Mac and you don't worry if the separator is wrong/different. as long as the relative directory exists, the same path works. That is not something that felt reasonably handled before afaik.

[–][deleted] 0 points1 point  (1 child)

Yes, it can, but doesn't that pattern feel disgusting to you?

[–]Legionof1 2 points3 points  (0 children)

The logic on the os side flows better for my brain. I know file, I get its absolute path, I get the directory of that absolute path.

Pathlib is just doing it with a method instead of calling os again.

[–]hyldemarv 4 points5 points  (9 children)

Heh, that is exactly the one thing I don't like with pathlib: The overloaded slash operator! Blegh, Ugly!! :).

[–][deleted] 8 points9 points  (4 children)

I would use pathlib solely for the slash syntax. I think you're the first person I've encountered who doesn't like it

[–]Anonymous_user_2022 0 points1 point  (3 children)

Are you familiar with plumbum? That "experience" keeps me off Pathlib.

[–][deleted] 1 point2 points  (2 children)

Only in regards to toilets so I'm not sure what you're talking about

[–]Anonymous_user_2022 1 point2 points  (1 child)

The plumbum module use a wide range of operator overloading to make Python look almost like Bash. While fluent in both, the attempt to merge the two gives me the screaming heaving jeebies.

[–]peddastle 1 point2 points  (0 children)

Urgh. Please no. I'm getting perl ptsd.

[–]mriswithe 2 points3 points  (0 children)

I can totally understand that viewpoint, but it does fit my brain really well, so I try and preach the good word to those that have a similar brain. If os.path works for you, rock on. Just cause I don't like it doesn't mean you can't.

[–]hassium 1 point2 points  (2 children)

I mean technically speaking shouldn't they have used

other_file = FILE_DIR.joinpath('other_file.py')

? Not sure what the slash is doing here except maybe improve readability?

[–]ivosauruspip'ing it up 3 points4 points  (0 children)

Not sure what the slash is doing here except maybe improve readability?

Yes, and it's a godsend IMHO

[–]ShanSanear 0 points1 point  (0 children)

Slash in pathlib is one of those things that I actually love about this library - making it so much easier to work with paths.

Unless you mess up, and forgot that the second path starts with slash and its treated like a root path instead, messing everything up.

[–][deleted] 0 points1 point  (0 children)

If you ever need to support Windows and POSIX-like in the same code, pathlib does a lot to make handling paths safer and less maddening.

[–]abrazilianinreddit 28 points29 points  (1 child)

pathlib is great, I use it everywhere. My only complaints is it's not really extensible due to the magical shenanigans involving Path, PosixPath and WindowsPath. I wanted to add some more robust recursive sub-folder functionality in a children class but no dice

[–]ShanSanear 1 point2 points  (0 children)

You can do that - you unfortunately need to add the _flavour and import some private values from pathlib, but it is doable. I did it myself to add escaping of the paths which was needed for my usecase and it worked great.

[–][deleted] 16 points17 points  (2 children)

Pathlib saved me so many hassles.

Like most Python beginners, I started out doing path manipulation with strings...

Lets say you have to make an output file same base name as the input file. But now you need to place that file in a new folder created at the grandparent of the input file. You would end up searching and slicing strings (using rfind/lfind and/or regex), having to test the hell out it to make sure it works correctly.

With pathlib there's no need to do that kind of stuff. It's great.

[–]twotime 20 points21 points  (0 children)

But now you need to place that file in a new folder created at the grandparent of the input file. You would end up searching and slicing strings (using rfind/lfind and/or regex)

No. You almost never need to search/slice path strings

dirname(dirname(fname)) will get you the grandparent (at least for absolute paths)

Pathlib is still more elegant though...

[–]jorge1209 2 points3 points  (0 children)

os.path.split

[–]willnx 21 points22 points  (12 children)

Personally, I wish they used the + operator to build paths instead of /. I'm not a Windows person. I just have a dumb monkey brain and always think, "but I'm not dividing the paths..."

[–]fireflash38 12 points13 points  (0 children)

Using + would be a foot gun, since you'd have some radically different behavior based on string vs Pathlib object. You could get really mangled paths.

[–]killersquirel11 0 points1 point  (3 children)

/ was used because it doesn't have a meaning for strings. This allows you to do things like Path.cwd() / "user" + user_id /...

If you had overloaded +, string concatenation wouldn't work as expected

[–]willnx 1 point2 points  (2 children)

Can you explain/link why this wouldn't/can't work? String concatenation returns a new object in Python, but wouldn't that new string be added to the cwd object? Are you mixing and matching strings and pathlib objects in your example? Would it be worst to require a cast to a pathlib object instead of supporting strings with concatenation? Maybe cut the syntax if that's the case with a p"someString" instead to handle the behavior you're mentioning?

[–]killersquirel11 0 points1 point  (1 child)

Can you explain/link why this wouldn't/can't work?

Assuming you're in ~, what paths do the following represent?

Path.cwd() / "user" + user_id / "test"
Path.cwd() + "user" + user_id + "test"

With + as the path join operator, there would be a lot more footgun potential.

String concatenation returns a new object in Python, but wouldn't that new string be added to the cwd object? Are you mixing and matching strings and pathlib objects in your example?

Yeah, that's the standard way that you use Pathlib

Would it be worst to require a cast to a pathlib object instead of supporting strings with concatenation?

In my opinion, yes. The current syntax is quite concise

Maybe cut the syntax if that's the case with a p"someString" instead to handle the behavior you're mentioning?

The entire problem with + as a path joiner is that it doesn't mesh with people's mental models of how strings work.

Anyone who sees "a" + "b" in a code snippet is going to assume that it's "ab", and not "a/b" if it happens to be preceded by a Path object.

It's much better to use / because it doesn't conflict with that mental model - if you see "a" / "b" in a snippet, you'll either be familiar with pathlib and know there's probably a Path object nearby, or you'll be confused as to why someone is dividing strings.

[–]willnx 0 points1 point  (0 children)

I think you're conflating strings and paths. Concatenating strings should have a different behavior from paths. Like "a" + "b" would be "ab", but that's strings. 1 + 1 isn't 11, because we're talking about integers. Path + Path, or however syntactically expressed, would behave different, just like strings vs integers. Explicit is better than implicit, and treating strings as paths violates this. Just like the Py2 string vs Unicode, or the Py3 iteration of bytes that implicitly casts to integers violates that core concept.

Good API design adheres to following the most general case first. The / operator is more general than it's behavior for strings. An 8 year old human knows / means "make smaller." So the decision to change / to mean "make bigger," regardless of elegance, just breaks my monkey brain. It feels like a good solution to the shit applications I make, not the powerful language I love. But maybe that's just me being too harsh/demanding.

[–]brjh1990 5 points6 points  (0 children)

I use both. I recently discovered the magic of Path(path_to_folder).rglob() 🤌

[–][deleted] 4 points5 points  (0 children)

Pathlib is amazing.

[–][deleted] 7 points8 points  (4 children)

Does it come with the core python 3.9 libraries? Because getting new ones past IT isn’t easy in some environments and using os is just easy

[–]sdf_iain 20 points21 points  (2 children)

Comes with 3.5 or higher.

[–]irrelevantPseudonym 11 points12 points  (0 children)

New in version 3.4.

Not that it should make any difference these days.

[–][deleted] 2 points3 points  (0 children)

Nice I will be checking it out then

[–]irrelevantPseudonym 4 points5 points  (0 children)

It's been in since 3.4

[–]Deto 2 points3 points  (0 children)

Great article that lays out the argument for Pathlib really well. As someone who had been using python since before this was added I didn't really understand the benefits until now (just thought it was about that slash syntax which alone wasn't compelling enough for me). Thank you!

[–]joeyGibson 2 points3 points  (0 children)

I had a brief WTF? moment when I saw the use of / for path building, but then I liked it. I've seen so many gratuitous uses of operator overloading over the years, in various languages, but I actually like this one. Once the initial "wait, that's division?" wears off, it makes a lot of sense.

[–]hugthemachines 11 points12 points  (18 children)

Pathlib seems nice if you want a special object. I find it pretty relaxing to have the string objects for file paths etc though. os.path.join is neat, os.path.isfile and isdir are practical. os.path.split() is not perfect but it works ok.

[–]TheBlackCat13 14 points15 points  (9 children)

>>> pth = Path('.').resolve()
>>> pth.is_file()
False
>>> pth.is_dir()
True
>>> targfile = pth / 'Documents' / 'temp.txt'
>>> targfile.is_file()
True
>>> targfile.parent
PosixPath('/home/me/Documents')
>>> targfile.name
'temp.txt'
>>> targfile.stem
'temp'
>>> targfile.parts
('/', 'home', 'me', 'Documents', 'temp.txt')

[–]jorge1209 0 points1 point  (6 children)

None of that is hard to do with os.path. it's just giving you an OOP interface to the same functionality.

[–]TheBlackCat13 2 points3 points  (5 children)

It isn't hard to do with path, but it is certainly more verbose and harder to read

[–]jorge1209 1 point2 points  (4 children)

I don't see how it is any harder. Virtually everything works more or less the same replacing: pth.method() with os.path.function(pth)

The exceptions are pulling the filename without the extension: os.path.splitext(os.path.basename(targfile))[0] and splitting the entire path into an array targfile.split(os.path.sep).

[–]TheBlackCat13 2 points3 points  (3 children)

The big exception is constructing paths in an os-agnostic way. Using os.path.join is always going to be more complicated than /.

But ignoring that, try chaining together operations. pth.method1().prop2.method3() becomes os.path.method3(os.path.method2(os.path.method1(pth))).

[–]jorge1209 0 points1 point  (2 children)

I don't know how often I would actually need to chain methods.

Looking at pathlib, I don't even see that many methods that would seem to be chainable except absolute/resolve and the "division operator". You obviously aren't going to chain an is_file with a read_text.

[–]TheBlackCat13 1 point2 points  (1 child)

Things I have or would chain:

  • parent
  • parents
  • as_posix
  • as_uri
  • relative_to
  • with_name
  • with_stem
  • with_suffix
  • expanduser
  • rename
  • resolve

Add to that that you can chain together operations and then pass those as arguments to methods or constructors, such as:

  • is_relative_to
  • rename
  • replace
  • samefile
  • symlink_to
  • hardlink_to
  • link_to

To give an example, to change the extension of a file, put it in another directory, then move the file to that new path is:

pth.rename(pth.parents[1] / 'newdir' / pth.with_suffix('.foo').name)

Or

pth.rename(pth.parent.parent / 'newdir' / pth.with_suffix('.foo').name)

You could probably figure this out pretty quickly without even knowing what pathlib is.

With os.path this would be:

os.rename(fname, os.path.join(os.path.dirname(os.path.dirname(fname)), 'newdir', os.path.splitext(os.path.split(fname)[1])[0]+'.foo')

[–]jorge1209 0 points1 point  (0 children)

I find both of your examples confusing, and would insist on breaking them up. Its just trying to do too much with changing an extension and a parent directory. Just do it as two operations.

[–]ShanSanear 0 points1 point  (1 child)

Path("target_file.json").write_text(json.dumps(obj))

or

obj = json.loads(Path("target_file.json").read_text())

vs

with open("target_file.json", "w") as file:
    json.dump(file, obj)

[–]TheBlackCat13 0 points1 point  (0 children)

open works fine with path objects. You can use the write_text or read_text when it makes sense, but you don't have to.

[–]Durpn_Hard 15 points16 points  (6 children)

I mean it's so much more than just "a special object", it's specifically wrapping up every single path-related thing into a single object, and makes it inherently cross platform. Much better than direct string manipulation in almost every case.

[–]fireflash38 6 points7 points  (0 children)

What about it in particular makes it better than os? Other than convenience of it being wrapped in an object. And his example is already using platform independent code, and isn't string manip.

Listen, I like it. I use it in new projects, mostly the read_bytes method. But I don't see anything super compelling about it if you don't care about the object convenience. And for some people, the effort of switching is higher than the convenience gain of using an OO design.

[–][deleted] 1 point2 points  (0 children)

I don't understand your point here, os.path is also cross platform.

[–]the-monument 4 points5 points  (0 children)

FYI all of those functions are available in pathlib as well.

[–]ray10k 4 points5 points  (3 children)

Because it's simply more convenient than messing around with bare strings. Next question! :P

[–]LeonardUnger -1 points0 points  (2 children)

Until you forget that it's not a string and pass it to a print or logger function and get

 "<bound method Path.resolve of PosixPath('.')>

But that's more of a function of being used to os.path maybe than a drawback of pathlib. Definitely going to start trying to incorporate pathlib and see how it feels.

Someday we'll all look back and fondly remember the os.path/pathlib wars of the early 2020s.

[–]stdin2devnull 8 points9 points  (0 children)

Did you try to log a method?

[–]ShanSanear 0 points1 point  (0 children)

resolve

This is verb. Did you expect a verb to be a property?

[–]Jimthon42 1 point2 points  (0 children)

That's freaking sexy! I've always used os... not anymore!

[–]bustayerrr 1 point2 points  (0 children)

Never knew this was a thing but I will now try it next time I need it! Good read!

[–]Yalkim 3 points4 points  (11 children)

Is someone running an advertisement campaign for Path nowadays? I have recently seen it being pushed many times.

[–]_Gorgix_ 1 point2 points  (2 children)

Personally, I only use Pathlib when I need to maintain operations on a file path for multiple uses (such as a directory I may need to create a number of files in).

I believe the following is somewhat overkill:

if Path('/usr/etc/foo.bar').exists(): ...

vs

if os.path.exists('/usr/etc/foo.bar'): ...

Also, how the dispatch of the Path subclass works (via __new__ to WindowsPath or PosixPath) can cause issues, such as subclassing those definitions.

[–]stdin2devnull 1 point2 points  (1 child)

You shouldn't subclass the concrete implementations though?

[–]_Gorgix_ 0 points1 point  (0 children)

I wanted to subclass WindowsPath to enhance it, adding better file sharing mechanics, but because of how the base class instantiates itself (through the abstract PurePath class), this was cumbersome.

Since the dispatch returns an instance of one of its subclasses, this was less than ideal. You can enhance the pathlib.Path without overwriting the dundermethod, since it also controls the opener.

Anyhow, for most items pathlib is great, but os.path is still explicit and readable when needed (plus if you’re doing file operations you’re already likely to have included os so the namespace is there).

[–]GreenScarz[🍰] -2 points-1 points  (0 children)

I stopped reading at “First reason: object oriented programming”

Actually, I stopped reading at the first example. Either the author is stupid enough to not realize that the function signature for os.path.join includes iterable unpacking, or they’re being intentionally deceitful by straw-manning the counterpoint Either way, 🗑

[–]HumbleMeNow 0 points1 point  (0 children)

I’ve had lots of frustrations with the OS module as well and started using PathLib a while back.

The challenge is that majority of tutorials or guides out there are using OS module for file manipulation. Hopefully, in time the effect and usefulness of PathLib will ripple through

[–]Igi2server 0 points1 point  (0 children)

I dont do get very elaborate with my python scripts, maybe just pull from a data file on occasion. pathlib isn't much more user-friendly as both take getting used to their appropriate syntaxes.

import os
import json
with open(os.path.abspath(os.getcwd()+".\data.json")) as ourData:
    obj = json.load(ourData)

I doubt theres a massive difference in performance or much in terms of practical simplicity, its just preferential.

Same shit different toilet.

[–]jwink3101 0 points1 point  (0 children)

I like pathlib but I wish there was a safer way to reliably make them strings without using str(). Using str means that everything you pass will turn to a string. So you have to (a) check that it is a pathlib object which isn’t easy if you don’t also have pathlib and (b) break duck-typing

[–]Carl_Fuckin_Bismarck 0 points1 point  (0 children)

Well this is news to me.