I have an "interface" that will be implemented by client code:
class Runner:
def run(self):
pass
run
should in general return a docutils
node
but because the far far most
common case is plain text, the caller allows run
to return a string, which will be
checked using type()
and turned into a node
.
However, the way I understand "Pythonic", this is not "Pythonic" because
checking the type()
of something doesn't let it "be" a type by "acting" like
one -- ie "Pythonic" code should use duck typing.
I considered
def run_str(self):
pass
def run_node(self):
return make_node(self.run_str())
but I don't care for this because it puts the not-so-interesting return type right there in the name; it's distracting.
Are there any ideas I've missed? Also, are there problems I might encounter down the road with my "bad" system (it seems more or less safe to me)?
I think this is a slightly deceptive example; there's something you haven't stated. I'm guessing that when you say you "have an interface," what you mean is that you have some code that accepts an object and calls its run
method.
If you aren't testing for the type of that object before calling its run
method, the you're using duck typing, plain and simple! (In this case, if it has a run
method, then it's a Runner
.) As long as you don't use type
or isinstance
on the object with a run
method, then you're being Pythonic.
The question of whether you should accept plain strings or only node objects is a subtly different question. Strings and node
objects probably don't implement the same interface at all! Strings fundamentally don't quack like a node
, so you don't have to treat them like one. This is like an elephant that comes along, and if you want it to quack like a duck, you have to give the elephant a tape player and train the elephant to use it first.
So this isn't a matter of "duck typing" any more, but of interface design. You're trying to decide how strict you want your interface to be.
To give you an answer, then, at this level, I think it's most Pythonic to assume that run
returns a node
object. There's no need to use isinstance
or type
to test for that. Just pretend it's a node
object and if the programmer using your interface gets that wrong, and sees an exception, then they'll have to read your docstring, which will tell them that run
should pass a node
object.
Then, if you want to also accept strings, or things that quack like strings, you can do so. And since strings are rather primitive types, I would say it's not inappropriate to use isinstance(obj, basestring)
(but not type(obj) == str
because that rejects unicode strings, etc.). Essentially, this is you being very liberal and kind to lazy users of your program; you're already going above and beyond by accepting elephants as well as things that quack like ducks.
(More concretely, I'd say this is a bit like calling iter
on an argument at the beginning of a function that you want to accept both generators and sequences.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With