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?
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With