[Python-Dev] convertibility and "Pythonicity"

David Abrahams dave@boost-consulting.com
Sat, 24 Aug 2002 09:14:22 -0400

[Is "Pythonicity" the right word?]

I'm interested in getting some qualitative feedback about something I'm
doing in Boost.Python. The questions are,
    1. How well does this behavior match up with what Python users have
probably come to expect?
    2. (related, I hope!) How close is it to the intended design of Python?

When wrapping a C++ function that expects a float argument, I thought it
would be bizarre if people couldn't pass a Python int. Well, Python ints
have a lovely __float__ function which can be used to convert them to
floats. Following that idea to its "logical" conclusion led me to where I
am today: when matching a formal argument corresponding to one of the
built-in Python types, first use the corresponding conversion slot.

That could lead to some surprising behaviors:

    char index(const char* s, int n); // wrapped using Boost.Python

    >>> index('foobar', 2)    # ok

    >>> index(3.14, 1.2)      # Wierd (floats have __str__)
    >>> index([1, 3, 5], 0.0) # Super wierd (everything has __str__)

So I went back and tried some "obvious" test in Python 2.2.1:

    >>> 'foobar'[3.0]
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: sequence index must be integer

Well, I had expected this to work, so I'm beginning to re-think my "liberal
conversion" policy. It seems like Python itself isn't using these slots to
do "implicit conversion". But then:

    >>> 'foobar'[3L]

[The int/long unification I've heard about hasn't happened yet, has it?]


    >>> range(3.3, 10.3)
    [3, 4, 5, 6, 7, 8, 9]


    >>> range('1', '5')
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: an integer is required

Now I note that strings don't have __int__, so I guess the int type handles
int('42') itself using special knowledge about strings. I suppose that's to
keep strings from seeming to be numbers, since the nb_int slot fills in the


    >>> class zero(object):
    ...     def __int__(self): return 0
    >>> range(zero(), 5)
    [0, 1, 2, 3, 4]

So, is there any general practice, (even if it's not universal)? Do Python
functions usually tend to coerce their arguments into the types they're
expecting? I'm guessing the answer is no...

           David Abrahams * Boost Consulting
dave@boost-consulting.com * http://www.boost-consulting.com