Python idioms in the API
See original GitHub issueThe README mentions that:
So far this extension has been written by folks who are primarily Rust programmers, so it’s highly likely that there’s some faux pas in terms of Python idioms. Feel free to send a PR to help make things more idiomatic if you see something!
Indeed, after using the API for a while I noticed a few aspects that aren’t quite in line with Python conventions, though some of them are harder to ignore than others.
- Nullary getter methods like
ExportType.name
that always succeed and always return the same value are a pretty clear-cut case for using Python properties instead. This is, by far, the least idiomatic aspect of the library, and it’s very easy to miss a()
that feels like it shouldn’t be there. - There are lots of unary setter methods like
Config.debug_info
named after the property they’re changing, and there is a good case for using Python setter-only properties instead. (As a bonus, if wasi-c-api ever adds getters for them, they could be made readable in a backwards compatible way.) This doesn’t change much for e.g.Config
, but forLinker
for exampleallow_shadowing
works like a simple setter with no side effects, butdefine_wasi
, while similarly imperatively named and used, irreversibly changes its state. - Nullary casting methods like
ExternType.func_type
feel very odd overall. 1. They’re named like accessors, but they work likedynamic_cast
. 2. The naming is inconsistent for conversion in different directions—FuncType
→ExternType
conversion is done byas_extern
, butExternType
→FuncType
conversion is performed byfunc_type
. 3. My impression so far is that most uses of these casting methods are in contexts where a specific type is expected, in which case it would be more convenient if they raisedTypeError
instead of returningNone
. (If they did, another method would be necessary to dispatch on an unknown type.) Val.get_*
introduces a third naming scheme for type conversion functions, one that clashes in meaning with e.g.Instance.get_export
.*.get_export()
methods seem like they would only fail in truly exceptional conditions and so also appear good candidates for raising an exception instead of returningNone
.
To summarize:
- I would change simple getters and setters to be properties.
- I would rename all casting methods to
as_*()
. - I would change
*.get_export()
to raise if the export is not found (this is particularly important forCaller.get_export()
, since currently it looks like there is no way to distinguish the cases of “export does not exist” and “the caller is gone” at all, even for debugging, and the exception message would allow that). - I would probably change the dynamic casting methods to raise if the type is wrong, and add a matching
is_*
method for the cases where dynamic dispatch is indeed necessary. I’m not completely certain about this one, but it seems that currently, dynamic dispatch would look something like:
if extern.memory():
do_something_with_memory(extern.memory())
elif extern.table():
do_something_with_table(extern.table())
so changing it to:
if extern.is_memory():
do_something_with_memory(extern.as_memory())
elif extern.is_table():
do_something_with_table(extern.as_table())
doesn’t really make it any worse.
What do you think? All of these changes are likely to break most existing code, so I’d rather discuss them before working on a PR.
Also, I should mention that “do nothing” is an option. In general Python libraries are a lot looser with conventions than libraries in many other languages; I’d rather see the API improved, personally, but the current one isn’t drastically more inconsistent than what one would expect in general.
Issue Analytics
- State:
- Created 3 years ago
- Comments:9 (3 by maintainers)
Top GitHub Comments
This is actually an established and widespread Python idiom called EAFP. Personally, I follow it very cautiously; for example, dictionary indexing raising
KeyError
that you are supposed to catch is a source of subtle bugs because thetry: except:
construct is unwieldy and it is tempting to put something besides the index operation itself in thetry:
expression, but of course now you may have caught an exception raised by third party code that should have bubbled up.In this case however, assuming my intuition about the use of
.get_export()
is correct–that the vast majority of invocations are likely to succeed, and failures indicate logic errors–it seems like a solid case for that idiom. Consider that it would work much like the built-in dictionaries.Thanks for the fix!