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
runmethod.If you aren’t testing for the type of that object before calling its
runmethod, the you’re using duck typing, plain and simple! (In this case, if it has arunmethod, then it’s aRunner.) As long as you don’t usetypeorisinstanceon the object with arunmethod, 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
nodeobjects probably don’t implement the same interface at all! Strings fundamentally don’t quack like anode, 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
runreturns anodeobject. There’s no need to useisinstanceortypeto test for that. Just pretend it’s anodeobject 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 thatrunshould pass anodeobject.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 nottype(obj) == strbecause 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
iteron an argument at the beginning of a function that you want to accept both generators and sequences.)