3 ms·
Designing Pythonic Library APIs
- Spivak 3y agoThere's a lot here that is pretty general and not Python specific, let's add some of those. * Don't use map/filter/select unless you have no other choice. Prefer comprehensions. * If a loop can be expressed by a comprehension then it should be. * If you can get away with a context manager, prefer that over destructors. * Put your unit tests in classes rather than module_level functions. * Have def main if __name__ == __main__: main() over entrypoints that have logic at the module level. * Inheritance and multiple inheritance in Python is fantastic. Prefer it over composition. * Remove the word lambda from your vocabulary. * If possible avoid returning a list and instead prefer a generator. * Replace "for key in dict" with "for key, value in dict.items()" as your default way of iterating over a dict. * Whenever possible use pathlib over open and always specify the encoding for files opened in text mode. * It's more common to have "def abstractfunc: pass" than raise NotImplementedError. * One file one class is for Java people, in Python pack it all in one file. * obj.register(func) should probably be a decorator "@obj.register def func: …" * Take advantage of truthy/falsely values such as [] being false. Not if len(list) > 0 but if list. * Write a good __repr__ for every class. * Take advantage of keyword only arguments to enforce subject.verb(object, adj1=2, adj2=2). * If you leave black at the default of 80 characters someone will egg your GH profile. * Add logging using the logging module and let the user if your lib decide if they're useful or not. * Abuse implicit string literal concatenation over str + str + str * Formatting strings with % or .format is out. Either f-strings or the string.template library. * Use StringIO over string concatenation. * Raise an exception over returning Union[Ok, Err] or Optional[T]. * Log a warning over using the warnings module. * Allow reading API keys from environment variables. * from package import Thing is preferred unless the module in question is just a collection of functions. * Use StrEnum over stringly typed function args. * Do pass around bound methods even though it might feel wrong.
- deleted 3y ago[deleted]
- benhoyt 3y agoI like (most of) this list! Note, however, that most of these are more for writing Python code, whereas my article was specifically about designing Python APIs -- that is, the public-facing stuff. A few specific comments on your points: > If a loop can be expressed by a comprehension then it should be. Not sure about this one. I prefer comprehensions if you're filtering or transforming without side effects (other than creating a new list). But I prefer a regular for loop over things like [os.remove(p) for p in paths if p.endswith('.pyc')], otherwise the side effect (the important bit) kind of gets lost, and it's harder to handle errors. > Inheritance and multiple inheritance in Python is fantastic. Prefer it over composition. Oooh, that one's up for debate. I won't enter into the fray here though. :-) > Remove the word lambda from your vocabulary. Mostly agreed, apart from tiny sort key functions and the like. > One file one class is for Java people, in Python pack it all in one file. Agreed, thanks for saying it! > from package import Thing is preferred unless the module in question is just a collection of functions. This is okay if you're importing from a module made to be used this way. But I disagree that it's better! See my rationale under https://benhoyt.com/writings/python-api-design/#module-and-package-structure https://benhoyt.com/writings/python-api-design/#module-and-p...
- Spivak 3y ago> Not sure about this one. I prefer comprehensions if you're filtering or transforming without side effects. That's what I was trying to drive at with if it can be replaced but you said it a million times clearer.