Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I balance "Pythonic" and "convenient" in this case?

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)?

like image 546
Owen Avatar asked Aug 18 '11 04:08

Owen


1 Answers

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.)

like image 71
senderle Avatar answered Oct 18 '22 07:10

senderle