I would not differentiate needlessly between the terms function and [“dunder”, “instance”, “module”, “class”] method, this isn’t practical at the current point in time, and arguably not pythonic. They are all callables
and behave very similar for all practical purposes, all quacking like a duck.
If you want to deep-dive into the intricacies, there’s technically a bunch of alternative “method types” with slightly differing calling and argument passing conventions. There are class methods, static methods, class properties, and module-scope functions off the top of my head, all very similar, but particularly suitable under specific circumstances.
If I wanted a String
utility class, for example, I could have that:
class String:
@staticmethod
def len(string: str) -> int:
if not isinstance(string, str):
raise TypeError("Cannot determine length of non-string object")
return len(string)
s = "foobar"
assert String.len(s) == 6
In Python I generally find static methods hardly as relevant as in other languages, and tend to create utility modules instead, with plain functions and maybe dataclasses for complex arguments or return values, which can then be imported and called pretty much the same way as a static method, but without the extra organisational class layer. I’d argue that’s very much a matter of taste, and how much of a purebred OOP jockey you are, for better or worse.
Class properties are cool, however, you might want to look into that. It’s getters/setters on crack, masquerading as their respective property, so that something like this “just works”:
s = MyString("foobar")
assert s.len == 6
Generally speaking, there is a race condition lurking where the OS may do whatever to your file you just checked, rendering the check strictly obsolete the moment you get the result. This isn’t typical, but possible, and a lovely old-school security vulnerability class. :)
A more practical argument is that you’re going to handle any errors your
open()
may throw, anyway, and therefore it’s simply redundant to check for file existence explicitly beforehand.Under specific circumstances, you may want to do explicit, very specific tests for more detailed error reporting than “error opening file!”, for example “save file is corrupted” if it’s too short or zero-length, or “save file directory is gone. What the hell, dude? Recreating it, but stop fiddling with my files!”
This is easy to overengineer. Best is to get into the very sensible habit of always checking for errors or exceptions returned by your calls, and this will become a non-issue.
In this particular use-case of save file loading, you might implement displaying a listing of save files in a directory with
opendir
/readdir
orFindFirstFile
/FindNextFile
and its ilk, to offer a list of files to load, which doubles as a crude existence test already. Many ways lead away from Rome. If you’re considering loading an autosave and offer a “Continue” button or something, a cheap existence test would work very well to decide if that button needs to be displayed in the first place, but doesn’t free you from handling anyopen()
errors later. You could alsoopen()
and validate an autosave directly, and when/if the user decides to “Continue”, use the already reserved file descriptor and maybe even the preloaded save data to quickly jump into the game.If you want a simple answer: Do not introduce race conditions. Always acquire a lock for a shared resource before doing anything with it.