Nameless
Nameless

Reputation: 403

eval() takes no keyword argument

Code:

import tkinter as tk

win = tk.Tk()

text = tk.Text(win, width = 50, height = 20, bg = 'black', fg = 'lightgreen', wrap = tk.WORD)
text.pack()

text.tag_configure('prompt', foreground = 'magenta')
text.tag_configure('output', foreground = 'yellow')

text.insert('end', '>>> ', 'prompt')

def on_return(*args):
  cmd = text.get('prompt.last', 'end')
  if cmd:
     try:
       output = str(eval(cmd, globals = {"__builtins__":None}))
     except Exception as e:
       output = str(e)
  text.insert('end', '\n' + output, 'output')
  text.insert('end', '\n>>> ' + output, 'prompt')
  return 'break'

text.bind('<Return>', on_return)

win.mainloop()

In order to get this output: eval() takes no keyword argument, just type anything on the text widget and press enter.

What should I do to avoid this output?

Upvotes: 1

Views: 2296

Answers (2)

Mark Peschel
Mark Peschel

Reputation: 324

By default, when you a declare a function, the arguments can be passed by name or by keyword. However, it is possible to declare a function so that positional or keyword passing is disallowed. This is used by many built-in functions such as eval and range to reduce the call-time, to allow the library owner to freely rename parameters, and to forbid weird invocation order. See PEP 570 for more.

To call these functions, you must give the arguments in the correct order. If you want to only give eval local variables, not global, just pass the default value for global: eval(expression, None, your_locals).

You can declare such a function by putting a forward slash / and an asterisk * in the argument list. Arguments before the slash are positional only, arguments between the slash and the asterisk are keyword-or-positional, and arguments after the asterisk are keyword only.

For example, to get behavior similar to eval, you would declare:

def eval2(expression, globals=None, locals=None, /):
    pass

eval2 accepts only positional arguments. If you were to invoke it with a keyword, you would get an exception:

eval2('print("hello", location)', locals={'location':'world'})
...
TypeError: eval2() got some positional-only arguments passed as keyword arguments: 'locals'

You can also declare a function that takes a mix of positional-only, normal, and keyword-only arguments:

def example_function(positional1, positional2, /, keyword_or_positional1, keyword_or_positional2, *, keyword1, keyword2):
    pass

Incidentally, this is also why you see slashes in some signatures in Python's help utility:

help(hasattr)
...
hasattr(obj, name, /)

Because obj and name are before a slash, they cannot be passed as keyword arguments. For some reason, the slash is not included in the HTML documentation unless there are normal arguments as well as positional-only.

Upvotes: 1

LhasaDad
LhasaDad

Reputation: 2143

In your code you need to remove the 'globals =' from the parameter passing in the dictionary. The correct line is:

output = str(eval(cmd, {"__builtins__":None}))

Upvotes: 3

Related Questions