Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python's IDLE behavior while defining fractional default values to function parameters

Tags:

python

I've defined a function that receives an optional parameter, using a fractional default value:

def foo(x=0.1):
    pass

Now when typing foo( in the IDLE shell, the tool-tip that pops out to help me complete the call reads (x=0<tuple>), instead of the expected (x=0.1). I've never encountered this before, though I find it hard to believe I haven't used any function/method with a fractional default values.

Assuming it's a feature, rather than a bug, I'd be glad if someone can explain why it's happening. I'm using python 2.7.5 64-bit on Windows 7.

EDIT:

From the comments, it does not seem to be a feature. I've checked different function definitions, by 2rs2ts's suggestion, and found every appearance of a decimal point I've tried to be replaced in the tool-tip. So this definition -

def foo(x=[(1,0.1), 2, .3]):
    pass

produces the tool-tip (x=[(1, 0<tuple>), 2, 0<tuple>]).

Should I close this question and submit a bug report instead?

like image 700
Ariel Avatar asked Jun 11 '13 20:06

Ariel


2 Answers

This is a strange answer and finding it felt a bit like a wild goose chase...

I did not see the issue posted at bugs.python.org, but after some poking around, I found the CallTips.py file in Python 2.6.6 and I saw the potentially offending line of code. By scrolling down to line 161 in the get_arg_text() method, I saw

arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", arg_text)

This looked just like what you posted in your question and indeed if arg_text is a float converted to a string, that line returns the <tuple> string:

arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", "9.0")    # returns (9<tuple>)

However, the issue must have been fixed at svn.python.org/.../Calltips.py (during PEP 384?) since that version does not have the line above. In fact, get_arg_text() was replaced by get_argspec().

So the answer would seem to be it was fixed during PEP 384. Based on the comments to your question, Python 3.3's IDLE has this fix but as you point out, Python 2.7.5 does not. For comparison, I pasted the two methods below so that someone might be able to explain exactly how PEP 384 fixed the issue you saw.

Hope it helps.

Just for reference:

Older versions of Calltips.py has get_arg_text(ob) (at least as recently as the 2.7.5 build you are using). For example, it is located at /Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/idlelib/CallTips.py on a Mac. The function was defined as follows:

def get_arg_text(ob):
    """Get a string describing the arguments for the given object"""
    arg_text = ""
    if ob is not None:
        arg_offset = 0
        if type(ob) in (types.ClassType, types.TypeType):
            # Look for the highest __init__ in the class chain.
            fob = _find_constructor(ob)
            if fob is None:
                fob = lambda: None
            else:
                arg_offset = 1
        elif type(ob)==types.MethodType:
            # bit of a hack for methods - turn it into a function
            # but we drop the "self" param.
            fob = ob.im_func
            arg_offset = 1
        else:
            fob = ob
        # Try to build one for Python defined functions
        if type(fob) in [types.FunctionType, types.LambdaType]:  # <- differs here!
            argcount = fob.func_code.co_argcount
            real_args = fob.func_code.co_varnames[arg_offset:argcount]
            defaults = fob.func_defaults or []
            defaults = list(map(lambda name: "=%s" % repr(name), defaults))
            defaults = [""] * (len(real_args) - len(defaults)) + defaults
            items = map(lambda arg, dflt: arg + dflt, real_args, defaults)
            if fob.func_code.co_flags & 0x4:
                items.append("...")
            if fob.func_code.co_flags & 0x8:
                items.append("***")
            arg_text = ", ".join(items)
            arg_text = "(%s)" % re.sub("\.\d+", "<tuple>", arg_text)
        # See if we can use the docstring
        doc = getattr(ob, "__doc__", "")
        if doc:
            doc = doc.lstrip()
            pos = doc.find("\n")
            if pos < 0 or pos > 70:
                pos = 70
            if arg_text:
                arg_text += "\n"
            arg_text += doc[:pos]
    return arg_text

The corresponding function located at svn.python.org/.../Calltips.py seems to have fixed a bug. The method was renamed to get_argspec:

def get_argspec(ob):
    """Get a string describing the arguments for the given object."""
    argspec = ""
    if ob is not None:
        if isinstance(ob, type):
            fob = _find_constructor(ob)
            if fob is None:
                fob = lambda: None
        elif isinstance(ob, types.MethodType):
            fob = ob.__func__
        else:
            fob = ob
        if isinstance(fob, (types.FunctionType, types.LambdaType)):
            argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
            pat = re.compile('self\,?\s*')
            argspec = pat.sub("", argspec)
        doc = getattr(ob, "__doc__", "")
        if doc:
            doc = doc.lstrip()
            pos = doc.find("\n")
            if pos < 0 or pos > 70:
                pos = 70
            if argspec:
                argspec += "\n"
            argspec += doc[:pos]
    return argspec
like image 127
gary Avatar answered Nov 11 '22 03:11

gary


Thanks to Gary's and Ariel's detective work, I was able to fix this by adding the negative lookbehind assertion "(?<!\d)" to the beginning of the replacement re. The re now matches, for instance, '.0', the funny 'name' for the first parameter tuple, which never follows a digit, but not match '0.0', the string representation of a float, which always begins with a digit. CPython tracker issue 18539

like image 24
Terry Jan Reedy Avatar answered Nov 11 '22 03:11

Terry Jan Reedy