Pathlib and using multiple methods - is this intended functionality?

First things first - here is my code.

from pathlib import path
import os 

def get_toml_path():
    #default_path = os.path.expanduser(const.D_CONFIG_PATH)
    final_path = Path(os.getenv('CONFIG_PATH', const.D_CONFIG_PATH)).expanduser().resolve()
    if not final_path.is_file():
        raise FileNotFoundError(f'File {final_path} does not exist')
    return final_path

What I was trying to do was - I have a CONST.py where D_CONFIG_PATH is a path like ~/blah/this.toml. Which would act as a default file path unless an env variable named CONFIG_PATH was set. I wanted to use Pathlib’s PATH().resolve() with CONFIG_PATH.

I tested with just the following where D_CONFIG_PATH was like `~/blah/this.toml and the env variable was not set.

final_path = Path(os.getenv('CONFIG_PATH', const.D_CONFIG_PATH)).resolve()

And obviously I got /home/steven/blah~/blah/this.toml

So what I did was;

final_path = Path(os.getenv('CONFIG_PATH', const.D_CONFIG_PATH)).expanduser().resolve()

Which works. It will use expanduser when the path is like ~/ and it will use resolve when it is a path like /path/to/something.

The docs don’t say anything about using multiple methods together. I am wondering if this is intended functionality or if I lucked into something? And will this be supported going forward?

Something else I noticed is - if you reverse the method order, it doesn’t work - resolve().expanduser(). Which makes me wonder;

  1. Was this functionality even intended?
  2. If it was - is the fact that this doesn’t work when I do resolve().expanduser() a bug?

Am I just out in the wild on this one? I may just being using Pathlib entirely wrong. Again, I was trying to use expanduser() when the path was pulled from my CONST.py file and resolve() when pulled from the env variable.

System:

  • Fedora 37
  • Python 3.11

Not entirely clear on what you mean by “combining methods”. This:

thing.method1().method2()

is not “combining” method1 and method2, it’s just using one after the other. method1 runs on thing, and method2 runs on whatever method1 returns.

As for Path.expanduser().resolve() vs. Path.resolve().expanduser(), the reason the former works but not the latter is documented in os.path.expanduser:

Emphasis mine.

Path.resolve() prepends the current working directory to any relative path, so a path beginning with ~ will be resolved to /current/working/directory/~. Since the tilde no longer appears at the beginning of the path, expanduser fails.

I think you’re getting hung up on semantics / reading something into what I wrote. I simply meant, when used together.

I don’t really see what point you’re trying to make with the rest.

What I can gather is, I don’t need, resolve() because expanduser should work regardless. I’ll need to test.

It’s not semantics. They are not used together. First one is used. Then the other is used. They have nothing to do with each other.

Consider:

a = "abc"
print(a.upper().lower())
print(a.lower().upper())

Would you expect these to have the same output? If not, why would you expect any other combination of methods to result in the same output when their order is changed?

You’re getting too hung up on one or two specific words I’m using. Think of used together as “written together.”

Reading some docs, it looks like I might have simply been misunderstanding what was happening as I theorized in OP.

I need to test the different methods individually to be certain.

Sure.

Why would writing methods together change their behavior?

Because .expanduser().resolve() is different than .expanduser().

I went down the rabbit hole and found everything I was looking for on SO. Thanks.

For the sake of future readers with similar questions who may stumble on this topic, could you share the SO post that solved your issue?

(I’m still not clear on what the issue was, tbh)

Here is one of over a dozen.

Steven, what you did, calling a class X instance method on the result of another X instance method, is called ‘method chaining’. It is a common practice. In general, order matters. Class str instances are immutable and the str methods that return an str are commonly chained.

List methods that only mutate the input list block method chaining by returning None instead of the list. This was Guido’s design choice, though some people wish he had made the other choice.

@tjreedy, good to know. Thank you.

I don’t entirely understand what you mean by this, “List methods that only mutate the input list block method chaining by returning None instead of the list.”. It almost makes sense, but I am missing the full meaning.

Thanks again.

I am not one of those people :- IMO this returns-new-values and
modifies-in-place-and-returns-None distinction avoids (by failing) many
common mistakes.

Cheers,
Cameron Simpson cs@cskk.id.au

I think you’re getting hung up on semantics / reading something into what I wrote. I simply said, when used together.

Semantics matter. In programming they’re critical. Precision in writing
is essential in technical discussions. Because semantics.

I don’t really see what point you’re trying to make with the rest. Pathlib’s expanduser works with ~/ and not direct paths. Pathlib’s resolve works with direct paths and not ~/.

I probably am just over complicating what I am trying to do. But the
fact that combining the methods in one step works in one order but not
the other is odd.

It just means they’re not commutative operations. Many operations are
order dependent - they’re nothing special or weird or odd about your
particular example.

Your statements above “Pathlib’s expanduser works with ~/ and not direct
paths. Pathlib’s resolve works with direct paths and not ~/.” pretty
much define your problem: effectively you need expanduser to convert
your “symbolic” path (in that it starts with a tilde which is a symbol
for a user’s home directory) into a plain nonsymbolic relative path
which can be used with resolve. Swapping them prevents the conversion
happening in time.

And resolve can’t “know” that it has receives a symbolic path, because
the tilde is an entirely legal pathname character. Your programme’s
wider context “knows” about the tilde, not pathlib.

Cheers,
Cameron Simpson cs@cskk.id.au

This is a forum post where I simply need to convey an idea of what I mean and the conversation evolve as learning happens. This is one place black and white shouldn’t be absolutely adhered to. As long as I convey what I am asking / what I mean so that in the end I get the information I came for / my misunderstanding is cleared up - that is all that really matters. In the end - that means we worked toward the black and white and specifics.

I feel like you’re more re summarizing the overall thread with the rest of what you wrote, it doesn’t quite explain what they meant by, "“List methods that only mutate the input list block method chaining by returning None instead of the list.” But I sort of get it. I’m sure if I re read it later, that sentence will click.

The short of it is - expanduser().resolve() works because expanduser has to happen before resolve for it to work properly. Because expanduser doesn’t care if you give it ~/ or /home/steven. Then resolve can do what it needs to after the fact.

Thanks again, everyone. I learned a lot.

And the way to best convey the idea of what you mean is to be precise. Because semantics.

It bothers me when people get sloppy with their language, and then blame everyone else for misinterpreting, rather than accepting the blame for being unclear. If you simply said “sorry, I was unclear, let me rephrase”, there’d be no problem. Trying to punt it back to your readers is unfair on people who are expecting and presuming upon accuracy.

I’ve taken responsibility in several of my replies, but I’m still hearing about it. It isn’t my fault if an individual hears a word, locks in, and cannot deviate even when told they should. This is an ongoing conversation, conversations evolve, and meaning becomes more clear. It is unfair to expect me to word my post in a way that would please every one in the world. My meaning is clear, and I re-iterated numerous times to make it more clear. At this point, you’re beating a dead horse simply to beat a dead horse.

This is a place of learning. I may not know the exact terminology that works for a specific individual, and I certainly cannot word a post in a manner that pleases everyone. The best I can do is convey what I mean. And one would think after three times of saying, “No, what I meant was…” Would get the point across that I didn’t mean it the initial way it was taken.

Thank you, all. As far as I am concerned, this post is closed.