Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
kwargs question
#1
Hi, I am really new to python and wishing I had gotten into it a lot earlier than I did. I have advanced considerably with my learning and have been using tkinter as a UI for its ease of use. Currently I am reading up on classes and using what I have learnt to build a custom widget. I am struggling to understand and how to implement **kwargs. Below is code I have found on a web site, it says I can pass in as many options as I want but did not give an example, can someone give me an example of how I would pass in perhaps some text for a canvas and/or a color for a canvas backgroud.

class ClassName(Tk.Frame):
	def __init__( self, parent, **options ):
		Tk.Frame.__init__( self, parent, **options ) 
T.I.A.

Jeff
Reply
#2
A basic example
import tkinter as tk

root = tk.Tk()
options = {'bg': 'tan'}
text_options = {'text': 'This is some text', 'fill': 'purple', 'font':('times 16 bold')}
canvas = tk.Canvas(root, **options)
canvas.create_text(200, 50, **text_options)
canvas.pack()
root.mainloop()
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply
#3
Thank you for such a rapid response, are the asterisk similar to a C pointer ?

I put a sample into my own code and it worked perfectly but I feel because I am not fully understanding I am just hard coding the values.

Below worked great

class ClassName(Tk.Frame):
	def __init__( self, parent, **options ):
		Tk.Frame.__init__( self, parent, **options ) 
		options = {'bg': 'tan'}
		self.parent=parent
		
		self.canvas=Canvas(self,width=300,height=300,**options)
		self.canvas.grid(row=0,column=0)
In my main class I would like to pass the options when I create the instance of widget_1 and widget_2 from ClassName

class Master_Gui():

	def __init__(self):
		self.root=Tk()
		self.root.title("Widget Control")
		self.root.resizable(False,False)
		self.display_widgets(self.root)

    def display_widgets(self,root):
		self.widget_1=ClassName(self.root) #pass in the bg color ??
		self.widget_1.grid(row=0,column=0)
		self.widget_2=ClassName(self.root) #pass in the bg color ??
		self.widget_2.grid(row=0,column=1)
Sorry if I am slow to catch on but once I see it then it will be there forever.

Jeff
Reply
#4
In this expression "func" is the name of the function, a is a positional argument, args is a tuple of positional arguments, c is a keyword argument and kwargs is a dictionary of keyword arguments.
def func(a, *args, c=None, **kwargs):
    print("a", a)
    print("args", args)
    print("c", c)
    print("kwargs", kwargs)

func("Hello", "world", c="!", French="salut tout le monde")
Output:
a Hello args ('world',) c ! kwargs {'French': 'salut tout le monde'}
**kwargs is usually used to pass unused keyword arguments to the superclass __init__.
class A():
    def __init__(self, x=None, **kwargs):
        print("x =", x, "kwargs", kwargs)

class B(A):
    def __init__(self, y=None, **kwargs):
        super().__init__(**kwargs)
        print("y =", y, "kwargs", kwargs)

item = B(x=3.14, y=42)
Output:
x = 3.14 kwargs {} y = 42 kwargs {'x': 3.14}
B.__init__() pops "y" out of the keyword dictionary and passes it along to A.__init__(). The popping happens automatically because "y" is a keyward argument in A.__init__(). Here's an example that explicitly pops "z" out of kwargs before passing along to B.__init__().
class A():
    def __init__(self, x=None, **kwargs):
        print("x =", x, "kwargs", kwargs)

class B(A):
    def __init__(self, y=None, **kwargs):
        z = kwargs.pop("z")
        super().__init__(**kwargs)
        print("y =", y, "z=", z, "kwargs", kwargs)

item = B(x=3.14, y=42, z=1.618)
Output:
x = 3.14 kwargs {} y = 42 z= 1.618 kwargs {'x': 3.14}
The explicit remove of key/value pairs from kwargs is unusual, but it works just fine. Anything you can do with a dictionary you can do with kwargs. In this example A.__init__() does some processing on a keyword argument and passes it along.
class A():
    def __init__(self, x=None, **kwargs):
        print("x =", x, "kwargs", kwargs)

class B(A):
    def __init__(self, y=None, **kwargs):
        z = kwargs["z"]=round(kwargs["z"])
        super().__init__(**kwargs)
        print("y =", y, "kwargs", kwargs)

