Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

long(4.2) now returns an int #48027

Closed
barry-scott mannequin opened this issue Sep 4, 2008 · 13 comments
Closed

long(4.2) now returns an int #48027

barry-scott mannequin opened this issue Sep 4, 2008 · 13 comments
Labels
release-blocker type-bug An unexpected behavior, bug, or error

Comments

@barry-scott
Copy link
Mannequin

barry-scott mannequin commented Sep 4, 2008

BPO 3777
Nosy @smontanaro, @barry-scott, @amauryfa, @benjaminp
Files
  • float_long.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2008-09-09.15:11:01.955>
    created_at = <Date 2008-09-04.20:49:16.788>
    labels = ['type-bug', 'release-blocker']
    title = 'long(4.2) now returns an int'
    updated_at = <Date 2008-09-09.15:45:26.526>
    user = 'https://github.com/barry-scott'

    bugs.python.org fields:

    activity = <Date 2008-09-09.15:45:26.526>
    actor = 'skip.montanaro'
    assignee = 'none'
    closed = True
    closed_date = <Date 2008-09-09.15:11:01.955>
    closer = 'amaury.forgeotdarc'
    components = ['None']
    creation = <Date 2008-09-04.20:49:16.788>
    creator = 'barry-scott'
    dependencies = []
    files = ['11410']
    hgrepos = []
    issue_num = 3777
    keywords = ['patch']
    message_count = 13.0
    messages = ['72519', '72529', '72671', '72681', '72685', '72702', '72718', '72738', '72798', '72829', '72860', '72869', '72874']
    nosy_count = 5.0
    nosy_names = ['skip.montanaro', 'barry-scott', 'amaury.forgeotdarc', 'jyasskin', 'benjamin.peterson']
    pr_nums = []
    priority = 'release blocker'
    resolution = 'fixed'
    stage = None
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue3777'
    versions = ['Python 2.6']

    @barry-scott
    Copy link
    Mannequin Author

    barry-scott mannequin commented Sep 4, 2008

    I am testing PySVN against python2.6b3.

    I see a failure when PyNumber_Long is called with a Float.
    It raises TypeError.

    The same code works on 2.3, 2.4 and 2.5.

    Looking with GDB I see:

    (gdb) bt
    #0 PyNumber_Long (o=0x1809384) at Objects/abstract.c:1735
    #1 0x020f8e70 in Py::Long::Long (this=0xbfffefc8, ob=@0xbfffefc0) at
    pysvn_client_cmd_list.cpp:739

    1681 m = o->ob_type->tp_as_number;
    1682 if (m && m->nb_long) { /* This should include subclasses
    of long */
    1683 /* Classic classes always take this branch. */
    1684 PyObject *res = m->nb_long(o);
    1685 if (res && (!PyInt_Check(res) &&
    !PyLong_Check(res))) {

    res does not contain the value that nb_long(o) calculated.
    and the if on 1685 is false so you get a type error.

    I have compiled on Mac OS X 10.4 powerpc and fedora 8 x86.
    Both fail in the exact same way.

    If you need to reproduce you will need to build pysvn and
    run a command that triggers the problem. I don't have a
    smaller example.

    @barry-scott barry-scott mannequin added the type-bug An unexpected behavior, bug, or error label Sep 4, 2008
    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 4, 2008

    Note to others: PySvn uses the PyCXX classes. The call in question is
    something similar to
    Py::Long( Py::Float( double( someValue ) ) )

    Barry, what is the exact error message that you get?
    What do you mean by "res does not contain the value that nb_long(o)
    calculated" ?
    And what is the exact content of the "o" variable at the time of the
    failure? (you could look for example at o->ob_type->tp_name)

    @barry-scott
    Copy link
    Mannequin Author

    barry-scott mannequin commented Sep 6, 2008

    You are right that its the Py::Long( Py::Float( double( x ) ) )
    that is triggering this problem.

    Here is the gdb from the powerpc build that shows the
    info you asked for and show res being corrupt.

    I'm going to try and build a smaller version of this
    problem using a minimal PyCXX module.

    >> import pysvn;pysvn.Client().ls('pysvn/init.py')

    Breakpoint 1, bp () at pysvn_client_cmd_list.cpp:33
    33 }
    (gdb) c
    Continuing.
    Current language: auto; currently c++

    Breakpoint 2, pysvn_client::cmd_ls (this=0x1114830, a_args=@0xbffff118,
    a_kws=@0xbffff120) at pysvn_client_cmd_list.cpp:138
    138 Py::Long l_tmp( f_tmp );
    (gdb) b PyNumber_Long
    Breakpoint 5 at 0x213cfc: file Objects/abstract.c, line 1673.
    (gdb) c
    Continuing.

    Breakpoint 5, PyNumber_Long (o=0x1809384) at Objects/abstract.c:1673
    1673 if (trunc_name == NULL) {
    (gdb) p o
    $25 = (PyObject *) 0x1809384
    Current language: auto; currently c
    (gdb) p *o
    $26 = {
    ob_refcnt = 1,
    ob_type = 0x35723c
    }
    (gdb) p *o->ob_type
    $27 = {
    ob_refcnt = 4,
    ob_type = 0x35f0bc,
    ob_size = 0,
    tp_name = 0x339d98 "float",
    tp_basicsize = 16,
    tp_itemsize = 0,
    tp_dealloc = 0x23de30 <float_dealloc>,
    tp_print = 0x23fa90 <float_print>,
    tp_getattr = 0,
    tp_setattr = 0,
    tp_compare = 0,
    tp_repr = 0x23fa50 <float_repr>,
    tp_as_number = 0x357ab4,
    tp_as_sequence = 0x0,
    tp_as_mapping = 0x0,
    tp_hash = 0x23e570 <float_hash>,
    tp_call = 0,
    tp_str = 0x23fa10 <float_str>,
    tp_getattro = 0x263800 <PyObject_GenericGetAttr>,
    tp_setattro = 0,
    tp_as_buffer = 0x0,
    tp_flags = 394747,
    tp_doc = 0x357a4c "float(x) -> floating point number\n\nConvert a
    string or number to a floating point number, if possible.",
    tp_traverse = 0,
    tp_clear = 0,
    tp_richcompare = 0x23e000 <float_richcompare>,
    tp_weaklistoffset = 0,
    tp_iter = 0,
    tp_iternext = 0,
    tp_methods = 0x35733c,
    tp_members = 0x0,
    tp_getset = 0x357300,
    tp_base = 0x0,
    tp_dict = 0x0,
    tp_descr_get = 0,
    tp_descr_set = 0,
    tp_dictoffset = 0,
    tp_init = 0,
    tp_alloc = 0,
    tp_new = 0x241a10 <float_new>,
    tp_free = 0,
    tp_is_gc = 0,
    tp_bases = 0x0,
    tp_mro = 0x0,
    tp_cache = 0x0,
    tp_subclasses = 0x0,
    tp_weaklist = 0x0,
    tp_del = 0,
    tp_version_tag = 0
    }
    (gdb) n
    1679 if (o == NULL)
    (gdb)
    1681 m = o->ob_type->tp_as_number;
    (gdb)
    1682 if (m && m->nb_long) { /* This should include subclasses
    of long */
    (gdb) p *m
    $28 = {
    nb_add = 0x242980 <float_add>,
    nb_subtract = 0x242c40 <float_sub>,
    nb_multiply = 0x242f00 <float_mul>,
    nb_divide = 0x2431c0 <float_classic_div>,
    nb_remainder = 0x243510 <float_rem>,
    nb_divmod = 0x243830 <float_divmod>,
    nb_power = 0x243bf0 <float_pow>,
    nb_negative = 0x241d70 <float_neg>,
    nb_positive = 0x241b60 <float_float>,
    nb_absolute = 0x241e70 <float_abs>,
    nb_nonzero = 0x23e580 <float_nonzero>,
    nb_invert = 0,
    nb_lshift = 0,
    nb_rshift = 0,
    nb_and = 0,
    nb_xor = 0,
    nb_or = 0,
    nb_coerce = 0x241f70 <float_coerce>,
    nb_int = 0x23e6e0 <float_trunc>,
    nb_long = 0x23e6e0 <float_trunc>,
    nb_float = 0x241b60 <float_float>,
    nb_oct = 0,
    nb_hex = 0,
    nb_inplace_add = 0,
    nb_inplace_subtract = 0,
    nb_inplace_multiply = 0,
    nb_inplace_divide = 0,
    nb_inplace_remainder = 0,
    nb_inplace_power = 0,
    nb_inplace_lshift = 0,
    nb_inplace_rshift = 0,
    nb_inplace_and = 0,
    nb_inplace_xor = 0,
    nb_inplace_or = 0,
    nb_floor_divide = 0x243b60 <float_floor_div>,
    nb_true_divide = 0x244340 <float_div>,
    nb_inplace_floor_divide = 0,
    nb_inplace_true_divide = 0,
    nb_index = 0
    }
    (gdb) n

    Breakpoint 3, PyNumber_Long (o=0x1809384) at Objects/abstract.c:1684
    1684 PyObject *res = m->nb_long(o);
    (gdb) s
    float_trunc (v=0x1809384) at Objects/floatobject.c:1084
    1084 double x = PyFloat_AsDouble(v);
    (gdb) p o
    $29 = (PyObject *) 0x1809384
    (gdb) p v
    $30 = (PyObject *) 0x1809384
    (gdb) n
    1087 (void)modf(x, &wholepart);
    (gdb) p x
    $31 = 0
    (gdb) n
    1100 if (LONG_MIN < wholepart && wholepart < LONG_MAX) {
    (gdb) p wholepart
    $32 = 4555
    (gdb) n
    1102 return PyInt_FromLong(aslong);
    (gdb) p asLong
    No symbol "asLong" in current context.
    (gdb) p aslong
    No symbol "aslong" in current context.
    (gdb) n
    1105 }
    (gdb) n
    PyNumber_Long (o=0x1809384) at Objects/abstract.c:1685
    1685 if (res && (!PyInt_Check(res) &&
    !PyLong_Check(res))) {
    (gdb) p res
    $33 = (PyObject *) 0x383cf0
    (gdb) p *res
    $34 = {
    ob_refcnt = 1362084,
    ob_type = 0x140
    }
    (gdb) n

    Breakpoint 4, PyNumber_Long (o=0x1809384) at Objects/abstract.c:1735
    1735 }
    (gdb)

    @barry-scott
    Copy link
    Mannequin Author

    barry-scott mannequin commented Sep 6, 2008

    O.k. I know what is going on.

    Here is the description from abstracts.h for PyNumber_Long:

         PyAPI_FUNC(PyObject *) PyNumber_Long(PyObject *o);
       /*
     Returns the o converted to a long integer object on success,
     or NULL on failure.  This is the equivalent of the Python
     expression: long(o).
    
       */
    

    Its says that I can expect a long integer. However PyNumber_long
    can return an int or a long.

    PyCXX checks for a long, but an int is not a long and I raise
    a type error.

    This is a contract break on the Python API.

    The change that causes this break is in floatobject.c
    From 2.5.2 code:

    static PyNumberMethods float_as_number = {
    ...
    float_int, /nb_int/
    float_long, /nb_long/

    From 2.6b3 code:

    static PyNumberMethods float_as_number = {
    ...
    float_trunc, /nb_int/
    float_trunc, /nb_long/

    float_trunc returns either an int or a long.
    Which is not what is required for the API.

    Here is the same bug at the pure python level.

     $ python2.6
    Python 2.6b3 (r26b3:65922, Aug 25 2008, 15:44:46) 
    [GCC 4.0.1 (Apple Computer, Inc. build 5370)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> long(4)
    4L
    >>> long(4.3)
    4
    >>> long("6")
    6L
    >>> type(long(4.3))
    <type 'int'>
    >>> 

    Barry

    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 6, 2008

    You are right: long(4.2) used to return a long.

    This was changed by the introduction of the float_trunc() function,
    which is now used for float.__trunc__, float.__int__ and float.__long__.

    OTOH, long() has always been allowed to return an int. Checked with
    python2.2:
    >>> class C:
    ...   def __long__(self): return 4
    ...
    >>> type(long(C()))
    <type 'int'>

    I suggest that:

    • your code should be more tolerant, specially when calling API
      functions from the "Abstract Objects Layer", accept both longs and ints.
    • Concerning long(float()), the new behavior breaks existing code, and
      should be reverted. I will try to come with a patch.

    @amauryfa amauryfa changed the title PyNumber_Long fails from Float long(4.2) now returns an int Sep 6, 2008
    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 6, 2008

    Here is a patch that restores the float->long conversion. The function
    was simply copied from 2.5.

    @barry-scott
    Copy link
    Mannequin Author

    barry-scott mannequin commented Sep 6, 2008

    My concern and yours is that this is not backwards
    compatible. I would hate to see "random" failures
    of extensions written using PyCXX because of this.

    I'm tempted to says that I'll keep PyCXX 5.x as is for
    Python 2.x and leave all the changes in semantics
    for PyCXX 6.0 that will support Python 3.0.
    And in Python 3.0 this problem does not exist
    by design.

    I don't think you example proves anything.
    Python does not check at the pure python level at all.

    >>> class X:
    ...     def __long__( self ):
    ...             return "Hello"
    ... 
    >>> long( X() )
    'Hello'
    >>> 

    You get all you deserve if you define __long__ and break its
    API.

    @benjaminp
    Copy link
    Contributor

    Jeffery, you made this change in r59671. Can you comment?

    @benjaminp
    Copy link
    Contributor

    In the meantime, Amaury, you patch is good.

    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 9, 2008

    Applied as r66332

    @smontanaro
    Copy link
    Contributor

    Just a quick nit, but it seems to me that since 2.6 still actually
    distinguishes between longs and ints that the two error messages in
    PyLong_FromFloat should mention "long" instead of "integer".

    Skip

    @amauryfa
    Copy link
    Member

    amauryfa commented Sep 9, 2008

    I suppose you meant PyLong_FromDouble()? I think the messages talk about
    abstract numbers, not a specific python type: "infinity/NaN cannot be
    converted to an integral value".

    @amauryfa amauryfa closed this as completed Sep 9, 2008
    @smontanaro
    Copy link
    Contributor

    Amaury> I suppose you meant PyLong_FromDouble()?

    Yes, sorry.

    Amaury> I think the messages talk about abstract numbers, not a specific
    Amaury> python type: "infinity/NaN cannot be converted to an integral
    Amaury> value".
    

    Well, 2.5 called it "long".

    Skip

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    release-blocker type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants