Public API design

class File:
    datetime: dt.datetime
    subject: str
    segments: ...

    @property
    def size(self) -> int:
    @property
    def name(self) -> str | None:
    @property
    def stem(self) -> str | None:
    @property
    def extension(self) -> str | None:

I have an object like this which is providing bindings to the same from rust. name, stem, and extension are parsed from the File.subject, which is a freeform string that MAY have the filename. The properties are there to provide a more file-like experience. Note that it is perfectly valid for File.subject to be garbage so File.(name|stem|extension) will be None. In rust it’s a simple Option<&str> but I’m wondering what should I do in python.

There’s some precedence in pathlib.Path where pathlib.Path.(name|stem|suffix) return an empty string if they fail instead of None but my API is already not 1:1 there (e.g., File.extension instead of File.suffix, because python’s Path.suffix preserves the leading dot while rust’s Path.extension does not).

It depends. There are a few valid options for properties that might or might not have a sensible value:

  • raise an error
  • return None
  • return a NULL element of the data type (0, [], "", set(), …)
  • return a default (e.g. <unknown> for name)

Which of these are correct depends on the expected usecases for these attributes, i.e. what do users most likely want to do when they get objects with missing values?

  • Abort because they can’t continue anyway? They would want to raise an error, so you can just do it as well.
  • Pass it around as a value for others to decide if we should abort or not? Return None: It’s a value that clearly indicates “nothing to see here”
  • Only use it to display information for the user? NULL element. Less jarring to look at than None, doesn’t have to be treated all that specially for most display-only code.
  • Just continue on without having to worry about it? Use a default. Note that choosing a good default is often harder, and I don’t think there would be good defaults for this specific situation.
1 Like

That’s an excellent breakdown, thank you.

I’ll go over each point:

  • raise an error - Since the spec I’m working with allows File.subject to be garbage, it’s not an error and erroring will break parsing otherwise valid files so I won’t raise an error
  • return a default - I think it’s impossible to pick a default value here that’ll be intuitive and not completely arbitrary, so I won’t be adding a default.
  • return None - Since it’s perfectly valid to be “nothing”, I give the user an explicit None and let them decide what to do with it. They may just display it, process it, or outright reject it.
  • return an empty string - Similar to the none case, but I’m assuming that they’ll likely use it for aesthetics and so don’t care too much about it missing or not.

That settles it, I’m much more confident in the return None approach now. Thank you for breaking it down so well for me!

1 Like