item = B(x=3.14, y=42, z=1.618)
Output:
x = 3.14 kwargs {'z': 2} y = 42 kwargs {'x': 3.14, 'z': 2}
This would normally be done by declaring a keyword argument in A.__init__()
class A():
    def __init__(self, x=None, **kwargs):
        print("x =", x, "kwargs", kwargs)

class B(A):
    def __init__(self, y=None, z=0, **kwargs):
        super().__init__(z=round(z), **kwargs)
        print("y =", y, "kwargs", kwargs)

item = B(x=3.14, y=42, z=1.618)
kwargs is really a dictionary. For proof, here is a function that creates a dictionary using kwargs.
def dictionary(**kwargs):
    return kwargs

constants = dictionary(pi=3.1416, Golden_ratio=1.618, Ultimate_answer=42)
print(constants)
print("pi =", constants["pi"])
Output:
{'pi': 3.1416, 'Golden_ratio': 1.618, 'Ultimate_answer': 42} pi = 3.1416
As to your question about how you could pass a bunch of options as arguments, you shouldn't. Functions should have few arguments. If your function has 5 or more arguments you should step back and review. "Is this the best way for me to do this?" "Are all these arguments confusing?" "Should all these arguments be attributes of my object, and should I set them independently of each other?

As to how you can pass a few values using keyword arguments, that is simple: keyward=value. If you want to configure a canvas object to have a blue background:
canvas = tk.Canvas(parent, bg="blue", width=200, height=150)
The argument/option names are described in the documentation of the Canvas class.

https://tkinter-docs.readthedocs.io/en/l...anvas.html
In this context "*" is the unpacking operator. *list unpacks the values from a list.
args = (1, 2, 3, 4)
print("args", args)
print("*args", *args)
a, b, c, d = args
print("a, b, c, d", a, b, c, d)
Output:
args (1, 2, 3, 4) *args 1 2 3 4 a, b, c, d 1 2 3 4
args is a tuple. That is why print surrounds the values by parenthesis when printing(args). *args unpacks the values returning the numbers 1, 2, 3, 4. When you print *args, parenthesis do not appear in the output. You can also unpacking during assignment. "a, b, c, d = args" unpacks the values of args into the variables a, b, c, d.

**kwargs unpacks a dictionary.
def myprint(one, two, three):
    print("**kwargs", one, two, three)

kwargs = {"one":1, "two":2, "three":3}
print("kwargs", kwargs)
print("*kwargs", *kwargs)
myprint(**kwargs)
Output:
kwargs {'one': 1, 'two': 2, 'three': 3} *kwargs one two three **kwargs 1 2 3
I could not "print(**kwargs)" because unpacking kwargs produces:
print(one=1, two=2, three=3)
The print function does not have keyword arguments one, two and three, so it raises an InvalidKeyword argument. To get around the problem I created a print function with the appropriate arguments.
Reply
#5
Wow that is so much to take in, I am going to have to ingest this over a couple of days. Thank you so much for taking the time to write all of that.

Basically I was wanting a widget in a self contained file that was shareable and user configurable, I will revisit what I have and look for better methods of configuration. Perhaps split the widget into smaller classes?

I know what a dictionary is but I'm not going to comment any further until I can come back understanding what you have told me Smile

thanks again
Jeff
Reply
#6
I think this is a bad idea:
class ClassName(Tk.Frame):
    def __init__( self, parent, **options ):
        Tk.Frame.__init__( self, parent, **options ) 
        options = {'bg': 'tan'}
        self.parent=parent
         
        self.canvas=Canvas(self,width=300,height=300,**options)
        self.canvas.grid(row=0,column=0)
The appropriate way to do this is.
import tkinter as tk

class ClassName(tk.Frame):
    def __init__(self, parent, canvas_bg=None, **kwargs):
        super().__init__(self, parent, **kwargs) 
        self.canvas=tk.Canvas(self, width=300, height=300, bg=canvas_bg)
        self.canvas.grid(row=0,column=0)

thing = ClassName(self, canvas_bg="tan", bg="yellow")
Even if you end up with dozens of keyword arguments it is far better to expose them in the argument list than to hide them in and options dictionary.
Reply
#7
Hi Jeff,

(Feb-15-2022, 08:38 PM)Jeff_t Wrote: are the asterisk similar to a C pointer ?

No, it is nothing like C pointers.

The asterisk in Python gets used for many things, but in this case what you are interested in is called argument unpacking. There are two flavours of argument unpacking, one for positional arguments, and one for keyword arguments.

If you have a tuple or other list of values, and you try to pass them to a function:

