Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

How is this code "dividing" by a string?

+5
−0

I found a strange expression in some Python code:

from library import HOME_DIRECTORY

file = HOME_DIRECTORY / "file.json"

It seems to be dividing a string by another string in order to do something with files.

However, I can't make this work in the REPL with my own strings:

>>> "one" / "two"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'str'

What is the code actually doing? Even if it didn't cause an error, I can't guess what dividing strings would mean, or why it would be useful.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

2 comment threads

Classes can overload operators. One example is the [`Path` class from `pathlib` module](https://docs.... (1 comment)
What type is HOME_DIRECTORY? (1 comment)

3 answers

+6
−0

You very likely have a pathlib Path (or PurePath) object there. pathlib overrides the division operator to perform platform-aware path appends.

>>> import pathlib
>>> pathlib.Path('/foo') / 'bar'
PosixPath('/foo/bar')
History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

0 comment threads

+6
−0

As already said by another answer, you're not "dividing a string by another string". I'd just like to complement by providing more details about how this works.


If you try to divide a string by another, such as x = 'a' / 'b', you'll get an error. Therefore, in your code, HOME_DIRECTORY is certainly not a string. But how can you "divide" it by a string?

Well, that's because in Python operators can be overloaded. In fact, when you do x / y, you're actually calling type(x).__truediv__(x, y). This behaviour is described in the language's Data Model, and in this same link you can also find all the special methods and the respective arithmetic operators (such as __add__ for +, __sub__ for -, and so on).

For the division operator (/), the special method is __truediv__, and that's why this code works:

x = 10
print(type(x).__truediv__(x, 2)) # 5.0
print(type(x).__truediv__(x, 2) == x / 2) # True

And this doesn't work for strings because they don't define such method:

print(int.__truediv__) # <slot wrapper '__truediv__' of 'int' objects>
print(str.__truediv__) # AttributeError: type object 'str' has no attribute '__truediv__'

x = 'a'
print(type(x).__truediv__(x, 'b')) # AttributeError: type object 'str' has no attribute '__truediv__'

That's how we know that HOME_DIRECTORY is not a string. And based on the code, it's probably an instance of pathlib.Path. If you look at the source code, you'll see that this class defines the __truediv__ method.

Not only that, but the method accepts either Path instances or strings as the second operand, and it internally converts all to a single Path object. That's why you can "divide" a Path by a string:

from pathlib import Path

p = Path('folder')
print(p / 'file.json') # folder/file.json
print(type(p).__truediv__(p, 'file.json')) # folder/file.json

# both are the same (calling the division operator is the same as calling __truediv__ method)
print(type(p).__truediv__(p, 'file.json') == p / 'file.json') # True

If you think of / in terms of arithmetic division, of course it won't make sense to "divide" something by a string. But I believe this was made because the slash (/) is used as a path separator in *nix systems, and allowing to write code such as folder/file would sound more "natural" and "intuitive", as we're dealing with Path instances (AKA "objects that represent filesystem paths"). So I could write folder/file just as I'd do in a command line.

In this case, you should stretch your mental model and forget about "divison". The / is just an operator that internally calls a specific method. The fact that numbers define it as the division operator is just a detail (a very convenient one, though, as it's the "expected" behaviour). But it's not required that this operator always means "division" in every case/context. Operator overloading allows you to redefine operations to have any meaning you want - whether it makes sense or not (for Path objects, it does, IMO).

Take, for instance, the + operator. For numbers, it performs addition, but for strings, it concatenates them ('1' + '2' results in '12'). That's because strings define the __add__ method, whose behaviour is different to int and float. Does it make sense to "add strings"? Maybe it does for lots of people, because that's a feature that many languages have and we're so used to it that it just became "normal", but in a strict sense, it wouldn't make sense to perform arithmetic operations with text. With operator overloading, on the other hand, we can extend and change the behaviour and meaning of any operator, to suit our needs (such as "For strings, + means concatenation instead of addition, because I said so").


As a side note, you can do it with your own classes:

class TrueDivTest:
    def __truediv__(self, other):
        return f'divided by {type(other).__name__}'

x = TrueDivTest()
print(x / 1) # divided by int
print(x / 'abc') # divided by str
print(x / []) # divided by list
print(x / x) # divided by TrueDivTest

Note how the second operand can be of any type. In a real case scenario, of course, I could perform a different action depending on the type, and/or restrict to specific types (and give an error if the type is not accepted), and so on. You're free to do anything with any operator, and give them any meaning you want. You're not restricted by the arithmetic definitions.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

0 comment threads

+2
−0

Python allows for operators to be overloaded (the subject of a separate future Q&A). The / operator doesn't strictly mean division in a mathematical sense; it means whatever the operands define it to mean - but we may call the operation "division" regardless. (Specifically, we say "true division" to refer to the operation implemented by / - this is to distinguish it from "floor division", implemented by //.)

In this case, the left-hand side is a type that implements a __truediv__ method (which implements the / operator); so Python translates the operator into a call to that method: HOME_DIRECTORY.__truediv__("file.json"). Strings don't implement this, but whatever HOME_DIRECTORY is, does.

The semantics, of course, depend on the type of HOME_DIRECTORY. However, based on the context, we can infer that HOME_DIRECTORY is an instance of the standard library pathlib.Path type. The pathlib library is designed specifically to support this sort of operation, to make it easier to work with file paths. In this case, the code simply joins up the path, creating a new path that represents "the file file.json, within the HOME_DIRECTORY".

Thus, HOME_DIRECTORY.__truediv__("file.json") is roughly equivalent to os.path.join(HOME_DIRECTORY, "file.json") - except that it starts and ends with a pathlib.Path. The os module also defines a PathLike protocol implemented by pathlib.Path, which allows you to use a pathlib.Path in most places that you'd use a path string.

Of course, the reason we use "division" for this operation is purely mnemonic: / is the default path separator (the one that Linux uses natively, and one which the Windows runtime will translate automatically). So given x and y path components, x / y intuitively makes sense as a way to put them together into a longer path.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »