Just to clarify discussion, here is what I'm proposing (which is still  
in flux, and simplified due to memory issues, which does make it less  
attractive as one does not get to choose the used encoding, but it  
would always be UTF-8 in Py3).

Without directive(s) (as it is now):

    char* <-> bytes

With the directive(s) (which can be applied locally or globally):

     char* <-> str
     unicode/bytes -> char* would also work (for Py2/Py3 respectively)

The encoding used would be the system default (in Py2) and UTF-8 (in  
Py3). This would use the defenc slot so the encoded char* would be  
valid as long as the unicode object is around, and the long term  
future of the defenc slot needs to be ensured before this could be  
used for non-arguments conversion.

Also out there is the idea of a directive that would make char* become  
unicode in both Py2 and Py3.


On Nov 29, 2009, at 8:47 AM, Stefan Behnel wrote:

> Robert Bradshaw, 28.11.2009 22:12:
>> My personal concern is the pain I see porting Sage to Py3. I'd have  
>> to
>> go through the codebase and throw in encodes() and decodes() and
>> change signatures of functions that take char* arguments
>
> That's what I figured. Instead of having to fix up the code, you  
> want a
> do-what-I-mean str data type that unifies everything that's unicode,  
> bytes
> and char*, and that magically handles it all for you.

Exactly. Improve the compiler rather than change the code.

> In that case, you should drop the argument of Pyrex compatibility  
> for now,
> because I don't think you can have a Cython specific hyper-versatile  
> data
> type with automatic memory management and all that, while staying
> compatible to the simple str/bytes type in Pyrex - even if we manage  
> to get
> it working without new syntax.

Just because I don't need Pyrex compatibility doesn't mean it isn't a  
worthwhile goal (though that was the main point of the previous  
thread, not this one).

> We'd clearly break a lot of existing Pyrex/Cython code by starting  
> to coerce char* to unicode, for example.

Only if the directive was enabled, and perhaps only in Py3. Existing  
code wouldn't break.

>> (which, I just realized, will be a step backwards for cpdef  
>> functions).
>
> True. For cpdef functions, a char* parameter would be well-defined  
> as long
> as user code doesn't use different encodings for char* internally  
> (which is
> somewhat unlikely).
>
> Ok, let's think this through. There's two different scenarios. One  
> deals
> with function signatures (strings going in and out), the other one  
> deals
> with conversion on assignments or casts.

I think it's easier if the Python to C and C to Python conversions are  
uniform whether it happen via to coercion, assignment, or function  
signature constraints. Then the question is what objects can be turned  
into a char* (the directive would add unicode) and what object does  
char* turn into (the directive would create str in Py2 and Py3).

Function arguments typed as char* are a particularly useful case  
though, and it would be nice to make this friendlier for Py3.

> In total, there are three cases:
> accepting bytes/str/unicode in a str/bytes/char* signature, coercing
> str/unicode to char*, and coercing char* to bytes or unicode.
>
> Function signatures have two sides to them that are not symmetric.  
> One is
> that you want your string accepting functions to be agnostic about  
> the type
> of string that comes in (although you may or may not want to have  
> control
> about memory usage if you use char* in the signature), and the other  
> side
> is that you want some string to go back out, which you may want to  
> be a
> Py2-str (read: bytes) or a unicode string (maybe in Py2 and  
> definitely in
> Py3). Remember that if your code originally couldn't handle unicode,
> there's likely to be more code that can't handle it, either, so you
> wouldn't want your hyper-versatile type to always turn into unicode.

You're right, it would be nice to be able to return a str in both Py2  
and Py3, which neither "return some_c_string" nor "return  
some_c_string.decode(...)" will do.

> 1) Passing unicode strings into a function that expects char* means  
> that
> some kind of encoding must happen and a new Python bytes object must  
> be
> created on the fly. The input object isn't a problem here as the  
> caller
> holds a reference to it anyway. The encoded object, however, must  
> have a
> lifetime. Looking at buffer arguments, I wouldn't mind if that was the
> lifetime of the function call itself. After all, it's the user's  
> choice to
> use char* instead of str/bytes/unicode. So the case of a parameter  
> typed as
> char* is actually easy to handle from a memory POV, given that some  
> kind of
> automatic encoding is in place.
>
> 2) Automatic encoding for an assignment from unicode to char* is  
> tricky,
> because you can't easily make assumptions about the lifetime of the  
> unicode
> object itself. You could get away with a weak-ref mapping from unicode
> strings to their byte encoded representation. I think every other  
> attempt
> to keep track of the lifetime of the unicode object is futile in  
> current
> Cython. Think of code like this, which I would expect to work:
>
>    cdef unicode u = u"abcdefg"
>    cdef char* s1 = u
>    u2 = u
>    cdef char* s2 = u2
>    u = None
>    print u, u2, s1, s2
>
> So supporting automatic unicode->char* coercion on assignments is  
> really
> hard to do internally.

As Greg pointed out, this would have *exactly* the same semantics as  
we now have for

    cdef bytes b = b"abcdefg"
    cdef char* s1 = b
    b2 = b
    cdef char* s2 = b2
    b = None
    print b, b2, s1, s2

using the defenc slot.
>

> 3) The third case is the same for both sigs and assignments: automatic
> decoding of char* to unicode vs. instantiation of a bytes object,  
> i.e. the
> following should do The Right Thing:
>
>    cdef char* some_c_string = ...
>    some_python_name = some_c_string
>
> This would be heavily simplified if some_python_name was typed as  
> either
> bytes or unicode (the latter of which might fail due to decoding  
> errors),
> and even str would work if it did different things in Py2 and Py3  
> (with
> potential decoding errors only in Py3). However, that won't work for
> untyped return values of def functions.

This is not really about assignment, it's about coercion. If we declare

     some_python_name = some_c_string

to always have the same meaning as

     some_python_name = <typeof(some_python_name)>some_c_string

then the meaning of <bytes> some_c_string and <unicode> some_c_string  
are clear, and <object> some_c_string is the only ambiguity, and the  
directive would control what <object> some_c_string means.

> Given that users would likely want
> to use bytes in Py2 (for simple non-unicode strings) and unicode for  
> other
> strings in Py2 and all text strings in Py3, this isn't easy to handle
> automatically.

If a user wants to return a mixture of str/unicode in Py2, or bytes/ 
str in Py3, they're going to have to be explicit one way or another,  
whether or not this directive is used. (It just changes the default.)

> Now, the proposal was to enable this with a compiler directive,  
> which would
> basically provide a default encoding. If this directive was used, all
> untyped coercions from char* to a Python object would use it. As Dag  
> noted
> already, this would interfere with type inference, as the resulting  
> type
> would still be char* in that case.

This is completely orthogonal to type inference. Type inference  
happens before any coercions are inserted, and would work just as well  
with our without this proposal. It's a question about what kind of  
coercion to allow/insert when one is needed.

> The only exception are untyped function return values.
>
> For typed coercions to str or unicode, I personally don't think that  
> it's
> too much typing to require "c_string.decode(enc)", which would work  
> nicely
> with type inference. However, that would, again, not yield the
> do-what-I-mean result of returning a byte string in Py2. Arguably,  
> that
> might be considered an optimisation, but it could still fall under  
> the DWIM
> compiler directive, e.g. as an "return_bytes_in_py2" option.
>
>
> Ok, to sum things up, it looks like a special kind of coercion at  
> function
> call bounderies would be quite easy to support, and would work  
> nicely with
> type inference enabled. It would also match the support that CPython's
> C-API argument unpacking functions have for converting Python strings.
> Everything else would mean hard work inside of Cython and be rather  
> hard to
> explain to users.

Argument unpacking would be the most useful case. If defenc is  
actually going away, then I agree things could get messy, but  
otherwise it would be as easy (or hard) to explain and use as str ->  
char* is now.

> BTW, I wouldn't mind extending the string input argument conversion  
> support
> to everything that supports the buffer protocol.

That might be interesting, though one difficulty is that buffers in  
general don't have a intrinsic notion of length. (Technically, nor do  
strings, null terminated strings encoded with null-free encodings are  
common enough to make char* useable.)

- Robert

_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to