values = (10, 20, 30)
result = function(values)
that passes the entire tuple as a single argument. Sometimes that is what you want, but sometimes what you want is to split the tuple into each individual value, and pass them as four separate arguments:

result = function(values[0], values[1], values[2], values[3])
That works, but it is painful to type out, and what if you don't know how many values there are? This is where unpacking is useful: we can tell Python to unpack the individual values from the tuple or list and pass them one by one as separate arguments:

# like function(values[0], values[1], ... ) as shown above
result = function(*values)
Much shorter to type and easier to use, and (probably?) faster too.

Okay, so that's positional argument unpacking. How about keyword unpacking? Now we need a dict with the arguments listed by name and value:

kwargs = {'direction': 'north', 'speed': 42, 'name': 'George'}
result = function(**kwargs)
# unpacks the dict and passes the key:value pairs as keyword arguments
# like function(direction='north', speed=42, name='George')
So the single asterisk * and the double asterisk ** get used for related, but opposite, things in function definitions and function calls.

In a function definition, they are used to collect (pack) extra arguments, and in a function call they are used to insert (unpack) arguments.

def myfunction(name, address, *extra, **more_keyword_args):
    print("name is", name)
    print("address is", address)
    print("extra is", extra)
    print("more_keyword_args is", more_keyword_args)

args = ('Natasha', 'Moscow', 'green', 99)
kwargs = {'partner': 'Boris', 'food': 'bacon and eggs'}
myfunction(*args, **kwargs)
You should copy and paste that code into the interpreter, try to guess what it will do, and then run the code to see if you are right.
Reply
#8
Another problem is that code that you use is old.
Using super().__init__() it will inherits all that's needed and don't need set **kwargs yourself in the class.
class ClassName(Tk.Frame):
    def __init__( self, parent, **options ):
        Tk.Frame.__init__( self, parent, **options ) 
Become this.
import tkinter as tk

class App(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
If look at Doc here
So can test this work be adding this.
import tkinter as tk

class App(tk.Frame):
    def __init__(self, master):
        super().__init__(master, background='lightblue', height=200, width=300)
Maybe not the best way this either look at TKINTER OOP,there you see serval updated and good examples.
Tkinter Object-Oriented Wrote:Summary
  • Use an object-oriented programming approach to make the code more organized.
  • Define a class that inherits from the tk.Tk class.
  • Always, call the super().__init__() from the parent class in the child class.
A problem is that there are many old and rather bad Tkinter tutorials out there.
I don't use Tkinter often myself,but can spot when there is something old and not updated in the approach.
Reply
#9
First I have to tell you guys this is a self teaching excercise, a hobby and always will be a hobby. Having said that I like things to be right.

The responses I have from you four are pretty deep but interesting, if I take it one piece at a time.

snippsat you are absolutely right, the code I have been using is old, it is a problem with python because it has been around for a long time and python has evolved considerably. The example I am working from is at least 7 years old but it is one of the few I was able to google for my purpose.

deanhystad was the first to mention super() which is something else I want to get my head around but the example did not work with my code, the example of super from sippsat did, that made me happy but I still need to find out what the subtle difference is. The error I got at first was 'ClassName' has no attribute 'tk'. At the moment I have left the super().__init__(master ...... in my widget class.

stevendaprano, I think you know my brain capacity, your post was easy for me to understand and I have yet to take the quiz at the end but be assured I will, and I also learnt about unpacking a little.

Maybe *args and **kwargs are not as important in this moment of time as I thought but you guys have certainly highlighted the use of super.

ty
Jeff
BashBedlam likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How do I instantiate a class with **kwargs? palladium 6 7,428 Feb-16-2023, 06:10 PM
Last Post: deanhystad
  Misunderstanding kwargs? Donovan 2 2,285 Aug-04-2020, 08:09 PM
Last Post: Donovan
  **kwargs question DPaul 10 4,387 Apr-01-2020, 07:52 AM
Last Post: buran
  Unpacking dictionary from .xlsx as Kwargs etjkai 5 2,865 Dec-27-2019, 05:31 PM
Last Post: etjkai
  opts vs kwargs Skaperen 4 2,473 Nov-30-2019, 04:57 AM
Last Post: Skaperen
  create dictionary from **kwargs that include tuple bluefrog 2 4,889 Oct-26-2016, 10:24 PM
Last Post: Larz60+
  How do you use *args and **kwargs? Gengar 3 19,750 Sep-20-2016, 04:22 PM
Last Post: Ofnuts

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020