On Nov 14, 2008, at 2:07 PM, Paul McGuire wrote:

Or to be even more thorough:
def sub(x: must have getitem, y: must have strip and strip must be
callable, and y.strip must return something that has replace and
replace must be callable)

So even this simple example gets nasty in a hurry, let alone the OP's
case where he stuffs y into a list in order to access it much later,
in a completely different chunk of code, only to find out that y
doesn't support the complete string interface as he expected.

Very true. That's why I think it's not worth trying to be too pure about it. Most of the time, if a method wants a Duck, you're going to just give it a Duck.

However, I would like to also handle the occasional case where I can't give it a Duck, but I can give it something that is a drop-in substitute for a Duck (really, truly, I promise, and if it blows up I'll take responsibility for it).

A real-world example from another language (sorry for that, I've been away from Python for ten years): in REALbasic, there is a Database base class, and a subclass for each particular database backend (Postgres, MySQL, whatever). This works fine most of the time, in that you can write general code that takes a Database object and Does Stuff with it.

However, all of those database backends are shipped by the vendor, or by plugin authors -- you can't create a useful Database subclass yourself, in RB code, because it has a private constructor. So you end up making your own database class, but that can't be used with all the code that expects a real Database object.

Of course, the framework design there is seriously flawed (Database should have been an interface, or at the very least, had a protected rather than private constructor). And in Python, there's no way to prevent subclassing AFAIK, so this particular issue wouldn't come up. But I still suspect that there may be times when I don't want to subclass for some reason (maybe I'm using the Decorator or Adapter or Bridge pattern). Yet I'm willing to guarantee that I've adhered to the interface of another class, and will behave like it in any way that matters.

So, the level of assertion that I want to make in a method that expects a Duck is just that its parameter is either a Duck, or something that the caller is claiming is just as good as a Duck. I'm not trying to prevent any possible error; I'm trying to catch the stupid errors where I inadvertently pass in something completely different, not duck-like at all (probably because some other method gave me a result I didn't realize it could produce).

So things like this should suffice:

        # simple element
        assert(is_stringlike(foo))
        assert(is_numeric(foo))
        assert(is_like(foo, Duck))

        # sequence of elements
        assert(seqof_stringlike(foo))
        assert(seqof_numeric(foo))
        assert(seqof_like(foo, Duck))
        # (also "listof_" variants for asserting mutable sequence of whatever)

        # dictionary of elements
        assert(dictof_like(foo, str, int))

Hmm, I was already forced to change my approach by the time I got to checking dictionaries. Perhaps a better formalism would be a "like" method that takes an argument, and something that indicates the desired type. This could be a tree if you want to check deeper into a container. Maybe something like:

        assert(fits(foo, dictlike(strlike, seqlike(intlike))))

which asserts that foo is something dictionary-like that maps string- like things to something like a sequence of integer-like things. Most cases would not be this complex, of course, but would be closer to

        assert(fits(foo, strlike))

But this is still pretty ugly. Hmm. Maybe I'd better wait for ABCs. :)

Cheers,
- Joe

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to