24 Jan 2020

feedPlanet Python

Peter Bengtsson: How to pad/fill a string by a variable in Python using f-strings

I often find myself Googling for this. Always a little bit embarrassed that I can't remember the incantation (syntax).

Suppose you have a string mystr that you want to fill with with spaces so it's 10 characters wide:

>>> mystr = 'peter'
>>> mystr.ljust(10)
'peter     '
>>> mystr.rjust(10)
'     peter'

Now, with "f-strings" you do:

>>> mystr = 'peter'
>>> f'{mystr:<10}'
'peter     '
>>> f'{mystr:>10}'
'     peter'

What also trips me up is, suppose that the number 10 is variable. I.e. it's not hardcoded into the f-string but a variable from somewhere else. Here's how you do it:

>>> width = 10
>>> f'{mystr:<{width}}'
'peter     '
>>> f'{mystr:>{width}}'
'     peter'

What I haven't figured out yet, is how you specify a different character than a simple single whitespace. I.e. does anybody know how to do this, but with f-strings:

>>> width = 10
>>> mystr.ljust(width, '*')
'peter*****'

24 Jan 2020 3:19am GMT

Peter Bengtsson: How to pad/fill a string by a variable in Python using f-strings

I often find myself Googling for this. Always a little bit embarrassed that I can't remember the incantation (syntax).

Suppose you have a string mystr that you want to fill with with spaces so it's 10 characters wide:

>>> mystr = 'peter'
>>> mystr.ljust(10)
'peter     '
>>> mystr.rjust(10)
'     peter'

Now, with "f-strings" you do:

>>> mystr = 'peter'
>>> f'{mystr:<10}'
'peter     '
>>> f'{mystr:>10}'
'     peter'

What also trips me up is, suppose that the number 10 is variable. I.e. it's not hardcoded into the f-string but a variable from somewhere else. Here's how you do it:

>>> width = 10
>>> f'{mystr:<{width}}'
'peter     '
>>> f'{mystr:>{width}}'
'     peter'

What I haven't figured out yet, is how you specify a different character than a simple single whitespace. I.e. does anybody know how to do this, but with f-strings:

>>> width = 10
>>> mystr.ljust(width, '*')
'peter*****'

24 Jan 2020 3:19am GMT

23 Jan 2020

feedPlanet Python

Erik Marsja: Rename Files in Python: A Guide with Examples using os.rename()

The post Rename Files in Python: A Guide with Examples using os.rename() appeared first on Erik Marsja.

In this post, we are going to work with Python 3 to rename files. Specifically, we will use the Python module os to rename a file and rename multiple files.

First, we will rename a single file in 4 easy steps. After that, we will learn how to rename multiple files using Python 3. To be able to change the name of multiple files using Python can come in handy. For example, if we have a bunch of data files (e.g., .csv files) with long, or strange names, we may want to rename them to make working with them easier later in our projects (e.g., when loading the CSV files into a Pandas dataframe).

Rename a file in Python using the os moduleExample of a folder with multiple files

How to Rename a File in Python

For instance, if we have a file called python-rename-files.txt we need to know where we have stored it. There are, of course, different ways to do this. Either, we can place the Python script in the same directory and just run it to rename the file. Here's a simple example on how to rename a file in Python:

import os
os.rename('python-rename-files.txt', 'renamed-file.txt')

4 Simple Steps to Rename a File in Python

In the first section, we are going to learn how to rename a single file in Python step-by-step. Now, the general procedure is similar when we are using Linux or Windows. However, how we go about in the first step to rename a file in Python may differ depending on which OS we use. In the renaming a file in Python examples below, we will learn how to carry on and changing names both in Linux and Windows.

1. Getting the File Path of the File we Want to Rename With Python

First, to get Python to rename a file Python needs to know where the file is located. That is, step 1 is finding the location of the file we want to change the name on.

That is, if we store our Python scripts (or Jupyter notebooks) in certain directories, we need to tell Python the complete path to the file we want to rename.

Finding the File Path in Windows

If we use Windows, we can open up the File Explorer. First, go to the folder where the file is located (e.g., "Files_To_Rename") and click on the file path (see image below) It should look something like "C:\Users\erima96\Documents\Files_To_Rename".

Rename a file with PythonHow to find the Path in Windows

Finding the File Path in Linux

Now, if we are to rename a file, using Python, in Linux we need to know the path. There are, of course, many methods to find the file path in Linux. One method is to open up a Terminal Window and use the readlink and xclip command-line applications:

readlink -f FILE_AND PATH | xclip -i

For instance, if the file we want to rename with Python is located in the folder "/home/jhf/Documents/Files_To_Rename/python-rename-files.txt" this is how we would do it:

Python renaming files in LinuxCopying a file path using command-line tools

2. Copy the Path of the File to Rename

Second, we copy the path of the file we want to rename. Note, this is as simple as just right-clicking on the path we already have marked:

Copy the file path for the file to rename in PythonCopy the file path

3. Importing the os Module

Third, we need to import a Python module called os. Luckily, this is very easy and we just type: import os. In the next step, we will actually rename a file using Python!

4. Renaming the File

Finally, we are ready to change the name of the file using the os module from Python. As the file path can be long we will create a string variable, fist, and then rename the file using os.rename. Here's how to rename a file using Python:os.rename(PATH_TO_THE_FILE, PATH_TO_THE_NEW_FILE)

Changing a Name of a File in Windows

Here's a full code example, for Windows users, on how to change the name of a file using Python:

import os
file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\'
os.rename(file_path + 'renamed-file.txt', 
          file_path + 'python-rename-files.txt')

Note, how we added an extra "\" to each subfolder (and at the end). In the os.rename method we also added the file_path to both the file we want to rename and the new file name (the string variable with the path to the file) and added the file name using '+'. If we don't do this the file will also be moved to the folder of the Python script. If we run the above code, we can see that we have successfully renamed the file using Python:

Learn how to rename a file - the renamed fileRenamed file

Renaming a File in Linux

Here's how to rename the file in Linux. Note, the file path will be a bit different (as we saw earlier when we found the file path):

import os
file_path = '/home/jhf/Documents/Files_To_Rename/python-rename-files.txt'
os.rename(file_path + 'renamed-file.txt', 
          file_path + 'python-rename-files.txt')

Now that we have learned how to rename a file in Python we are going to continue with an example in which we change the name of multiple files. As previously mentioned, renaming multiple files can come in handy if we have many files that need new names.

How to Rename Multiple Files in Python

In this section, we are going to learn how to rename multiple files in Python. There are, of course, several ways to do this. One method could be to create a Python list with all the file names that we want to change. However, if we have many files this is not an optimal way to change the name of multiple files. Thus, we are going to use another method from the os module; listdir. Furthermore, we will also use the filter method from the module called fnmatch.

renaming multiple files in python

The first step, before renaming the files, is to create a list of all text files in a folder (e.g., in the subfolder "Many_Files"). Note, that we have added the subfolder to the file_path string.

import os, fnmatch

file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\Many_Files\\'

files_to_rename = fnmatch.filter(os.listdir(file_path), '*.txt')

print(files_to_rename)

Next, we are going to loop through each file name and rename them. In the example below, we are using the file_path string, from the code chunk above, because this is where the files are located. If we were to have the files we want to rename in a different folder, we'd change file_path to that folder. Furthermore, we are creating a new file name (e.g., new_name = 'new_file') and we use the enumerate method to give them a unique name. For example, the file 'euupbihlaqgk.txt' will be renamed 'Datafile1.txt'.

new_name = 'Datafile'

for i, file_name in enumerate(files_to_rename):
    new_file_name = new_name + str(i) + '.txt'
    
    os.rename(file_path + file_name, 
          file_path + new_file_name)

As can be seen in the rename a file in Python example above, we also used the str() method to change the data type of the integer variable called i to a string.

Remember, if we were using Linux we'd have to change the file_path so that it will find the files we want to rename.

Renaming Multiple Files by Replacing Characters in the File Name

Now, sometimes we have files that we want to replace certain characters from. In this final example, we are going to use the replace method to replace underscores ("_") from file names. This, of course, means that we need to rename the files.

Removing underscore from files in Python (i.e., renaming them)Files with underscores
import os, fnmatch

file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\Many_Files\\'

files_to_rename = fnmatch.filter(os.listdir(file_path), '*.txt')

for file_name in files_to_rename:    
    os.rename(file_path + file_name, 
          file_path + file_name.replace(' ', '-'))

Now, in the code above we have successfully replaced the "_" from the filenames using replace and then we renamed the files. Again, remember if we were to rename files in Linux the file_path string need be changed (e.g. " ").

Replaced underscores (done in Python)Underscores replaced

Conclusion: Renaming Files in Python

In this post, we have learned how to rename a single file, as well as multiple files, using Python. This was accomplished using the os and fnmatch modules. Finally, we learned how to replace underscores (and rename) files using Python.

The post Rename Files in Python: A Guide with Examples using os.rename() appeared first on Erik Marsja.

23 Jan 2020 8:40pm GMT

Erik Marsja: Rename Files in Python: A Guide with Examples using os.rename()

The post Rename Files in Python: A Guide with Examples using os.rename() appeared first on Erik Marsja.

In this post, we are going to work with Python 3 to rename files. Specifically, we will use the Python module os to rename a file and rename multiple files.

First, we will rename a single file in 4 easy steps. After that, we will learn how to rename multiple files using Python 3. To be able to change the name of multiple files using Python can come in handy. For example, if we have a bunch of data files (e.g., .csv files) with long, or strange names, we may want to rename them to make working with them easier later in our projects (e.g., when loading the CSV files into a Pandas dataframe).

Rename a file in Python using the os moduleExample of a folder with multiple files

How to Rename a File in Python

For instance, if we have a file called python-rename-files.txt we need to know where we have stored it. There are, of course, different ways to do this. Either, we can place the Python script in the same directory and just run it to rename the file. Here's a simple example on how to rename a file in Python:

import os
os.rename('python-rename-files.txt', 'renamed-file.txt')

4 Simple Steps to Rename a File in Python

In the first section, we are going to learn how to rename a single file in Python step-by-step. Now, the general procedure is similar when we are using Linux or Windows. However, how we go about in the first step to rename a file in Python may differ depending on which OS we use. In the renaming a file in Python examples below, we will learn how to carry on and changing names both in Linux and Windows.

1. Getting the File Path of the File we Want to Rename With Python

First, to get Python to rename a file Python needs to know where the file is located. That is, step 1 is finding the location of the file we want to change the name on.

That is, if we store our Python scripts (or Jupyter notebooks) in certain directories, we need to tell Python the complete path to the file we want to rename.

Finding the File Path in Windows

If we use Windows, we can open up the File Explorer. First, go to the folder where the file is located (e.g., "Files_To_Rename") and click on the file path (see image below) It should look something like "C:\Users\erima96\Documents\Files_To_Rename".

Rename a file with PythonHow to find the Path in Windows

Finding the File Path in Linux

Now, if we are to rename a file, using Python, in Linux we need to know the path. There are, of course, many methods to find the file path in Linux. One method is to open up a Terminal Window and use the readlink and xclip command-line applications:

readlink -f FILE_AND PATH | xclip -i

For instance, if the file we want to rename with Python is located in the folder "/home/jhf/Documents/Files_To_Rename/python-rename-files.txt" this is how we would do it:

Python renaming files in LinuxCopying a file path using command-line tools

2. Copy the Path of the File to Rename

Second, we copy the path of the file we want to rename. Note, this is as simple as just right-clicking on the path we already have marked:

Copy the file path for the file to rename in PythonCopy the file path

3. Importing the os Module

Third, we need to import a Python module called os. Luckily, this is very easy and we just type: import os. In the next step, we will actually rename a file using Python!

4. Renaming the File

Finally, we are ready to change the name of the file using the os module from Python. As the file path can be long we will create a string variable, fist, and then rename the file using os.rename. Here's how to rename a file using Python:os.rename(PATH_TO_THE_FILE, PATH_TO_THE_NEW_FILE)

Changing a Name of a File in Windows

Here's a full code example, for Windows users, on how to change the name of a file using Python:

import os
file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\'
os.rename(file_path + 'renamed-file.txt', 
          file_path + 'python-rename-files.txt')

Note, how we added an extra "\" to each subfolder (and at the end). In the os.rename method we also added the file_path to both the file we want to rename and the new file name (the string variable with the path to the file) and added the file name using '+'. If we don't do this the file will also be moved to the folder of the Python script. If we run the above code, we can see that we have successfully renamed the file using Python:

Learn how to rename a file - the renamed fileRenamed file

Renaming a File in Linux

Here's how to rename the file in Linux. Note, the file path will be a bit different (as we saw earlier when we found the file path):

import os
file_path = '/home/jhf/Documents/Files_To_Rename/python-rename-files.txt'
os.rename(file_path + 'renamed-file.txt', 
          file_path + 'python-rename-files.txt')

Now that we have learned how to rename a file in Python we are going to continue with an example in which we change the name of multiple files. As previously mentioned, renaming multiple files can come in handy if we have many files that need new names.

How to Rename Multiple Files in Python

In this section, we are going to learn how to rename multiple files in Python. There are, of course, several ways to do this. One method could be to create a Python list with all the file names that we want to change. However, if we have many files this is not an optimal way to change the name of multiple files. Thus, we are going to use another method from the os module; listdir. Furthermore, we will also use the filter method from the module called fnmatch.

renaming multiple files in python

The first step, before renaming the files, is to create a list of all text files in a folder (e.g., in the subfolder "Many_Files"). Note, that we have added the subfolder to the file_path string.

import os, fnmatch

file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\Many_Files\\'

files_to_rename = fnmatch.filter(os.listdir(file_path), '*.txt')

print(files_to_rename)

Next, we are going to loop through each file name and rename them. In the example below, we are using the file_path string, from the code chunk above, because this is where the files are located. If we were to have the files we want to rename in a different folder, we'd change file_path to that folder. Furthermore, we are creating a new file name (e.g., new_name = 'new_file') and we use the enumerate method to give them a unique name. For example, the file 'euupbihlaqgk.txt' will be renamed 'Datafile1.txt'.

new_name = 'Datafile'

for i, file_name in enumerate(files_to_rename):
    new_file_name = new_name + str(i) + '.txt'
    
    os.rename(file_path + file_name, 
          file_path + new_file_name)

As can be seen in the rename a file in Python example above, we also used the str() method to change the data type of the integer variable called i to a string.

Remember, if we were using Linux we'd have to change the file_path so that it will find the files we want to rename.

Renaming Multiple Files by Replacing Characters in the File Name

Now, sometimes we have files that we want to replace certain characters from. In this final example, we are going to use the replace method to replace underscores ("_") from file names. This, of course, means that we need to rename the files.

Removing underscore from files in Python (i.e., renaming them)Files with underscores
import os, fnmatch

file_path = 'C:\\Users\\erima96\\Documents\\Files_To_Rename\\Many_Files\\'

files_to_rename = fnmatch.filter(os.listdir(file_path), '*.txt')

for file_name in files_to_rename:    
    os.rename(file_path + file_name, 
          file_path + file_name.replace(' ', '-'))

Now, in the code above we have successfully replaced the "_" from the filenames using replace and then we renamed the files. Again, remember if we were to rename files in Linux the file_path string need be changed (e.g. " ").

Replaced underscores (done in Python)Underscores replaced

Conclusion: Renaming Files in Python

In this post, we have learned how to rename a single file, as well as multiple files, using Python. This was accomplished using the os and fnmatch modules. Finally, we learned how to replace underscores (and rename) files using Python.

The post Rename Files in Python: A Guide with Examples using os.rename() appeared first on Erik Marsja.

23 Jan 2020 8:40pm GMT

IslandT: Create a project to track total sales at different locations with the Python program

In the previous posts, we have gone through a project which will receive the user input and commit those data into the earning table. This program has been further modified to include the plotting of a bar chart to indicate the total sales of various inventories in various locations.

This project has been uploaded to this site, you can download the source code of this project for free through this link. If you like this project, don't forget to award me with stars on the same project page or share the project page with friends!

Once you have downloaded the files, run the ui.py file and play around with the program.

I am using the Pandas module to plot the graph.

# begin plotting the bar chart
            s = pd.Series(index=label_x, data=label_y)
            s.plot(color="green", kind="bar", title = cc + " Sales for January at " + location)
            plt.show()
The bar chart of total sales for each inventory

Below are the steps to use the program, under the Shoe Sale and Shirt Sale section, select either the type of shoe or the shirt, then select the amount under earning and select a place under location. Just go ahead and pressed the submit button beside the shoe sale's Combobox or else pressed the submit button beside the shirt sale's Combobox to commit the data to the earning table!

The data we have entered are inside the earning table

In order to display the bar chart, all you need to do is to select the location under the plotting graph section and then pressed either the shoe or the shirt button within that same section.

There are more to come, you can edit the program as you wish to suit your needs. Enjoy!

23 Jan 2020 4:10pm GMT

IslandT: Create a project to track total sales at different locations with the Python program

In the previous posts, we have gone through a project which will receive the user input and commit those data into the earning table. This program has been further modified to include the plotting of a bar chart to indicate the total sales of various inventories in various locations.

This project has been uploaded to this site, you can download the source code of this project for free through this link. If you like this project, don't forget to award me with stars on the same project page or share the project page with friends!

Once you have downloaded the files, run the ui.py file and play around with the program.

I am using the Pandas module to plot the graph.

# begin plotting the bar chart
            s = pd.Series(index=label_x, data=label_y)
            s.plot(color="green", kind="bar", title = cc + " Sales for January at " + location)
            plt.show()
The bar chart of total sales for each inventory

Below are the steps to use the program, under the Shoe Sale and Shirt Sale section, select either the type of shoe or the shirt, then select the amount under earning and select a place under location. Just go ahead and pressed the submit button beside the shoe sale's Combobox or else pressed the submit button beside the shirt sale's Combobox to commit the data to the earning table!

The data we have entered are inside the earning table

In order to display the bar chart, all you need to do is to select the location under the plotting graph section and then pressed either the shoe or the shirt button within that same section.

There are more to come, you can edit the program as you wish to suit your needs. Enjoy!

23 Jan 2020 4:10pm GMT

Stack Abuse: String Formatting with Python 3's f-Strings

Introduction

Python 3.6 introduced a new way to format strings: f-Strings. It is faster than other string formatting methods in Python, and they allow us to evaluate Python expressions inside a string.

In this post, we'll look at the various ways we can format strings in Python. Then we'll have a deeper look at f-Strings, looking at how we can use it when displaying different data.

Traditional String Formatting in Python

Before we get into f-Strings, let's have a look at the more "traditional" string formatting options available in Python. If you just want to skip to learning about f-Strings, check out the section String Formatting with f-Strings in this article.

String Concatenation

String concatenation means that we are combining two strings to make a new one. In Python, we typically concatenate strings with the + operator.

In your Python interpreter, let's use concatenation to include a variable in a string:

name = "Python"
print("I like " + name + " very much!")

You would see the following output:

I like Python very much!

String concatenation works only on strings. If you want non-string data to be included, you have to convert it manually.

Let's see by concatenating a number with a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + age + " years old")

Running the above you'll see this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Now, let's convert age to a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + str(age) + " years old")

The interpreter would correctly show:

You wouldn't believe me, but I am only 30 years old

This method is not always the quickest for Python to evaluate, nor is it easy for humans to manage with many variables. It's better to use Python's dedicated string formatting methods.

C-Style String Formatting

C-style string formatting, also referred to as the printf style, uses strings as templates that are marked with % so that variables can be substituted.

For example, %d tells Python that we are substituting a number, whereas %s tells Python that we are substituting a string.

There are a few controlling mechanisms available too, for example %02d ensures that the number is at least 2 digits, and if it is not, the rest will be filled by zeros. We can use a marker like %.4f to substitute a decimal with exactly 4 floating points in the string.

In your Python interpreter, type the following code so we can see C-style string formatting in action:

print('Here is an integer %02d' % 4)
print('And a float: %.2f' % 3.141592)
print("And a %s with %d replacements" % ("mess", 2))

Your output should be:

Here is an integer 04
And a float: 3.14
And a mess with 2 replacements

As you could see, with more and more arguments it gets pretty convoluted and hard to follow. So Python takes this one step further and allows you to use dictionaries in % formatting.

Although it isn't much of an improvement in its overall aesthetics, it could help a little bit on the readability front.

Try formatting a C-style string with a dictionary:

print('Using %(dictionary)s format in C-style string formatting, here is %(num)d number to make it more interesting!' % {'dictionary': "named", "num": 1})

As you can see, we can specify the name within parenthesis between the % and the rest of the identifier.

Your interpreter should display:

Using named format in C-style string formatting, here is 1 number to make it more interesting!

If you haven't spent the last 42 years developing in C, the above syntax might not be to your liking. As such, Python gives us another option using the format() method on strings.

Python format() Function

The format() function behaves similarly to the C-style formatting, but it is much more readable. It works by invoking the format() method on your string while providing the replacements in the template within curly brackets - {}.

Using your Python interpreter, we'll use the format() method to format a string. Let's start by substituting one string:

print("This is a first {}".format("attempt!"))

The output will be:

This is a first attempt!

We can use curly brackets multiple times, substituting the variables in order:

print("And {} can add {} string too.".format("we", "more"))

Output:

And we can add more string too.

We can also name the variables we are formatting:

print("String format also supports {dictionary} arguments.".format(dictionary="named"))

The above code should print:

String format also supports named arguments.

While this method is very readable, Python is more efficient with f-Strings, while also being readable. Let's have a look at f-Strings more closely.

String Formatting with f-Strings

F-Strings, or formatted string literals, are prefixed with an f and contain the replacement fields with curly braces. They are evaluated at run-time, which makes them the fastest string formatting option in Python (at least in the standard CPython implementation).

First, verify that you have a Python version that's 3.6 or higher:

$ python3 --version
Python 3.6.0

If your Python version is less than 3.6, you need to upgrade to use this feature.

F-String literals start with an f, followed by any type of strings (i.e. single quotation, double quotation, triple quotation), then you can include your Python expression inside the string, in between curly brackets.

Let's string to use an f-String in your Python shell:

name = "f-String"
print(f"hello {name}!")

And now run it to see the magic:

hello f-String!

It's very similar to using the format() function. However, it comes with a lot of features that are not available with other formatting methods.

Let's have a look at those features, starting with multi-line strings.

Multi-line f-Strings

F-Strings allows for multi-line strings, all you have to do is wrap them in parentheses:

name = "World"
message = (
  f"Hello {name}. "
  "This is from a multi-lined f-string. "
  f"Have a good day, {name}"
)
print(message)

You will see this on printed out:

Hello World. This is from a multi-lined f-string. Have a good day, World

Don't forget to put the f literal in front of lines that have replacements. The substitutions won't work otherwise.

Evaluating Python Expressions in f-Strings

Python's f-Strings allow you to do expressions right in the string itself. The following string is valid:

number = 10
print(f"{number} + 1 = {number+1}")

And outputs:

10 + 1 = 11

You can include any valid expression you want, as long as it doesn't have special characters. For example, we can have function calls:

def func():
  return 42

print(f"func() = {func()}")

This returns:

func() = 42

Formatting Classes with f-Strings

You can include objects created from classes in an f-String. Every class has two different methods that allow an object to be converted to a string:

f-Strings automatically call the __str__() for the object you provide them.

Let's try our f-Strings with a class to see for ourselves. We will create a User class that stores our name and associated database ID that our imaginary system uses.

Create a new file called fstring_classes.py and add the following:

# fstring_classes.py

class User:
    def __init__(self, name, db_id):
        self.name = name
        self.db_id = db_id

    def __str__(self):
        return f"User with name: {self.name}"

my_user = User("Sajjad", 12)
print(f"{my_user}")

In your Terminal, run the file with Python:

$ python fstring_classes.py

This program will show:

User with name: Sajjad

If we wanted to use an f-String with the __repr_() method of an object, you can put an !r in front of your object being substituted.

Edit the fstring_classes.py file and change the last line:

print(f"{my_user}")

Into the following, which includes the !r suffix:

print(f"{my_user!r}")

Now we can run this program again:

$ python fstring_classes.py

And our output will be:

User with name: Sajjad with id 12

Handling Special Characters in f-Strings

F-Strings, like any other Python string, support the use of backslashes to escape characters. All the following are valid Python f-Strings:

print(f'escaping with \\ is also possible, like: \'')

Running this will print:

escaping with \ is also possible, like: '

However, there is one limitation, we cannot use backslashes within the curly braces (the expression part of the f-String). If you try the following in your Python interpreter:

print(f'{ord("\n")}')

You'll get the following error:

  File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash

The same limitation is true for same-type quotation marks. The code below:

a = {"key": "value"}
print(f"the value is {a["key"]}")

Gives us this syntax error:

  File "<stdin>", line 1
    print(f"the value is {a["key"]}")
                               ^
SyntaxError: invalid syntax

To avoid these problems, we can either calculate them outside of the f-String:

ord_newline = ord("\n")
print(f'{ord_newline}')

Which prints:

10

Or we can use different quotation types:

a = {"key": "val"}
print(f"The value is {a['key']}")

Which displays:

The value is val

Conclusion

In this post, we looked at different methods of string formatting in Python, with a focus on the f-String method. In Python, we are spoiled for options as we can concatenate strings, use C-Style formatting with %, or the format() method on strings.

However, with f-Strings, we get a readable solution that Python has optimized for. Aside from its speed benefits, we've seen how well f-Strings work with evaluating expressions and objects from classes.

23 Jan 2020 1:23pm GMT

Stack Abuse: String Formatting with Python 3's f-Strings

Introduction

Python 3.6 introduced a new way to format strings: f-Strings. It is faster than other string formatting methods in Python, and they allow us to evaluate Python expressions inside a string.

In this post, we'll look at the various ways we can format strings in Python. Then we'll have a deeper look at f-Strings, looking at how we can use it when displaying different data.

Traditional String Formatting in Python

Before we get into f-Strings, let's have a look at the more "traditional" string formatting options available in Python. If you just want to skip to learning about f-Strings, check out the section String Formatting with f-Strings in this article.

String Concatenation

String concatenation means that we are combining two strings to make a new one. In Python, we typically concatenate strings with the + operator.

In your Python interpreter, let's use concatenation to include a variable in a string:

name = "Python"
print("I like " + name + " very much!")

You would see the following output:

I like Python very much!

String concatenation works only on strings. If you want non-string data to be included, you have to convert it manually.

Let's see by concatenating a number with a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + age + " years old")

Running the above you'll see this error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Now, let's convert age to a string:

age = (10 + 5) * 2
print("You wouldn't believe me, but I am only " + str(age) + " years old")

The interpreter would correctly show:

You wouldn't believe me, but I am only 30 years old

This method is not always the quickest for Python to evaluate, nor is it easy for humans to manage with many variables. It's better to use Python's dedicated string formatting methods.

C-Style String Formatting

C-style string formatting, also referred to as the printf style, uses strings as templates that are marked with % so that variables can be substituted.

For example, %d tells Python that we are substituting a number, whereas %s tells Python that we are substituting a string.

There are a few controlling mechanisms available too, for example %02d ensures that the number is at least 2 digits, and if it is not, the rest will be filled by zeros. We can use a marker like %.4f to substitute a decimal with exactly 4 floating points in the string.

In your Python interpreter, type the following code so we can see C-style string formatting in action:

print('Here is an integer %02d' % 4)
print('And a float: %.2f' % 3.141592)
print("And a %s with %d replacements" % ("mess", 2))

Your output should be:

Here is an integer 04
And a float: 3.14
And a mess with 2 replacements

As you could see, with more and more arguments it gets pretty convoluted and hard to follow. So Python takes this one step further and allows you to use dictionaries in % formatting.

Although it isn't much of an improvement in its overall aesthetics, it could help a little bit on the readability front.

Try formatting a C-style string with a dictionary:

print('Using %(dictionary)s format in C-style string formatting, here is %(num)d number to make it more interesting!' % {'dictionary': "named", "num": 1})

As you can see, we can specify the name within parenthesis between the % and the rest of the identifier.

Your interpreter should display:

Using named format in C-style string formatting, here is 1 number to make it more interesting!

If you haven't spent the last 42 years developing in C, the above syntax might not be to your liking. As such, Python gives us another option using the format() method on strings.

Python format() Function

The format() function behaves similarly to the C-style formatting, but it is much more readable. It works by invoking the format() method on your string while providing the replacements in the template within curly brackets - {}.

Using your Python interpreter, we'll use the format() method to format a string. Let's start by substituting one string:

print("This is a first {}".format("attempt!"))

The output will be:

This is a first attempt!

We can use curly brackets multiple times, substituting the variables in order:

print("And {} can add {} string too.".format("we", "more"))

Output:

And we can add more string too.

We can also name the variables we are formatting:

print("String format also supports {dictionary} arguments.".format(dictionary="named"))

The above code should print:

String format also supports named arguments.

While this method is very readable, Python is more efficient with f-Strings, while also being readable. Let's have a look at f-Strings more closely.

String Formatting with f-Strings

F-Strings, or formatted string literals, are prefixed with an f and contain the replacement fields with curly braces. They are evaluated at run-time, which makes them the fastest string formatting option in Python (at least in the standard CPython implementation).

First, verify that you have a Python version that's 3.6 or higher:

$ python3 --version
Python 3.6.0

If your Python version is less than 3.6, you need to upgrade to use this feature.

F-String literals start with an f, followed by any type of strings (i.e. single quotation, double quotation, triple quotation), then you can include your Python expression inside the string, in between curly brackets.

Let's string to use an f-String in your Python shell:

name = "f-String"
print(f"hello {name}!")

And now run it to see the magic:

hello f-String!

It's very similar to using the format() function. However, it comes with a lot of features that are not available with other formatting methods.

Let's have a look at those features, starting with multi-line strings.

Multi-line f-Strings

F-Strings allows for multi-line strings, all you have to do is wrap them in parentheses:

name = "World"
message = (
  f"Hello {name}. "
  "This is from a multi-lined f-string. "
  f"Have a good day, {name}"
)
print(message)

You will see this on printed out:

Hello World. This is from a multi-lined f-string. Have a good day, World

Don't forget to put the f literal in front of lines that have replacements. The substitutions won't work otherwise.

Evaluating Python Expressions in f-Strings

Python's f-Strings allow you to do expressions right in the string itself. The following string is valid:

number = 10
print(f"{number} + 1 = {number+1}")

And outputs:

10 + 1 = 11

You can include any valid expression you want, as long as it doesn't have special characters. For example, we can have function calls:

def func():
  return 42

print(f"func() = {func()}")

This returns:

func() = 42

Formatting Classes with f-Strings

You can include objects created from classes in an f-String. Every class has two different methods that allow an object to be converted to a string:

f-Strings automatically call the __str__() for the object you provide them.

Let's try our f-Strings with a class to see for ourselves. We will create a User class that stores our name and associated database ID that our imaginary system uses.

Create a new file called fstring_classes.py and add the following:

# fstring_classes.py

class User:
    def __init__(self, name, db_id):
        self.name = name
        self.db_id = db_id

    def __str__(self):
        return f"User with name: {self.name}"

my_user = User("Sajjad", 12)
print(f"{my_user}")

In your Terminal, run the file with Python:

$ python fstring_classes.py

This program will show:

User with name: Sajjad

If we wanted to use an f-String with the __repr_() method of an object, you can put an !r in front of your object being substituted.

Edit the fstring_classes.py file and change the last line:

print(f"{my_user}")

Into the following, which includes the !r suffix:

print(f"{my_user!r}")

Now we can run this program again:

$ python fstring_classes.py

And our output will be:

User with name: Sajjad with id 12

Handling Special Characters in f-Strings

F-Strings, like any other Python string, support the use of backslashes to escape characters. All the following are valid Python f-Strings:

print(f'escaping with \\ is also possible, like: \'')

Running this will print:

escaping with \ is also possible, like: '

However, there is one limitation, we cannot use backslashes within the curly braces (the expression part of the f-String). If you try the following in your Python interpreter:

print(f'{ord("\n")}')

You'll get the following error:

  File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash

The same limitation is true for same-type quotation marks. The code below:

a = {"key": "value"}
print(f"the value is {a["key"]}")

Gives us this syntax error:

  File "<stdin>", line 1
    print(f"the value is {a["key"]}")
                               ^
SyntaxError: invalid syntax

To avoid these problems, we can either calculate them outside of the f-String:

ord_newline = ord("\n")
print(f'{ord_newline}')

Which prints:

10

Or we can use different quotation types:

a = {"key": "val"}
print(f"The value is {a['key']}")

Which displays:

The value is val

Conclusion

In this post, we looked at different methods of string formatting in Python, with a focus on the f-String method. In Python, we are spoiled for options as we can concatenate strings, use C-Style formatting with %, or the format() method on strings.

However, with f-Strings, we get a readable solution that Python has optimized for. Aside from its speed benefits, we've seen how well f-Strings work with evaluating expressions and objects from classes.

23 Jan 2020 1:23pm GMT

PyCon: Refund policy for Attendees and Financial Aid recipients traveling to PyCon internationally

International travel to the United States has become a greater risk for many in our community. In light of current conditions, PyCon would like to highlight the support we provide for international travelers. If you are travelling internationally to PyCon, take note of the following information. Financial Aid applicants should take note of additional information specific to that process in the second section.

For All International Travellers to PyCon

In order to better assist our international community, we have the following refund policy for PyCon attendees:
Please note the above policy applies to attendees not traveling to or from OFAC sanctioned countries only. In the event that you have a refund request or questions, please contact our registration team: pycon-reg at python.org


For International Travellers who are applying for Financial Aid

We have the following policies for recipients of Financial Aid grants.

While PyCon's Financial Aid program does strive to make travel possible for a broader audience than can not comfortably attend the conference on their own budget, it cannot eliminate the risks of travel. Indeed, our mechanism for delivering awarded funds - a disbursement collected at the conference itself - can only succeed for travelers who actually reach PyCon.

With that in mind, let's review the risks of traveling to PyCon financial aid recipients may encounter, and then learn about the promise that the conference makes for refunds:

  • Second: if despite holding a visa you are denied entry upon arrival to the United States, but used our registration page to book a room in a conference hotel, our staff will personally work with the hotel to make sure you do not receive a cancellation fee.
  • Third: if despite holding a visa you are denied entry upon arrival to the United States, PyCon will fully refund your registration fee. While this is more serious for our conference budget - at such a late date, we will be unlikely to be able to register someone else in your place - we have decided to put the financial safety of our Financial Aid recipients from overseas first.
The deadline for applying to PyCon 2020 for Financial Aid is January 31st (23:59:59 on Jan 31st AoE)! The link to the application is on our main Financial Aid page: https://us.pycon.org/2020/financial-assistance/. If you have a refund request or questions, please contact our Financial Aid team: pycon-aid at python.org.

In conclusion

We hope that these extra guarantees beyond the normal terms of our refund policy will help attendees plan more confidently and will continue to make PyCon 2020 an option for as many Pythonistas from around the world as possible.

- The PyCon Staff and Organizers

23 Jan 2020 12:22pm GMT

PyCon: Refund policy for Attendees and Financial Aid recipients traveling to PyCon internationally

International travel to the United States has become a greater risk for many in our community. In light of current conditions, PyCon would like to highlight the support we provide for international travelers. If you are travelling internationally to PyCon, take note of the following information. Financial Aid applicants should take note of additional information specific to that process in the second section.

For All International Travellers to PyCon

In order to better assist our international community, we have the following refund policy for PyCon attendees:
Please note the above policy applies to attendees not traveling to or from OFAC sanctioned countries only. In the event that you have a refund request or questions, please contact our registration team: pycon-reg at python.org


For International Travellers who are applying for Financial Aid

We have the following policies for recipients of Financial Aid grants.

While PyCon's Financial Aid program does strive to make travel possible for a broader audience than can not comfortably attend the conference on their own budget, it cannot eliminate the risks of travel. Indeed, our mechanism for delivering awarded funds - a disbursement collected at the conference itself - can only succeed for travelers who actually reach PyCon.

With that in mind, let's review the risks of traveling to PyCon financial aid recipients may encounter, and then learn about the promise that the conference makes for refunds:

  • Second: if despite holding a visa you are denied entry upon arrival to the United States, but used our registration page to book a room in a conference hotel, our staff will personally work with the hotel to make sure you do not receive a cancellation fee.
  • Third: if despite holding a visa you are denied entry upon arrival to the United States, PyCon will fully refund your registration fee. While this is more serious for our conference budget - at such a late date, we will be unlikely to be able to register someone else in your place - we have decided to put the financial safety of our Financial Aid recipients from overseas first.
The deadline for applying to PyCon 2020 for Financial Aid is January 31st (23:59:59 on Jan 31st AoE)! The link to the application is on our main Financial Aid page: https://us.pycon.org/2020/financial-assistance/. If you have a refund request or questions, please contact our Financial Aid team: pycon-aid at python.org.

In conclusion

We hope that these extra guarantees beyond the normal terms of our refund policy will help attendees plan more confidently and will continue to make PyCon 2020 an option for as many Pythonistas from around the world as possible.

- The PyCon Staff and Organizers

23 Jan 2020 12:22pm GMT

Abhijeet Pal: Python Program to Convert Binary Number to Decimal and Vice-Versa

A binary number is a number expressed in the base-2 numeral system or binary numeral system, which uses only two symbols 0 and 1. The decimal numeral system is the standard system for denoting integer and non-integer numbers. All decimal numbers can be converted to equivalent binary values and vice versa for example, the binary equivalent of "2" is "10" to explore more visit binary to decimal converter. In this article, we will create python programs for converting a binary number into decimal and vice versa Convert Binary to Decimal # Taking binary input binary = input("Enter a binary number:") # Calling the function BinaryToDecimal(binary) def BinaryToDecimal(binary): decimal = 0 for digit in binary: decimal = decimal*2 + int(digit) print("The decimal value is:", decimal) Output Enter a binary number:10 The decimal value is: 2 Pythonic way to convert binary into decimal We can use the built-in int() function to convert binary numbers into decimals. The int() function converts the binary string in base 2 number. binary_string = input("Enter a binary number :") try: decimal = int(binary_string,2) print("The decimal value is …

The post Python Program to Convert Binary Number to Decimal and Vice-Versa appeared first on Django Central.

23 Jan 2020 6:04am GMT

Abhijeet Pal: Python Program to Convert Binary Number to Decimal and Vice-Versa

A binary number is a number expressed in the base-2 numeral system or binary numeral system, which uses only two symbols 0 and 1. The decimal numeral system is the standard system for denoting integer and non-integer numbers. All decimal numbers can be converted to equivalent binary values and vice versa for example, the binary equivalent of "2" is "10" to explore more visit binary to decimal converter. In this article, we will create python programs for converting a binary number into decimal and vice versa Convert Binary to Decimal # Taking binary input binary = input("Enter a binary number:") # Calling the function BinaryToDecimal(binary) def BinaryToDecimal(binary): decimal = 0 for digit in binary: decimal = decimal*2 + int(digit) print("The decimal value is:", decimal) Output Enter a binary number:10 The decimal value is: 2 Pythonic way to convert binary into decimal We can use the built-in int() function to convert binary numbers into decimals. The int() function converts the binary string in base 2 number. binary_string = input("Enter a binary number :") try: decimal = int(binary_string,2) print("The decimal value is …

The post Python Program to Convert Binary Number to Decimal and Vice-Versa appeared first on Django Central.

23 Jan 2020 6:04am GMT

Artem Rys: “Effective Python” by Brett Slatkin book review

I'd recommend this book to the people who are using Python at least several months and are feeling good with the basics.

Continue reading on python4you »

23 Jan 2020 1:32am GMT

Artem Rys: “Effective Python” by Brett Slatkin book review

I'd recommend this book to the people who are using Python at least several months and are feeling good with the basics.

Continue reading on python4you »

23 Jan 2020 1:32am GMT

22 Jan 2020

feedPlanet Python

Learn PyQt: Matplotlib plots in PyQt5, embedding charts in your GUI applications

In a previous tutorial we covered plotting in PyQt5 using PyQtGraph. PyQtGraph uses the Qt vector-based QGraphicsScene to draw plots and provides a great interface for interactive and high performance plotting.

However, there is another plotting library for Python which is used far more widely, and which offers a richer assortment of plots - Matplotlib. If you're migrating an existing data analysis tool to a PyQt GUI, or if you simply want to have access to the array of plot abilities that Matplotlib offers, then you'll want to know how to include Matplotlib plots within your application.

In this tutorial we'll cover how to embed Matplotlib plots in your PyQt applications

Many other Python libraries - such as seaborn and pandas- make use of the Matplotlib backend for plotting. These plots can be embedded in PyQt5 in the same way shown here, and the reference to the axes passed when plotting. There is a pandas example at the end of this tutorial.

Installing Matplotlib

The following examples assume you have Matplotlib installed. If not you can install it as normal using Pip, with the following -

pip install matplotlib

A simple example

The following minimal example sets up a Matplotlib canvas FigureCanvasQTAgg which creates the Figure and adds a single set of axes to it. This canvas object is also a QWidget and so can be embedded straight into an application as any other Qt widget.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)
        sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])
        self.setCentralWidget(sc)

        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

In this case we're adding our MplCanvas widget as the central widget on the window with .setCentralWidget(). This means it will take up the entirety of the window and resize together with it. The plotted data [0,1,2,3,4], [10,1,20,3,40] is provided as two lists of numbers (x and y respectively) as required by the .plot method.

Basic plot with embedded Matplotlib Basic plot with embedded Matplotlib

Plot controls

Plots from Matplotlib displayed in PyQt5 are actually rendered as simple (bitmap) images by the Agg backend. The FigureCanvasQTAgg class wraps this backend and displays the resulting image on a Qt widget. The effect of this architecture is that Qt is unaware of the positions of lines and other plot elements - only the x, y coordinates of any clicks and mouse movements over the widget.

However, support for handling Qt mouse events and transforming them into interactions on the plot is built into Matplotlib. This can be controlled through a custom toolbar which can be added to your applications alongside the plot. In this section we'll look at adding these controls so we can zoom, pan and get data from embedded Matplotlib plots.

The complete code, importing the toolbar widget NavigationToolbar2QT and adding it to the interface within a QVBoxLayout, is shown below -

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtGui, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        sc = MplCanvas(self, width=5, height=4, dpi=100)
        sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])

        # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second.
        toolbar = NavigationToolbar(sc, self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(sc)

        # Create a placeholder widget to hold our toolbar and canvas.
        widget = QtWidgets.QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

We'll step through the changes.

First we import the toolbar widget from matplotlib.backends.backend_qt5agg.NavigationToolbar2QT renaming it with the simpler name NavigationToolbar. We create an instance of the toolbar by calling NavigationToolbar with two parameters, first the canvas object sc and then the parent for the toolbar, in this case our MainWindow object self. Passing in the canvas links the created toolbar to it, allowing it to be controlled. The resulting toolbar object is stored in the variable toolbar.

We need to add two widgets to the window, one above the other, so we use a QVBoxLayout. First we add our toolbar widget toolbar and then the canvas widget sc to this layout. Finally, we set this layout onto our simple widget layout container which is set as the central widget for the window.

Running the above code will produce the following window layout, showing the plot at the bottom and the controls on top as a toolbar.

Matplotlib plot with Toolbar Matplotlib plot with Toolbar

The buttons provided by NavigationToolbar2QT allow the following actions -

  • Home, Back/Forward, Pan & Zoom which are used to navigate through the plots. The Back/Forward buttons can step backwards and forwards through navigation steps, for example zooming in and then clicking Back will return to the previous zoom. Home returns to the initial state of the plot.
  • Plot margin/position configuration which can adjust the plot within the window.
  • Axis/curve style editor, where you can modify plot titles and axes scales, along with setting plot line colours and line styles. The colour selection uses the platform-default colour picker, allowing any available colours to be selected.
  • Save, to save the resulting figure as an image (all Matplotlib supported formats).

A few of these configuration settings are shown below.

Matplotlib figure options Matplotlib figure options
Matplotlib curves figure options Matplotlib curves figure options

For more information on navigating and configuring Matplotlib plots, take a look at the official Matplotlib toolbar documentation.

Updating plots

Quite often in applications you'll want to update the data shown in plots, whether in response to input from the user or updated data from an API. There are two ways to update plots in Matplotlib, either

  1. clearing and redrawing the canvas (simpler, but slower) or,
  2. by keeping a reference to the plotted line and updating the data.

If performance is important to your app it is recommended you do the latter, but the first is simpler. We start with the simple clear-and-redraw method first below -

Clear and redraw

import sys
import random
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure


class MplCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
        self.setCentralWidget(self.canvas)

        n_data = 50
        self.xdata = list(range(n_data))
        self.ydata = [random.randint(0, 10) for i in range(n_data)]
        self.update_plot()

        self.show()

        # Setup a timer to trigger the redraw by calling update_plot.
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_plot)
        self.timer.start()

    def update_plot(self):
        # Drop off the first y element, append a new one.
        self.ydata = self.ydata[1:] + [random.randint(0, 10)]
        self.canvas.axes.cla()  # Clear the canvas.
        self.canvas.axes.plot(self.xdata, self.ydata, 'r')
        # Trigger the canvas to update and redraw.
        self.canvas.draw()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

In this example we've moved the plotting to a update_plot method to keep it self-contained. In this method we take our ydata array and drop off the first value with [1:] then append a new random integer between 0 and 10. This has the effect of scrolling the data to the left.

To redraw we simply call axes.cla() to clear the axes (the entire canvas) and the axes.plot(…) to re-plot the data, including the updated values. The resulting canvas is then redrawn to the widget by calling canvas.draw().

The update_plot method is called every 100 msec using a QTimer. The clear-and-refresh method is fast enough to keep a plot updated at this rate, but as we'll see shortly, falters as the speed increases.

Maplotlib plot updated using clear and redraw

In-place redraw

The changes required to update the plotted lines in-place are fairly minimal, requiring only an addition variable to store and retrieve the reference to the plotted line. The updated MainWindow code is shown below.

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
        self.setCentralWidget(self.canvas)

        n_data = 50
        self.xdata = list(range(n_data))
        self.ydata = [random.randint(0, 10) for i in range(n_data)]

        # We need to store a reference to the plotted line 
        # somewhere, so we can apply the new data to it.
        self._plot_ref = None
        self.update_plot()

        self.show()

        # Setup a timer to trigger the redraw by calling update_plot.
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_plot)
        self.timer.start()

    def update_plot(self):
        # Drop off the first y element, append a new one.
        self.ydata = self.ydata[1:] + [random.randint(0, 10)]

        # Note: we no longer need to clear the axis.       
        if self._plot_ref is None:
            # First time we have no plot reference, so do a normal plot.
            # .plot returns a list of line <reference>s, as we're
            # only getting one we can take the first element.
            plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
            self._plot_ref = plot_refs[0]
        else:
            # We have a reference, we can use it to update the data for that line.
            self._plot_ref.set_ydata(self.ydata)

        # Trigger the canvas to update and redraw.
        self.canvas.draw()

First, we need a variable to hold a reference to the plotted line we want to update, which here we're calling _plot_ref. We initialize self._plot_ref with None so we can check its value later to determine if the line has already been drawn - if the value is still None we have not yet drawn the line.

T> If you were drawing multiple lines you would probably want to use a list or dict data structure to store the multiple references and keep track of which is which.

Finally, we update the ydata data as we did before, rotating it to the left and appending a new random value. Then we either -

  1. if self._plotref is None (i.e. we have not yet drawn the line) draw the line and store the reference in self._plot_ref, or
  2. update the line in place by calling self._plot_ref.set_ydata(self.ydata)

We obtain a reference to the plotted when calling .plot. However .plot returns a list (to support cases where a single .plot call can draw more than one line). In our case we're only plotting a single line, so we simply want the first element in that list - a single Line2D object. To get this single value into our variable we can assign to a temporary variable plot_refs and then assign the first element to our self._plot_ref variable.

plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
self._plot_ref = plot_refs[0]

You could also use tuple-unpacking, picking off the first (and only) element in the list with -

self._plot_ref, = self.canvas.axes.plot(self.xdata, self.ydata, 'r')

If you run the resulting code, there will be no noticeable difference in performance between this and the previous method at this speed. However if you attempt to update the plot faster (e.g. down to every 10 msec) you'll start to notice that clearing the plot and re-drawing takes longer, and the updates do not keep up with the timer. We can compare the two versions below -

Both using 100 msec timer, clear-and-redraw on the left, update-in-place on the right.

Maplotlib plot updated using clear and redraw

Both using 10 msec timer, clear-and-redraw on the left, update-in-place on the right.

Maplotlib plot updated using clear and redraw

Whether this performance difference is enough to matter in your application depends on what you're building, and should be weighed against the added complication of keeping and managing the references to plotted lines.

Embedding plots from Pandas

Pandas is a Python package focused on working with table (data frames) and series data structures, which is particularly useful for data analysis workflows. It comes with built-in support for plotting with Matplotlib and here we'll take a quick look at how to embed these plots into PyQt5. With this you will be able to start building PyQt5 data-analysis applications built around Pandas.

Pandas plotting functions are directly accessible from the DataFrame objects. The function signature is quite complex, giving a lot of options to control how the plots will be drawn.

DataFrame.plot(
    x=None, y=None, kind='line', ax=None, subplots=False,
    sharex=None, sharey=False, layout=None, figsize=None,
    use_index=True, title=None, grid=None, legend=True, style=None,
    logx=False, logy=False, loglog=False, xticks=None, yticks=None,
    xlim=None, ylim=None, rot=None, fontsize=None, colormap=None,
    table=False, yerr=None, xerr=None, secondary_y=False, 
    sort_columns=False, **kwargs
)

The parameter we're most interested in is ax which allows us to pass in our own matplotlib.Axes instance on which Pandas will plot the DataFrame.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure

import pandas as pd


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)

        # Create our pandas DataFrame with some simple
        # data and headers.
        df = pd.DataFrame([
           [0, 10], [5, 15], [2, 20], [15, 25], [4, 10], 
        ], columns=['A', 'B'])

        # plot the pandas DataFrame, passing in the 
        # matplotlib Canvas axes.
        df.plot(ax=sc.axes)

        self.setCentralWidget(sc)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

The key step here is passing the canvas axes in when calling the plot method on the DataFrameon the line df.plot(ax=sc.axes). You can use this same pattern to update the plot any time, although bear in mind that Pandas clears and redraws the entire canvas, meaning that it is not ideal for high performance plotting.

The resulting plot generated through Pandas is shown below -

Pandas plot embedded in PyQt5 Pandas plot embedded in PyQt5

Just as before, you can add the Matplotlib toolbar and control support to plots generated using Pandas, allowing you to zoom/pan and modify them live. The following code combines our earlier toolbar example with the Pandas example.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

import pandas as pd


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)

        # Create our pandas DataFrame with some simple
        # data and headers.
        df = pd.DataFrame([
           [0, 10], [5, 15], [2, 20], [15, 25], [4, 10], 
        ], columns=['A', 'B'])

        # plot the pandas DataFrame, passing in the 
        # matplotlib Canvas axes.
        df.plot(ax=sc.axes)

        # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second.
        toolbar = NavigationToolbar(sc, self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(sc)

        # Create a placeholder widget to hold our toolbar and canvas.
        widget = QtWidgets.QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

Running this you should see the following window, showing a Pandas plot embedded in PyQt5 alongside the Matplotlib toolbar.

Pandas plot with Matplotlib toolbar Pandas plot with Matplotlib toolbar

What's next

In this tutorial we looked at how you can embed Matplotlib plots in your PyQt5 applications. Being able to use Matplotlib plots in your applications allows you to create custom data analysis and visualization tools from Python.

Matplotlib is a huge library and too big to cover in detail here. If you're not familiar with Matplotlib plotting and want to give it a try, take a look at the documentation and example plots to see what is possible. If you are familiar with it you should now be able to put those skills to work in your PyQt5 apps!

22 Jan 2020 9:57pm GMT

Learn PyQt: Matplotlib plots in PyQt5, embedding charts in your GUI applications

In a previous tutorial we covered plotting in PyQt5 using PyQtGraph. PyQtGraph uses the Qt vector-based QGraphicsScene to draw plots and provides a great interface for interactive and high performance plotting.

However, there is another plotting library for Python which is used far more widely, and which offers a richer assortment of plots - Matplotlib. If you're migrating an existing data analysis tool to a PyQt GUI, or if you simply want to have access to the array of plot abilities that Matplotlib offers, then you'll want to know how to include Matplotlib plots within your application.

In this tutorial we'll cover how to embed Matplotlib plots in your PyQt applications

Many other Python libraries - such as seaborn and pandas- make use of the Matplotlib backend for plotting. These plots can be embedded in PyQt5 in the same way shown here, and the reference to the axes passed when plotting. There is a pandas example at the end of this tutorial.

Installing Matplotlib

The following examples assume you have Matplotlib installed. If not you can install it as normal using Pip, with the following -

pip install matplotlib

A simple example

The following minimal example sets up a Matplotlib canvas FigureCanvasQTAgg which creates the Figure and adds a single set of axes to it. This canvas object is also a QWidget and so can be embedded straight into an application as any other Qt widget.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)
        sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])
        self.setCentralWidget(sc)

        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

In this case we're adding our MplCanvas widget as the central widget on the window with .setCentralWidget(). This means it will take up the entirety of the window and resize together with it. The plotted data [0,1,2,3,4], [10,1,20,3,40] is provided as two lists of numbers (x and y respectively) as required by the .plot method.

Basic plot with embedded Matplotlib Basic plot with embedded Matplotlib

Plot controls

Plots from Matplotlib displayed in PyQt5 are actually rendered as simple (bitmap) images by the Agg backend. The FigureCanvasQTAgg class wraps this backend and displays the resulting image on a Qt widget. The effect of this architecture is that Qt is unaware of the positions of lines and other plot elements - only the x, y coordinates of any clicks and mouse movements over the widget.

However, support for handling Qt mouse events and transforming them into interactions on the plot is built into Matplotlib. This can be controlled through a custom toolbar which can be added to your applications alongside the plot. In this section we'll look at adding these controls so we can zoom, pan and get data from embedded Matplotlib plots.

The complete code, importing the toolbar widget NavigationToolbar2QT and adding it to the interface within a QVBoxLayout, is shown below -

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtGui, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        sc = MplCanvas(self, width=5, height=4, dpi=100)
        sc.axes.plot([0,1,2,3,4], [10,1,20,3,40])

        # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second.
        toolbar = NavigationToolbar(sc, self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(sc)

        # Create a placeholder widget to hold our toolbar and canvas.
        widget = QtWidgets.QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

We'll step through the changes.

First we import the toolbar widget from matplotlib.backends.backend_qt5agg.NavigationToolbar2QT renaming it with the simpler name NavigationToolbar. We create an instance of the toolbar by calling NavigationToolbar with two parameters, first the canvas object sc and then the parent for the toolbar, in this case our MainWindow object self. Passing in the canvas links the created toolbar to it, allowing it to be controlled. The resulting toolbar object is stored in the variable toolbar.

We need to add two widgets to the window, one above the other, so we use a QVBoxLayout. First we add our toolbar widget toolbar and then the canvas widget sc to this layout. Finally, we set this layout onto our simple widget layout container which is set as the central widget for the window.

Running the above code will produce the following window layout, showing the plot at the bottom and the controls on top as a toolbar.

Matplotlib plot with Toolbar Matplotlib plot with Toolbar

The buttons provided by NavigationToolbar2QT allow the following actions -

  • Home, Back/Forward, Pan & Zoom which are used to navigate through the plots. The Back/Forward buttons can step backwards and forwards through navigation steps, for example zooming in and then clicking Back will return to the previous zoom. Home returns to the initial state of the plot.
  • Plot margin/position configuration which can adjust the plot within the window.
  • Axis/curve style editor, where you can modify plot titles and axes scales, along with setting plot line colours and line styles. The colour selection uses the platform-default colour picker, allowing any available colours to be selected.
  • Save, to save the resulting figure as an image (all Matplotlib supported formats).

A few of these configuration settings are shown below.

Matplotlib figure options Matplotlib figure options
Matplotlib curves figure options Matplotlib curves figure options

For more information on navigating and configuring Matplotlib plots, take a look at the official Matplotlib toolbar documentation.

Updating plots

Quite often in applications you'll want to update the data shown in plots, whether in response to input from the user or updated data from an API. There are two ways to update plots in Matplotlib, either

  1. clearing and redrawing the canvas (simpler, but slower) or,
  2. by keeping a reference to the plotted line and updating the data.

If performance is important to your app it is recommended you do the latter, but the first is simpler. We start with the simple clear-and-redraw method first below -

Clear and redraw

import sys
import random
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure


class MplCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
        self.setCentralWidget(self.canvas)

        n_data = 50
        self.xdata = list(range(n_data))
        self.ydata = [random.randint(0, 10) for i in range(n_data)]
        self.update_plot()

        self.show()

        # Setup a timer to trigger the redraw by calling update_plot.
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_plot)
        self.timer.start()

    def update_plot(self):
        # Drop off the first y element, append a new one.
        self.ydata = self.ydata[1:] + [random.randint(0, 10)]
        self.canvas.axes.cla()  # Clear the canvas.
        self.canvas.axes.plot(self.xdata, self.ydata, 'r')
        # Trigger the canvas to update and redraw.
        self.canvas.draw()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

In this example we've moved the plotting to a update_plot method to keep it self-contained. In this method we take our ydata array and drop off the first value with [1:] then append a new random integer between 0 and 10. This has the effect of scrolling the data to the left.

To redraw we simply call axes.cla() to clear the axes (the entire canvas) and the axes.plot(…) to re-plot the data, including the updated values. The resulting canvas is then redrawn to the widget by calling canvas.draw().

The update_plot method is called every 100 msec using a QTimer. The clear-and-refresh method is fast enough to keep a plot updated at this rate, but as we'll see shortly, falters as the speed increases.

Maplotlib plot updated using clear and redraw

In-place redraw

The changes required to update the plotted lines in-place are fairly minimal, requiring only an addition variable to store and retrieve the reference to the plotted line. The updated MainWindow code is shown below.

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.canvas = MplCanvas(self, width=5, height=4, dpi=100)
        self.setCentralWidget(self.canvas)

        n_data = 50
        self.xdata = list(range(n_data))
        self.ydata = [random.randint(0, 10) for i in range(n_data)]

        # We need to store a reference to the plotted line 
        # somewhere, so we can apply the new data to it.
        self._plot_ref = None
        self.update_plot()

        self.show()

        # Setup a timer to trigger the redraw by calling update_plot.
        self.timer = QtCore.QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.update_plot)
        self.timer.start()

    def update_plot(self):
        # Drop off the first y element, append a new one.
        self.ydata = self.ydata[1:] + [random.randint(0, 10)]

        # Note: we no longer need to clear the axis.       
        if self._plot_ref is None:
            # First time we have no plot reference, so do a normal plot.
            # .plot returns a list of line <reference>s, as we're
            # only getting one we can take the first element.
            plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
            self._plot_ref = plot_refs[0]
        else:
            # We have a reference, we can use it to update the data for that line.
            self._plot_ref.set_ydata(self.ydata)

        # Trigger the canvas to update and redraw.
        self.canvas.draw()

First, we need a variable to hold a reference to the plotted line we want to update, which here we're calling _plot_ref. We initialize self._plot_ref with None so we can check its value later to determine if the line has already been drawn - if the value is still None we have not yet drawn the line.

T> If you were drawing multiple lines you would probably want to use a list or dict data structure to store the multiple references and keep track of which is which.

Finally, we update the ydata data as we did before, rotating it to the left and appending a new random value. Then we either -

  1. if self._plotref is None (i.e. we have not yet drawn the line) draw the line and store the reference in self._plot_ref, or
  2. update the line in place by calling self._plot_ref.set_ydata(self.ydata)

We obtain a reference to the plotted when calling .plot. However .plot returns a list (to support cases where a single .plot call can draw more than one line). In our case we're only plotting a single line, so we simply want the first element in that list - a single Line2D object. To get this single value into our variable we can assign to a temporary variable plot_refs and then assign the first element to our self._plot_ref variable.

plot_refs = self.canvas.axes.plot(self.xdata, self.ydata, 'r')
self._plot_ref = plot_refs[0]

You could also use tuple-unpacking, picking off the first (and only) element in the list with -

self._plot_ref, = self.canvas.axes.plot(self.xdata, self.ydata, 'r')

If you run the resulting code, there will be no noticeable difference in performance between this and the previous method at this speed. However if you attempt to update the plot faster (e.g. down to every 10 msec) you'll start to notice that clearing the plot and re-drawing takes longer, and the updates do not keep up with the timer. We can compare the two versions below -

Both using 100 msec timer, clear-and-redraw on the left, update-in-place on the right.

Maplotlib plot updated using clear and redraw

Both using 10 msec timer, clear-and-redraw on the left, update-in-place on the right.

Maplotlib plot updated using clear and redraw

Whether this performance difference is enough to matter in your application depends on what you're building, and should be weighed against the added complication of keeping and managing the references to plotted lines.

Embedding plots from Pandas

Pandas is a Python package focused on working with table (data frames) and series data structures, which is particularly useful for data analysis workflows. It comes with built-in support for plotting with Matplotlib and here we'll take a quick look at how to embed these plots into PyQt5. With this you will be able to start building PyQt5 data-analysis applications built around Pandas.

Pandas plotting functions are directly accessible from the DataFrame objects. The function signature is quite complex, giving a lot of options to control how the plots will be drawn.

DataFrame.plot(
    x=None, y=None, kind='line', ax=None, subplots=False,
    sharex=None, sharey=False, layout=None, figsize=None,
    use_index=True, title=None, grid=None, legend=True, style=None,
    logx=False, logy=False, loglog=False, xticks=None, yticks=None,
    xlim=None, ylim=None, rot=None, fontsize=None, colormap=None,
    table=False, yerr=None, xerr=None, secondary_y=False, 
    sort_columns=False, **kwargs
)

The parameter we're most interested in is ax which allows us to pass in our own matplotlib.Axes instance on which Pandas will plot the DataFrame.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure

import pandas as pd


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)

        # Create our pandas DataFrame with some simple
        # data and headers.
        df = pd.DataFrame([
           [0, 10], [5, 15], [2, 20], [15, 25], [4, 10], 
        ], columns=['A', 'B'])

        # plot the pandas DataFrame, passing in the 
        # matplotlib Canvas axes.
        df.plot(ax=sc.axes)

        self.setCentralWidget(sc)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

The key step here is passing the canvas axes in when calling the plot method on the DataFrameon the line df.plot(ax=sc.axes). You can use this same pattern to update the plot any time, although bear in mind that Pandas clears and redraws the entire canvas, meaning that it is not ideal for high performance plotting.

The resulting plot generated through Pandas is shown below -

Pandas plot embedded in PyQt5 Pandas plot embedded in PyQt5

Just as before, you can add the Matplotlib toolbar and control support to plots generated using Pandas, allowing you to zoom/pan and modify them live. The following code combines our earlier toolbar example with the Pandas example.

import sys
import matplotlib
matplotlib.use('Qt5Agg')

from PyQt5 import QtCore, QtWidgets

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure

import pandas as pd


class MplCanvas(FigureCanvasQTAgg):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        super(MplCanvas, self).__init__(fig)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        # Create the maptlotlib FigureCanvas object, 
        # which defines a single set of axes as self.axes.
        sc = MplCanvas(self, width=5, height=4, dpi=100)

        # Create our pandas DataFrame with some simple
        # data and headers.
        df = pd.DataFrame([
           [0, 10], [5, 15], [2, 20], [15, 25], [4, 10], 
        ], columns=['A', 'B'])

        # plot the pandas DataFrame, passing in the 
        # matplotlib Canvas axes.
        df.plot(ax=sc.axes)

        # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second.
        toolbar = NavigationToolbar(sc, self)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(toolbar)
        layout.addWidget(sc)

        # Create a placeholder widget to hold our toolbar and canvas.
        widget = QtWidgets.QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        self.show()


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()

Running this you should see the following window, showing a Pandas plot embedded in PyQt5 alongside the Matplotlib toolbar.

Pandas plot with Matplotlib toolbar Pandas plot with Matplotlib toolbar

What's next

In this tutorial we looked at how you can embed Matplotlib plots in your PyQt5 applications. Being able to use Matplotlib plots in your applications allows you to create custom data analysis and visualization tools from Python.

Matplotlib is a huge library and too big to cover in detail here. If you're not familiar with Matplotlib plotting and want to give it a try, take a look at the documentation and example plots to see what is possible. If you are familiar with it you should now be able to put those skills to work in your PyQt5 apps!

22 Jan 2020 9:57pm GMT

Python Circle: Hello Word in Django: How to start with Django

Creating a hello world Django app, Starting the development of Django application in less than 5 minutes, How to start development in python Django, your first application in Django

22 Jan 2020 4:46pm GMT

Python Circle: Hello Word in Django: How to start with Django

Creating a hello world Django app, Starting the development of Django application in less than 5 minutes, How to start development in python Django, your first application in Django

22 Jan 2020 4:46pm GMT

Catalin George Festila: Python 3.7.5 : Django security issues - part 003.

Let's update this subject today with another new tutorial. In the last tutorial about Django security I wrote about python package named django-axes. First, let's fix an old issue about a URL pattern that matches anything and expects an integer that generates errors like: ... File "/home/mythcat/.local/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 1772, in

22 Jan 2020 2:26pm GMT

Catalin George Festila: Python 3.7.5 : Django security issues - part 003.

Let's update this subject today with another new tutorial. In the last tutorial about Django security I wrote about python package named django-axes. First, let's fix an old issue about a URL pattern that matches anything and expects an integer that generates errors like: ... File "/home/mythcat/.local/lib/python3.7/site-packages/django/db/models/fields/__init__.py", line 1772, in

22 Jan 2020 2:26pm GMT

Davy Wybiral: ESP32-Cam Quickstart with Arduino Code





Learn how to add a camera to your Arduino projects the easy way using one of these cheap ESP32-Cam modules. Great for pet cams, home surveillance, time lapses, and computer vision applications.

22 Jan 2020 2:15pm GMT

Davy Wybiral: ESP32-Cam Quickstart with Arduino Code





Learn how to add a camera to your Arduino projects the easy way using one of these cheap ESP32-Cam modules. Great for pet cams, home surveillance, time lapses, and computer vision applications.

22 Jan 2020 2:15pm GMT

Real Python: Python GUI Programming With Tkinter

Python has a lot of GUI frameworks, but Tkinter is the only framework that's built into the Python standard library. Tkinter has several strengths. It's cross-platform, so the same code works on Windows, macOS, and Linux. Visual elements are rendered using native operating system elements, so applications built with Tkinter look like they belong on the platform where they're run.

Although Tkinter is considered the de-facto Python GUI framework, it's not without criticism. One notable criticism is that GUIs built with Tkinter look outdated. If you want a shiny, modern interface, then Tkinter may not be what you're looking for.

However, Tkinter is lightweight and relatively painless to use compared to other frameworks. This makes it a compelling choice for building GUI applications in Python, especially for applications where a modern sheen is unnecessary, and the top priority is to build something that's functional and cross-platform quickly.

In this tutorial, you'll learn how to:

Once you've mastered these skills by working through the exercises at the end of each section, you'll tie everything together by building two applications. The first is a temperature converter, and the second is a text editor. It's time to dive right in and see how to build an application with Tkinter!

Note: This tutorial is adapted from the chapter "Graphical User Interfaces" of Python Basics: A Practical Introduction to Python 3.

The book uses Python's built-in IDLE editor to create and edit Python files and interact with the Python shell. In this tutorial, references to IDLE have been removed in favor of more general language.

The bulk of the material in this tutorial has been left unchanged, and you should have no problems running the example code from the editor and environment of your choice.

Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you'll need to take your Python skills to the next level.

Building Your First Python GUI Application With Tkinter

The foundational element of a Tkinter GUI is the window. Windows are the containers in which all other GUI elements live. These other GUI elements, such as text boxes, labels, and buttons, are known as widgets. Widgets are contained inside of windows.

First, create a window that contains a single widget. Start up a new Python shell session and follow along!

Note: The code examples in this tutorial have all been tested on Windows, macOS, and Ubuntu Linux 18.04 with Python versions 3.6, 3.7, and 3.8.

If you've installed Python with the official installers available for Windows and macOS from python.org, then you should have no problem running the sample code. You can safely skip the rest of this note and continue with the tutorial!

If you haven't installed Python with the official installers, or there's no official distribution for your system, then here are some tips for getting up and going.

Python on macOS with Homebrew:

The Python distribution for macOS available on Homebrew does not come bundled with the Tcl/Tk dependency Tkinter. The default system version is used instead. This version may be outdated and prevent you from importing the Python GUI Tkinter module. To avoid this problem, use the official macOS installer.

Ubuntu Linux 16.04:

The latest version of Python available in the Ubuntu Linux 16.04 Universe repository is 3.5. You can install the latest version with the deadsnakes PPA. Here are the commands to set up the PPA and download the latest version of Python with the correct Tcl/Tk version:

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt-get update
$ sudo apt-get install python3.8 python3-tk

The first two commands add the deadsnakes PPA to your system's repository list, and the last command installs Python 3.8 and the Python GUI Tkinter module.

Ubuntu Linux 18.04:

You can install the latest version of Python with the correct Tcl/Tk version from the Universe repository with the following command:

$ sudo apt-get install python3.8 python3-tk

This installs Python 3.8, as well as the Python GUI Tkinter module.

Other Linux Flavors:

If you're unable to get a working Python installation on your flavor of Linux, then you can build Python with the correct version of Tcl/Tk from the source code. For a step-by-step walkthrough of this process, check out the Python 3 Installation & Setup Guide.

With your Python shell open, the first thing you need to do is import the Python GUI Tkinter module:

>>>
>>> import tkinter as tk

A window is an instance of Tkinter's Tk class. Go ahead and create a new window and assign it to the variable window:

>>>
>>> window = tk.Tk()

When you execute the above code, a new window pops up on your screen. How it looks depends on your operating system:

A blank Tkinter application window on Windows 10, macOS, and Ubuntu Linux

Throughout the rest of this tutorial, you'll see Windows screenshots.

Adding a Widget

Now that you have a window, you can add a widget. Use the tk.Label class to add some text to a window. Create a Label widget with the text "Hello, Tkinter" and assign it to a variable called greeting:

>>>
>>> greeting = tk.Label(text="Hello, Tkinter")

The window you created earlier doesn't change. You just created a Label widget, but you haven't added it to the window yet. There are several ways to add widgets to a window. Right now, you can use the Label widget's .pack() method:

>>>
>>> greeting.pack()

The window now looks like this:

Example

When you .pack() a widget into a window, Tkinter sizes the window as small as it can while still fully encompassing the widget. Now execute the following:

>>>
>>> window.mainloop()

Nothing seems to happen, but notice that a new prompt does not appear in the shell.

window.mainloop() tells Python to run the Tkinter event loop. This method listens for events, such as button clicks or keypresses, and blocks any code that comes after it from running until the window it's called on is closed. Go ahead and close the window you've created, and you'll see a new prompt displayed in the shell.

Warning: When you work with Tkinter from a Python REPL, updates to windows are applied as each line is executed. This is not the case when a Tkinter program is executed from a Python file!

If you don't include window.mainloop() at the end of a program in a Python file, then the Tkinter application will never run, and nothing will be displayed.

Creating a window with Tkinter only takes a couple of lines of code. But blank windows aren't very useful! In the next section, you'll learn about some of the widgets available in Tkinter, and how you can customize them to meet your application's needs.

Check Your Understanding

Expand the code blocks below to check your understanding:

Write a full Python script that creates a Tkinter window with the text "Python rocks!".

The window should look like this:

A Tkinter window containing the text

Try this exercise now.

You can expand the code block below to see a solution:

Here's one possible solution:

import tkinter as tk

window = tk.Tk()
label = tk.Label(text="Python rocks!")
label.pack()

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Working With Widgets

Widgets are the bread and butter of the Python GUI framework Tkinter. They are the elements through which users interact with your program. Each widget in Tkinter is defined by a class. Here are some of the widgets available:

Widget Class Description
Label A widget used to display text on the screen
Button A button that can contain text and can perform an action when clicked
Entry A text entry widget that allows only a single line of text
Text A text entry widget that allows multiline text entry
Frame A rectangular region used to group related widgets or provide padding between widgets

You'll see how to work with each of these in the following sections. Note that Tkinter has many more widgets than the ones listed here. For a full list, check out Basic Widgets and More Widgets in the TkDocs tutorial. For now, take a closer look at the Label widget.

Displaying Text and Images With Label Widgets

Label widgets are used to display text or images. The text displayed by a Label widget can't be edited by the user. It's for display purposes only. As you saw in the example at the beginning of this tutorial, you can create a Label widget by instantiating the Label class and passing a string to the text parameter:

label = tk.Label(text="Hello, Tkinter")

Label widgets display text with the default system text color and the default system text background color. These are typically black and white, respectively, but you may see different colors if you have changed these settings in your operating system.

You can control Label text and background colors using the foreground and background parameters:

label = tk.Label(
    text="Hello, Tkinter",
    foreground="white",  # Set the text color to white
    background="black"  # Set the background color to black
)

There are numerous valid color names, including:

Many of the HTML color names work with Tkinter. A chart with most of the valid color names is available here. For a full reference, including macOS and Windows-specific system colors that are controlled by the current system theme, check out the colors manual page.

You can also specify a color using hexadecimal RGB values:

label = tk.Label(text="Hello, Tkinter", background="#34A2FE")

This sets the label background to a nice, light blue color. Hexadecimal RGB values are more cryptic than named colors, but they're also more flexible. Fortunately, there are tools available that make getting hexadecimal color codes relatively painless.

If you don't feel like typing out foreground and background all the time, then you can use the shorthand fg and bg parameters to set the foreground and background colors:

label = tk.Label(text="Hello, Tkinter", fg="white", bg="black")

You can also control the width and height of a label with the width and height parameters:

label = tk.Label(
    text="Hello, Tkinter",
    fg="white",
    bg="black",
    width=10,
    height=10
)

Here's what this label looks like in a window:

A Tkinter window containing a button with a black background and white text that reads

It may seem strange that the label in the window isn't square even though the width and height are both set to 10. This is because the width and height are measured in text units. One horizontal text unit is determined by the width of the character "0", or the number zero, in the default system font. Similarly, one vertical text unit is determined by the height of the character "0".

Note: Tkinter uses text units for width and height measurements, instead of something like inches, centimeters, or pixels, to ensure consistent behavior of the application across platforms.

Measuring units by the width of a character means that the size of a widget is relative to the default font on a user's machine. This ensures the text fits properly in labels and buttons, no matter where the application is running.

Labels are great for displaying some text, but they don't help you get input from a user. The next three widgets that you'll look at are all used to get user input.

Displaying Clickable Buttons With Button Widgets

Button widgets are used to display clickable buttons. They can be configured to call a function whenever they're clicked. You'll cover how to call functions from button clicks in the next section. For now, take a look at how to create and style a Button.

There are many similarities between Button and Label widgets. In many ways, a Button is just a Label that you can click! The same keyword arguments you use to create and style a Label will work with Button widgets. For example, the following code creates a Button with a blue background and yellow text. It also sets the width and height to 25 and 5 text units, respectively:

button = tk.Button(
    text="Click me!",
    width=25,
    height=5,
    bg="blue",
    fg="yellow",
)

Here's what the button looks like in a window:

A Tkinter window containing a button with a blue background and yellow text that reads

Pretty nifty! The next two widgets you'll see are used to collect text input from a user.

Getting User Input With Entry Widgets

When you need to get a little bit of text from a user, like a name or an email address, use an Entry widget. They display a small text box that the user can type some text into. Creating and styling an Entry widget works pretty much exactly like Label and Button widgets. For example, the following code creates a widget with a blue background, some yellow text, and a width of 50 text units:

entry = tk.Entry(fg="yellow", bg="blue", width=50)

The interesting bit about Entry widgets isn't how to style them, though. It's how to use them to get input from a user. There are three main operations that you can perform with Entry widgets:

  1. Retrieving text with .get()
  2. Deleting text with .delete()
  3. Inserting text with .insert()

The best way to get an understanding of Entry widgets is to create one and interact with it. Open up a Python shell and follow along with the examples in this section. First, import tkinter and create a new window:

>>>
>>> import tkinter as tk
>>> window = tk.Tk()

Now create a Label and an Entry widget:

>>>
>>> label = tk.Label(text="Name")
>>> entry = tk.Entry()

The Label describes what sort of text should go in the Entry widget. It doesn't enforce any sort of requirements on the Entry, but it tells the user what your program expects them to put there. You need to .pack() the widgets into the window so that they're visible:

>>>
>>> label.pack()
>>> entry.pack()

Here's what that looks like:

A Tkinter window containing an Entry widget withe Label

Notice that Tkinter automatically centers the Label above the Entry widget in the window. This is a feature of .pack(), which you'll learn more about in later sections.

Click inside the Entry widget with your mouse and type "Real Python":

A Tkinter window containing an Entry widget with the text

Now you've got some text entered into the Entry widget, but that text hasn't been sent to your program yet. You can use .get() to retrieve the text and assign it to a variable called name:

>>>
>>> name = entry.get()
>>> name
'Real Python'

You can .delete() text as well. This method takes an integer argument that tells Python which character to remove. For example, the code block below shows how .delete(0) deletes the first character from the Entry:

>>>
>>> entry.delete(0)

The text remaining in the widget is now "eal Python":

A Tkinter window containing an Entry widget with the text

Note that, just like Python string objects, text in an Entry widget is indexed starting with 0.

If you need to remove several characters from an Entry, then pass a second integer argument to .delete() indicating the index of the character where deletion should stop. For example, the following code deletes the first four letters in the Entry:

>>>
>>> entry.delete(0, 4)

The remaining text now reads "Python":

A Tkinter window containing an Entry widget with the text

Entry.delete() works just like string slicing. The first argument determines the starting index, and the deletion continues up to but not including the index passed as the second argument. Use the special constant tk.END for the second argument of .delete() to remove all text in an Entry:

>>>
>>> entry.delete(0, tk.END)

You'll now see a blank text box:

A Tkinter window containing an Entry widget withe Label

On the opposite end of the spectrum, you can also .insert() text into an Entry widget:

>>>
>>> entry.insert(0, "Python")

The window now looks like this:

A Tkinter window containing an Entry widget with the text

The first argument tells .insert() where to insert the text. If there is no text in the Entry, then the new text will always be inserted at the beginning of the widget, no matter what value you pass as the first argument. For example, calling .insert() with 100 as the first argument instead of 0, as you did above, would have generated the same output.

If an Entry already contains some text, then .insert() will insert the new text at the specified position and shift all existing text to the right:

>>>
>>> entry.insert(0, "Real ")

The widget text now reads "Real Python":

A Tkinter window containing an Entry widget with the text

Entry widgets are great for capturing small amounts of text from a user, but because they're only displayed on a single line, they're not ideal for gathering large amounts of text. That's where Text widgets come in!

Getting Multiline User Input With Text Widgets

Text widgets are used for entering text, just like Entry widgets. The difference is that Text widgets may contain multiple lines of text. With a Text widget, a user can input a whole paragraph or even several pages of text! Just like Entry widgets, there are three main operations you can perform with Text widgets:

  1. Retrieve text with .get()
  2. Delete text with .delete()
  3. Insert text with .insert()

Although the method names are the same as the Entry methods, they work a bit differently. It's time to get your hands dirty by creating a Text widget and seeing what all it can do.

Note: Do you still have the window from the previous section open?

If so, then you can close it by executing the following:

>>>
>>> window.destroy()

You can also close it manually by clicking the Close button.

In your Python shell, create a new blank window and .pack() a Text() widget into it:

>>>
>>> window = tk.Tk()
>>> text_box = tk.Text()
>>> text_box.pack()

Text boxes are much larger than Entry widgets by default. Here's what the window created above looks like:

A Tkinter window containing a Text Box widget

Click anywhere inside the window to activate the text box. Type in the word "Hello". Then press Enter and type "World" on the second line. The window should now look like this:

A Tkinter window containing a Text Box widget with the text

Just like Entry widgets, you can retrieve the text from a Text widget using .get(). However, calling .get() with no arguments doesn't return the full text in the text box like it does for Entry widgets. It raises an exception:

>>>
>>> text_box.get()
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    text_box.get()
TypeError: get() missing 1 required positional argument: 'index1'

Text.get() required at least one argument. Calling .get() with a single index returns a single character. To retrieve several characters, you need to pass a start index and an end index. Indices in Text widgets work differently than Entry widgets. Since Text widgets can have several lines of text, an index must contain two pieces of information:

  1. The line number of a character
  2. The position of a character on that line

Line numbers start with 1, and character positions start with 0. To make an index, you create a string of the form "<line>.<char>", replacing <line> with the line number and <char> with the character number. For example, "1.0" represents the first character on the first line, and "2.3" represents the fourth character on the second line.

Use the index "1.0" to get the first letter from the text box you created earlier:

>>>
>>> text_box.get("1.0")
'H'

There are five letters in the word "Hello", and the character number of o is 4, since character numbers start from 0, and the word "Hello" starts at the first position in the text box. Just like Python string slices, in order to get the entire word "Hello" from the text box, the end index must be one more than the index of the last character to be read.

So, to get the word "Hello" from the text box, use "1.0" for the first index and "1.5" for the second index:

>>>
>>> text_box.get("1.0", "1.5")
'Hello'

To get the word "World" on the second line of the text box, change the line numbers in each index to 2:

>>>
>>> text_box.get("2.0", "2.5")
'World'

To get all of the text in a text box, set the starting index in "1.0" and use the special tk.END constant for the second index:

>>>
>>> text_box.get("1.0", tk.END)
'Hello\nWorld\n'

Notice that text returned by .get() includes any newline characters. You can also see from this example that every line in a Text widget has a newline character at the end, including the last line of text in the text box.

.delete() is used to delete characters from a text box. It work just like .delete() for Entry widgets. There are two ways to use .delete():

  1. With a single argument
  2. With two arguments

Using the single-argument version, you pass to .delete() the index of a single character to be deleted. For example, the following deletes the first character H from the text box:

>>>
>>> text_box.delete("1.0")

The first line of text in the window now reads "ello":

A Tkinter window containing a Text Box widget with the text

With the two-argument version, you pass two indices to delete a range of characters starting at the first index and up to, but not including, the second index.

For example, to delete the remaining "ello" on the first line of the text box, use the indices "1.0" and "1.4":

>>>
>>> text_box.delete("1.0", "1.4")

Notice that the text is gone from the first line. This leaves a blank line followed the word World on the second line:

A Tkinter window containing a Text Box widget with a blank first line and the text

Even though you can't see it, there's still a character on the first line. It's a newline character! You can verify this using .get():

>>>
>>> text_box.get("1.0")
'\n'

If you delete that character, then the rest of the contents of the text box will shift up a line:

>>>
>>> text_box.delete("1.0")

Now, "World" is on the first line of the text box:

A Tkinter window containing a Text Box widget with the text

Try to clear out the rest of the text in the text box. Set "1.0" as the start index and use tk.END for the second index:

>>>
>>> text_box.delete("1.0", tk.END)

The text box is now empty:

A Tkinter window containing a Text Box widget

You can insert text into a text box using .insert():

>>>
>>> text_box.insert("1.0", "Hello")

This inserts the word "Hello" at the beginning of the text box, using the same "<line>.<column>" format used by .get() to specify the insertion position:

A Tkinter window containing a Text Box widget with the text

Check out what happens if you try to insert the word "World" on the second line:

>>>
>>> text_box.insert("2.0", "World")

Instead of inserting the text on the second line, the text is inserted at the end of the first line:

A Tkinter window containing a Text Box widget with the text

If you want to insert text onto a new line, then you need to insert a newline character manually into the string being inserted:

>>>
>>> text_box.insert("2.0", "\nWorld")

Now "World" is on the second line of the text box:

A Tkinter window containing a Text Box widget with the text

.insert() will do one of two things:

  1. Insert text at the specified position if there's already text at or after that position.
  2. Append text to the specified line if the character number is greater than the index of the last character in the text box.

It's usually impractical to try and keep track of what the index of the last character is. The best way to insert text at the end of a Text widget is to pass tk.END to the first parameter of .insert():

text_box.insert(tk.END, "Put me at the end!")

Don't forget to include the newline character (\n) at the beginning of the text if you want to put it on a new line:

text_box.insert(tk.END, "\nPut me on a new line!")

Label, Button, Entry, and Text widgets are just a few of the widgets available in Tkinter. There are several others, including widgets for checkboxes, radio buttons, scroll bars, and progress bars. For more information on all of the available widgets, see the Additional Widgets list in the Additional Resources section.

Assigning Widgets to Frames With Frame Widgets

In this tutorial, you're going to work with only five widgets. These are the four you've seen so far plus the Frame widget. Frame widgets are important for organizing the layout of your widgets in an application.

Before you get into the details about laying out the visual presentation of your widgets, take a closer look at how Frame widgets work, and how you can assign other widgets to them. The following script creates a blank Frame widget and assigns it to the main application window:

import tkinter as tk

window = tk.Tk()
frame = tk.Frame()
frame.pack()

window.mainloop()

frame.pack() packs the frame into the window so that the window sizes itself as small as possible to encompass the frame. When you run the above script, you get some seriously uninteresting output:

A Tkinter window containing an empty Frame widget

An empty Frame widget is practically invisible. Frames are best thought of as containers for other widgets. You can assign a widget to a frame by setting the widget's master attribute:

frame = tk.Frame()
label = tk.Label(master=frame)

To get a feel for how this works, write a script that creates two Frame widgets called frame_a and frame_b. In this script, frame_a contains a label with the text "I'm in Frame A", and frame_b contains the label "I'm in Frame B". Here's one way to do this:

import tkinter as tk

window = tk.Tk()

frame_a = tk.Frame()
frame_b = tk.Frame()

label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()

label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()

frame_a.pack()
frame_b.pack()

window.mainloop()

Note that frame_a is packed into the window before frame_b. The window that opens shows the label in frame_a above the label in frame_b:

A Tkinter window containg two Frame widgets stacked vertically, with the text

Now see what happens when you swap the order of frame_a.pack() and frame_b.pack():

import tkinter as tk

window = tk.Tk()

frame_a = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()

frame_b = tk.Frame()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()

# Swap the order of `frame_a` and `frame_b`
frame_b.pack()
frame_a.pack()

window.mainloop()

The output looks like this:

A Tkinter window containg two Frame widgets stacked vertically, with the text

Now label_b is on top. Since label_b is assigned to frame_b, it moves to wherever frame_b is positioned.

All four of the widget types you've learned about-Label, Button, Entry, and Text-have a master attribute that's set when you instantiate them. That way, you can control which Frame a widget is assigned to. Frame widgets are great for organizing other widgets in a logical manner. Related widgets can be assigned to the same frame so that, if the frame is ever moved in the window, then the related widgets stay together.

In addition to grouping your widgets logically, Frame widgets can add a little flare to the visual presentation of your application. Read on to see how to create various borders for Frame widgets.

Adjusting Frame Appearance With Reliefs

Frame widgets can be configured with a relief attribute that creates a border around the frame. You can set relief to be any of the following values:

To apply the border effect, you must set the borderwidth attribute to a value greater than 1. This attribute adjusts the width of the border in pixels. The best way to get a feel for what each effect looks like is to see them for yourself. Here's a script that packs five Frame widgets into a window, each with a different value for the relief argument:

 1 import tkinter as tk
 2 
 3 border_effects = {
 4     "flat": tk.FLAT,
 5     "sunken": tk.SUNKEN,
 6     "raised": tk.RAISED,
 7     "groove": tk.GROOVE,
 8     "ridge": tk.RIDGE,
 9 }
10 
11 window = tk.Tk()
12 
13 for relief_name, relief in border_effects.items():
14     frame = tk.Frame(master=window, relief=relief, borderwidth=5)
15     frame.pack(side=tk.LEFT)
16     label = tk.Label(master=frame, text=relief_name)
17     label.pack()
18 
19 window.mainloop()

Here's a breakdown of this script:

The window produced by the above script looks like this:

A Tkinter window containing 5 Frame widgets, each with one of the five relief values: tk.FLAT, tk.SUNKET, tk.RAISED, tk.GROOVE, and tk.RIDGE

In this image, you can see the following effects:

These effects give your Python GUI Tkinter application a bit of visual appeal.

Understanding Widget Naming Conventions

When you create a widget, you can give it any name you like, as long as it's a valid Python identifier. It's usually a good idea to include the name of the widget class in the variable name you assign to the widget instance. For example, if a Label widget is used to display a user's name, then you might name the widget label_user_name. An Entry widget used to collect a user's age might be called entry_age.

When you include the widget class name in the variable name, you help yourself (and anyone else that needs to read your code) to understand what type of widget the variable name refers to. However, using the full name of the widget class can lead to long variable names, so you may want to adopt a shorthand for referring to each widget type. For the rest of this tutorial, you'll use the following shorthand prefixes to name widgets:

Widget Class Variable Name Prefix Example
Label lbl lbl_name
Button btn btn_submit
Entry ent ent_age
Text txt txt_notes
Frame frm frm_address

In this section, you learned how to create a window, use widgets, and work with frames. At this point, you can make some plain windows that display messages, but you've yet to create a full-blown application. In the next section, you'll learn how to control the layout of your applications using Tkinter's powerful geometry managers.

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Write a complete script that displays an Entry widget that's 40 text units wide and has a white background and black text. Use .insert() to display text in the widget that reads "What is your name?".

The output window should look like this:

A Tkinter window containing an Entry widget with the text

Try this exercise now.

You can expand the code block below to see a solution:

There are a couple of ways to solve this exercise. Here's one solution that uses the bg and fg parameters to set the Entry widget's background and foreground colors:

import tkinter as tk

window = tk.Tk()

entry = tk.Entry(width=40, bg="white", fg="black")
entry.pack()

entry.insert(0, "What is your name?")

window.mainloop()

This solution is great because it explicitly sets the background and foreground colors for the Entry widget.

On most systems, the default background color for an Entry widget is white, and the default foreground color is black. So, you might be able to generate the same window with the bg and fg parameters left out:

import tkinter as tk

window = tk.Tk()

entry = tk.Entry(width=40)
entry.pack()

entry.insert(0, "What is your name?")

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Controlling Layout With Geometry Managers

Up until now, you've been adding widgets to windows and Frame widgets using .pack(), but you haven't learned what exactly this method does. Let's clear things up! Application layout in Tkinter is controlled with geometry managers. While .pack() is an example of a geometry manager, it isn't the only one. Tkinter has two others:

Each window and Frame in your application can use only one geometry manager. However, different frames can use different geometry managers, even if they're assigned to a frame or window using another geometry manager. Start by taking a closer look at .pack().

The .pack() Geometry Manager

.pack() uses a packing algorithm to place widgets in a Frame or window in a specified order. For a given widget, the packing algorithm has two primary steps:

  1. Compute a rectangular area called a parcel that's just tall (or wide) enough to hold the widget and fills the remaining width (or height) in the window with blank space.
  2. Center the widget in the parcel unless a different location is specified.

.pack() is powerful, but it can be difficult to visualize. The best way to get a feel for .pack() is to look at some examples. See what happens when you .pack() three Label widgets into a Frame:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=100, height=100, bg="red")
frame1.pack()

frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()

frame3 = tk.Frame(master=window, width=25, height=25, bg="blue")
frame3.pack()

window.mainloop()

.pack() places each Frame below the previous one by default, in the order that they're assigned to the window:

A Tkinter window with three colored squares packed vertically

Each Frame is placed at the top-most available position. The red Frame is placed at the top of the window. Then the yellow Frame is placed just below the red one and the blue Frame just below the yellow one.

There are three invisible parcels containing each of the three Frame widgets. Each parcel is as wide as the window and as tall as the Frame that it contains. Since no anchor point was specified when .pack() was called for each Frame, they're all centered inside of their parcels. That's why each Frame is centered in the window.

.pack() accepts some keyword arguments for more precisely configuring widget placement. For example, you can set the fill keyword argument to specify in which direction the frames should fill. The options are tk.X to fill in the horizontal direction, tk.Y to fill vertically, and tk.BOTH to fill in both directions. Here's how you would stack the three frames so that each one fills the whole window horizontally:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, height=100, bg="red")
frame1.pack(fill=tk.X)

frame2 = tk.Frame(master=window, height=50, bg="yellow")
frame2.pack(fill=tk.X)

frame3 = tk.Frame(master=window, height=25, bg="blue")
frame3.pack(fill=tk.X)

window.mainloop()

Notice that the width is not set on any of the Frame widgets. width is no longer necessary because each frame sets .pack() to fill horizontally, overriding any width you may set.

The window produced by this script looks like this:

A Tkinter window containing three colored frames packed vertically and expanded horizontally to fill the entire window

One of the nice things about filling the window with .pack() is that the fill is responsive to window resizing. Try widening the window generated by the previous script to see how this works. As you widen the window, the width of the three Frame widgets grow to fill the window:

A Tkinter window that expands horizontally with window resizing

Notice, though, that the Frame widgets don't expand in the vertical direction.

The side keyword argument of .pack() specifies on which side of the window the widget should be placed. These are the available options:

If you don't set side, then .pack() will automatically use tk.TOP and place new widgets at the top of the window, or at the top-most portion of the window that isn't already occupied by a widget. For example, the following script places three frames side-by-side from left to right and expands each frame to fill the window vertically:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.Y, side=tk.LEFT)

frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.Y, side=tk.LEFT)

frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.Y, side=tk.LEFT)

window.mainloop()

This time, you have to specify the height keyword argument on at least one of the frames to force the window to have some height.

The resulting window looks like this:

A Tkinter window containing three colored frames packed horizontally and expanded vertically to fill the entire window

Just like when you set fill=tk.X to make the frames responsive when you resized the window horizontally, you can set fill=tk.Y to make the frames responsive when you resize the window vertically:

A Tkinter window that expands vertically with window resizing

To make the layout truly responsive, you can set an initial size for your frames using the width and height attributes. Then, set the fill keyword argument of .pack() to tk.BOTH and set the expand keyword argument to True:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

window.mainloop()

When you run the above script, you'll see a window that initially looks the same as the one you generated in the previous example. The difference is that now you can resize the window however you want and the frames will expand and fill the window responsively:

A Tkinter window that expands both horizontally and vertically with window resizing

Pretty cool!

The .place() Geometry Manager

You can use .place() to control the precise location that a widget should occupy in a window or Frame. You must provide two keyword arguments, x and y, which specify the x- and y-coordinates for the top-left corner of the widget. Both x and y are measured in pixels, not text units.

Keep in mind that the origin (where x and y are both 0) is the top-left corner of the Frame or window. So, you can think of the y argument of .place() as the number of pixels from the top of the window, and the x argument as the number of pixels from the left of the window.

Here's an example of how the .place() geometry manager works:

 1 import tkinter as tk
 2 
 3 window = tk.Tk()
 4 
 5 frame = tk.Frame(master=window, width=150, height=150)
 6 frame.pack()
 7 
 8 label1 = tk.Label(master=frame, text="I'm at (0, 0)", bg="red")
 9 label1.place(x=0, y=0)
10 
11 label2 = tk.Label(master=frame, text="I'm at (75, 75)", bg="yellow")
12 label2.place(x=75, y=75)
13 
14 window.mainloop()

Here's how this code works:

Here's the window the code produces:

A Tkinter window containing two Label widgets laid out using the .place() geometry manager

.place() is not used often. It has two main drawbacks:

  1. Layout can be difficult to manage with .place(). This is especially true if your application has lots of widgets.
  2. Layouts created with .place() are not responsive. They don't change as the window is resized.

One of the main challenges of cross-platform GUI development is making layouts that look good no matter which platform they are viewed on, and .place() is a poor choice for making responsive and cross-platform layouts.

That's not to say .place() should never be used! In some cases, it might be just what you need. For example, if you're creating a GUI interface for a map, then .place() might be the perfect choice to ensure widgets are placed at the correct distance from each other on the map.

.pack() is usually a better choice than .place(), but even .pack() has some downsides. The placement of widgets depends on the order in which .pack() is called, so it can be difficult to modify existing applications without fully understanding the code controlling the layout. The .grid() geometry manager solves a lot of these issues, as you'll see in the next section.

The .grid() Geometry Manager

The geometry manager you'll likely use most often is .grid(), which provides all the power of .pack() in a format that's easier to understand and maintain.

.grid() works by splitting a window or Frame into rows and columns. You specify the location of a widget by calling .grid() and passing the row and column indices to the row and column keyword arguments, respectively. Both row and column indices start at 0, so a row index of 1 and a column index of 2 tells .grid() to place a widget in the third column of the second row.

The following script creates a 3 × 3 grid of frames with Label widgets packed into them:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Here's what the resulting window looks like:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them

Two geometry managers are being used in this example. Each Frame is attached to the window with the .grid() geometry manager:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Each label is attached to its master Frame with .pack():

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

The important thing to realize here is that even though .grid() is called on each Frame object, the geometry manager applies to the window object. Similarly, the layout of each frame is controlled with the .pack() geometry manager.

The frames in the previous example are placed tightly next to one another. To add some space around each Frame, you can set the padding of each cell in the grid. Padding is just some blank space that surrounds a widget and separates it visually from its contents.

The two types of padding are external and internal padding. External padding adds some space around the outside of a grid cell. It's controlled with two keyword arguments to .grid():

  1. padx adds padding in the horizontal direction.
  2. pady adds padding in the vertical direction.

Both padx and pady are measured in pixels, not text units, so setting both of them to the same value will create the same amount of padding in both directions. Try to add some padding around the outside of the frames in the previous example:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Here's the resulting window:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them. Each grid cell has 5 pixels of exterior padding.

.pack() also has padx and pady parameters. The following code is nearly identical to the previous code, except that you add 5 pixels of additional padding around each Label in both the x and y directions:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)

        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack(padx=5, pady=5)

window.mainloop()

The extra padding around the Label widgets gives each cell in the grid a little bit of breathing room between the Frame border and the text in the Label:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them. Each grid cell and Label widget has 5 pixels of exterior padding.

That looks pretty nice! But if you try and expand the window in any direction, then you'll notice that the layout isn't very responsive:

A Tkinter window containing a 3 x 3 grid that does not expand with window resizing

The whole grid stays at the top-left corner as the window expands.

You can adjust how the rows and columns of the grid grow as the window is resized using .columnconfigure() and .rowconfigure() on the window object. Remember, the grid is attached to window, even though you're calling .grid() on each Frame widget. Both .columnconfigure() and .rowconfigure() take three essential arguments:

  1. The index of the grid column or row that you want to configure (or a list of indices to configure multiple rows or columns at the same time)
  2. A keyword argument called weight that determines how the column or row should respond to window resizing, relative to the other columns and rows
  3. A keyword argument called minsize that sets the minimum size of the row height or column width in pixels

weight is set to 0 by default, which means that the column or row doesn't expand as the window resizes. If every column and row is given a weight of 1, then they all grow at the same rate. If one column has a weight of 1 and another a weight of 2, then the second column expands at twice the rate of the first. Adjust the previous script to better handle window resizing:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    window.columnconfigure(i, weight=1, minsize=75)
    window.rowconfigure(i, weight=1, minsize=50)

    for j in range(0, 3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)

        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack(padx=5, pady=5)

window.mainloop()

.columnconfigure() and .rowconfigure() are placed in the body of the outer for loop. (You could explicitly configure each column and row outside of the for loop, but that would require writing an additional six lines of code.)

On each iteration of the loop, the i-th column and row are configured to have a weight of 1. This ensures that each row and column expands at the same rate whenever the window is resized. The minsize argument is set to 75 for each column and 50 for each row. This makes sure the Label widget always displays its text without chopping off any characters, even if the window size is extremely small.

The result is a grid layout that expands and contracts smoothly as the window is resized:

A Tkinter window containing a fully responsive 3 x 3 grid layout

Try it yourself to get a feel for how it works! Play around with the weight and minsize parameters to see how they affect the grid.

By default, widgets are centered in their grid cells. For example, the following code creates two Label widgets and places them in a grid with one column and two rows:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0)

label2 = tk.Label(text="B")
label2.grid(row=1, column=0)

window.mainloop()

Each grid cell is 250 pixels wide and 100 pixels tall. The labels are placed in the center of each cell, as you can see in the following figure:

A Tkinter window with grid geometry manager and custom row and column sizes

You can change the location of each label inside of the grid cell using the sticky parameter. sticky accepts a string containing one or more of the following letters:

The letters "n", "s", "e", and "w" come from the cardinal directions north, south, east, and west. Setting sticky to "n" on both Labels in the previous code positions each Label at the top-center of its grid cell:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="n")

label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="n")

window.mainloop()

Here's the output:

A Tkinter window with grid geometry manager and sticky set to

You can combine multiple letters in a single string to position each Label in the corner of its grid cell:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="ne")

label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="sw")

window.mainloop()

In this example, the sticky parameter of label1 is set to "ne", which places the label at the top-right corner of its grid cell. label2 is positioned in the bottom-left corner by passing "sw" to sticky. Here's what that looks like in the window:

A Tkinter window with grid geometry manager and sticky set to

When a widget is positioned with sticky, the size of the widget itself is just big enough to contain any text and other contents inside of it. It won't fill the entire grid cell. In order to fill the grid, you can specify "ns" to force the widget to fill the cell in the vertical direction, or "ew" to fill the cell in the vertical direction. To fill the entire cell, set sticky to "nsew". The following example illustrates each of these options:

import tkinter as tk

window = tk.Tk()

window.rowconfigure(0, minsize=50)
window.columnconfigure([0, 1, 2, 3], minsize=50)

label1 = tk.Label(text="1", bg="black", fg="white")
label2 = tk.Label(text="2", bg="black", fg="white")
label3 = tk.Label(text="3", bg="black", fg="white")
label4 = tk.Label(text="4", bg="black", fg="white")

label1.grid(row=0, column=0)
label2.grid(row=0, column=1, sticky="ew")
label3.grid(row=0, column=2, sticky="ns")
label4.grid(row=0, column=3, sticky="nsew")

window.mainloop()

Here's what the output looks like:

A Tkinter window with grid geometry manager and sticky used to fill horizontally, vertically, and along both axes.

What the above example illustrates is that the .grid() geometry manager's sticky parameter can be used to achieve the same effects as the .pack() geometry manager's fill parameter. The correspondence between the sticky and fill parameters is summarized in the following table:

.grid() .pack()
sticky="ns" fill=tk.Y
sticky="ew" fill=tk.X
sticky="nsew" fill=tk.BOTH

.grid() is a powerful geometry manager. It's often easier to understand than .pack() and is much more flexible than .place(). When you're creating new Tkinter applications, you should consider using .grid() as your primary geometry manager.

Note: .grid() offers much more flexibility than you've seen here. For example, you can configure cells to span multiple rows and columns. For more information, check out the Grid Geometry Manager section of the TkDocs tutorial.

Now that you've got the fundamentals of geometry managers down for the Python GUI framework Tkinter, the next step is to assign actions to buttons to bring your applications to life.

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Below is an image of an address entry form made with Tkinter.

An address entry form window built with Tkinter

Write a complete script that re-creates the window. You may use any geometry manager you like.

You can expand the code block below to see a solution:

There are many different ways to solve this exercise. If your solution generates a window identical to the one in the exercise statement, then congratulations! You've successfully solved the exercise! Below, you can look at two solutions that use the .grid() geometry manager.

One solution creates a Label and Entry widget for each field with the desired settings:

import tkinter as tk

# Create a new window with the title "Address Entry Form"
window = tk.Tk()
window.title("Address Entry Form")

# Create a new frame `frm_form` to contain the Label
# and Entry widgets for entering address information.
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
# Pack the frame into the window
frm_form.pack()

# Create the Label and Entry widgets for "First Name"
lbl_first_name = tk.Label(master=frm_form, text="First Name:")
ent_first_name = tk.Entry(master=frm_form, width=50)
# Use the grid geometry manager to place the Label and
# Entry widgets in the first and second columns of the
# first row of the grid
lbl_first_name.grid(row=0, column=0, sticky="e")
ent_first_name.grid(row=0, column=1)

# Create the Label and Entry widgets for "Last Name"
lbl_last_name = tk.Label(master=frm_form, text="Last Name:")
ent_last_name = tk.Entry(master=frm_form, width=50)
# Place the widgets in the second row of the grid
lbl_last_name.grid(row=1, column=0, sticky="e")
ent_last_name.grid(row=1, column=1)

# Create the Label and Entry widgets for "Address Line 1"
lbl_address1 = tk.Label(master=frm_form, text="Address Line 1:")
ent_address1 = tk.Entry(master=frm_form, width=50)
# Place the widgets in the third row of the grid
lbl_address1.grid(row=2, column=0, sticky="e")
ent_address1.grid(row=2, column=1)

# Create the Label and Entry widgets for "Address Line 2"
lbl_address2 = tk.Label(master=frm_form, text="Address Line 2:")
ent_address2 = tk.Entry(master=frm_form, width=5)
# Place the widgets in the fourth row of the grid
lbl_address2.grid(row=3, column=0, sticky=tk.E)
ent_address2.grid(row=3, column=1)

# Create the Label and Entry widgets for "City"
lbl_city = tk.Label(master=frm_form, text="City:")
ent_city = tk.Entry(master=frm_form, width=50)
# Place the widgets in the fifth row of the grid
lbl_city.grid(row=4, column=0, sticky=tk.E)
ent_city.grid(row=4, column=1)

# Create the Label and Entry widgets for "State/Province"
lbl_state = tk.Label(master=frm_form, text="State/Province:")
ent_state = tk.Entry(master=frm_form, width=50)
# Place the widgets in the sixth row of the grid
lbl_state.grid(row=5, column=0, sticky=tk.E)
ent_state.grid(row=5, column=1)

# Create the Label and Entry widgets for "Postal Code"
lbl_postal_code = tk.Label(master=frm_form, text="Postal Code:")
ent_postal_code = tk.Entry(master=frm_form, width=50)
# Place the widgets in the seventh row of the grid
lbl_postal_code.grid(row=6, column=0, sticky=tk.E)
ent_postal_code.grid(row=6, column=1)

# Create the Label and Entry widgets for "Country"
lbl_country = tk.Label(master=frm_form, text="Country:")
ent_country = tk.Entry(master=frm_form, width=50)
# Place the widgets in the eight row of the grid
lbl_country.grid(row=7, column=0, sticky=tk.E)
ent_country.grid(row=7, column=1)

# Create a new frame `frm_buttons` to contain the
# Submit and Clear buttons. This frame fills the
# whole window in the horizontal direction and has
# 5 pixels of horizontal and vertical padding.
frm_buttons = tk.Frame()
frm_buttons.pack(fill=tk.X, ipadx=5, ipady=5)

# Create the "Submit" button and pack it to the
# right side of `frm_buttons`
btn_submit = tk.Button(master=frm_buttons, text="Submit")
btn_submit.pack(side=tk.RIGHT, padx=10, ipadx=10)

# Create the "Clear" button and pack it to the
# right side of `frm_buttons`
btn_clear = tk.Button(master=frm_buttons, text="Clear")
btn_clear.pack(side=tk.RIGHT, ipadx=10)

# Start the application
window.mainloop()

There's nothing wrong with this solution. It's a bit long, but everything is very explicit. If you want to change something, then it's clear to see exactly where to do so.

That said, the solution can be considerably shortened by recognizing that each Entry has the same width, and that all you need for each Label is the text:

import tkinter as tk

# Create a new window with the title "Address Entry Form"
window = tk.Tk()
window.title("Address Entry Form")

# Create a new frame `frm_form` to contain the Label
# and Entry widgets for entering address information.
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
# Pack the frame into the window
frm_form.pack()

# List of field labels
labels = [
    "First Name:",
    "Last Name:",
    "Address Line 1:",
    "Address Line 2:",
    "City:",
    "State/Province:",
    "Postal Code:",
    "Country:",
]

# Loop over the list of field labels
for idx, text in enumerate(labels):
    # Create a Label widget with the text from the labels list
    label = tk.Label(master=frm_form, text=text)
    # Create an Entry widget
    entry = tk.Entry(master=frm_form, width=50)
    # Use the grid geometry manager to place the Label and
    # Entry widgets in the row whose index is idx
    label.grid(row=idx, column=0, sticky="e")
    entry.grid(row=idx, column=1)

# Create a new frame `frm_buttons` to contain the
# Submit and Clear buttons. This frame fills the
# whole window in the horizontal direction and has
# 5 pixels of horizontal and vertical padding.
frm_buttons = tk.Frame()
frm_buttons.pack(fill=tk.X, ipadx=5, ipady=5)

# Create the "Submit" button and pack it to the
# right side of `frm_buttons`
btn_submit = tk.Button(master=frm_buttons, text="Submit")
btn_submit.pack(side=tk.RIGHT, padx=10, ipadx=10)

# Create the "Clear" button and pack it to the
# right side of `frm_buttons`
btn_clear = tk.Button(master=frm_buttons, text="Clear")
btn_clear.pack(side=tk.RIGHT, ipadx=10)

# Start the application
window.mainloop()

In this solution, a list is used to store the strings for each Label in the form. They're stored in the order that each form field should appear. Then, enumerate() gets both the index and string from each value in the labels list.

When you're ready, you can move on to the next section.

Making Your Applications Interactive

By now, you have a pretty good idea of how to create a window with Tkinter, add some widgets, and control the application layout. That's great, but applications shouldn't just look good-they actually need to do something! In this section, you'll learn how to bring your applications to life by performing actions whenever certain events occur.

Using Events and Event Handlers

When you create a Tkinter application, you must call window.mainloop() to start the event loop. During the event loop, your application checks if an event has occurred. If so, then some code can be executed in response.

The event loop is provided for you with Tkinter, so you don't have to write any code that checks for events yourself. However, you do have to write the code that will be executed in response to an event. In Tkinter, you write functions called event handlers for the events that you use in your application.

Note: An event is any action that occurs during the event loop that might trigger some behavior in the application, such as when a key or mouse button is pressed.

When an event occurs, an event object is emitted, which means that an instance of a class representing the event is instantiated. You don't need to worry about creating these classes yourself. Tkinter will create instances of event classes for you automatically.

You'll write your own event loop in order to understand better how Tkinter's event loop works. That way, you can see how Tkinter's event loop fits into your application, and which parts you need to write yourself.

Assume there's a list called events_list that contains event objects. A new event object is automatically appended to events_list every time an event occurs in your program. (You don't need to implement this updating mechanism. It just automatically happens for you in this conceptual example.) Using an infinite loop, you can continually check if there are any event objects in events_list:

# Assume that this list gets updated automatically
events_list = []

# Run the event loop
while True:
    # If events_list is empty, then no events have occurred and you
    # can skip to the next iteration of the loop
    if events_list == []:
        continue

    # If execution reaches this point, then there is at least one
    # event object in events_list
    event = events_list[0]

Right now, the event loop you've created doesn't do anything with event. Let's change that. Suppose your application needs to respond to keypresses. You need to check that event was generated by a user pressing a key on their keyboard, and, if so, pass event to an event handler function for key presses.

Assume that event has a .type attribute set to the string "keypress" if the event is a keypress event object, and a .char attribute containing the character of the key that was pressed. Create a new function handle_keypress() and update your event loop code:

events_list = []

# Create an event handler
def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

while True:
    if events_list == []:
        continue
    event = events_list[0]

    # If event is a keypress event object
    if event.type == "keypress":
        # Call the keypress event handler
        handle_keypress(event)

When you call window.mainloop(), something like the above loop is run for you. This method takes care of two parts of the loop for you:

  1. It maintains a list of events that have occurred.
  2. It runs an event handler any time a new event is added to that list.

Update your event loop to use window.mainloop() instead of your own event loop:

import tkinter as tk

# Create a window object
window = tk.Tk()

# Create an event handler
def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

# Run the event loop
window.mainloop()

.mainloop() takes care of a lot for you, but there's something missing from the above code. How does Tkinter know when to use handle_keypress()? Tkinter widgets have a method called .bind() for just this purpose.

Using .bind()

To call an event handler whenever an event occurs on a widget, use .bind(). The event handler is said to be bound to the event because it's called every time the event occurs. You'll continue with the keypress example from the previous section and use .bind() to bind handle_keypress() to the keypress event:

import tkinter as tk

window = tk.Tk()

def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

# Bind keypress event to handle_keypress()
window.bind("<Key>", handle_keypress)

window.mainloop()

Here, the handle_keypress() event handler is bound to a "<Key>" event using window.bind(). Whenever a key is pressed while the application is running, your program will print the character of the key pressed.

Note: The output of the above program is not printed in the Tkinter application window. It is printed to stdout.

If you run the program in IDLE, you'll see the output in the interactive window. If you run the program from a terminal, you should see the output in your terminal.

.bind() always takes at least two arguments:

  1. An event that's represented by a string of the form "<event_name>", where event_name can be any of Tkinter's events
  2. An event handler that's the name of the function to be called whenever the event occurs

The event handler is bound to the widget on which .bind() is called. When the event handler is called, the event object is passed to the event handler function.

In the example above, the event handler is bound to the window itself, but you can bind an event handler to any widget in your application. For example, you can bind an event handler to a Button widget that will perform some action whenever the button is pressed:

def handle_click(event):
    print("The button was clicked!")

button = tk.Button(text="Click me!")

button.bind("<Button-1>", handle_click)

In this example, the "<Button-1>" event on the button widget is bound to the handle_click event handler. The "<Button-1>" event occurs whenever the left mouse button is pressed while the mouse is over the widget. There are other events for mouse button clicks, including "<Button-2>" for the middle mouse button and "<Button-3>" for the right mouse button.

Note: For a list of commonly used events, see the Event types section of the Tkinter 8.5 reference.

You can bind any event handler to any kind of widget with .bind(), but there's an easier way to bind event handlers to button clicks using the Button widget's command attribute.

Using command

Every Button widget has a command attribute that you can assign to a function. Whenever the button is pressed, the function is executed.

Take a look at an example. First, you'll create a window with a Label widget that holds a numerical value. You'll put buttons on the left and right side of the label. The left button will be used to decrease the value in the Label, and the right one will increase the value. Here's the code for the window:

import tkinter as tk

window = tk.Tk()

window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)

btn_decrease = tk.Button(master=window, text="-")
btn_decrease.grid(row=0, column=0, sticky="nsew")

lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)

btn_increase = tk.Button(master=window, text="+")
btn_increase.grid(row=0, column=2, sticky="nsew")

window.mainloop()

The window looks like this:

A Tkinter application with increase and decrease buttons that increase and decrease a counter

With the app layout defined, you can bring it to life by giving the buttons some commands. Start with the left button. When this button is pressed, it should decrease the value in the label by 1. There are two things you need to know how to do in order to do this:

  1. How do you get the text in a Label?
  2. How do you update the text in a Label?

Label widgets don't have .get() like Entry and Text widgets do. However, you can retrieve the text from the label by accessing the text attribute with a dictionary-style subscript notation:

label = Tk.Label(text="Hello")

# Retrieve a Label's text
text = label["text"]

# Set new text for the label
label["text"] = "Good bye"

Now that you know how to get and set a label's text, write a function increase() that increases the value in the lbl_value by 1:

def increase():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value + 1}"

increase() gets the text from lbl_value and converts it to an integer with int(). Then, it increases this value by 1 and sets the label's text attribute to this new value.

You'll also need decrease() to decrease the value in value_label by 1:

def decrease():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value - 1}"

Put increase() and decrease() in your code just after the import statement.

To connect the buttons to the functions, assign the function to the button's command attribute. You can do this when you instantiate the button. For example, to assign increase() to increase_button, update the line that instantiates the button to the following:

btn_increase = tk.Button(master=window, text="+", command=increase)

Now assign decrease() to decrease_button:

btn_decrease = tk.Button(master=window, text="-", command=decrease)

That's all you need to do to bind the buttons to increase() and decrease() and make the program functional. Try saving your changes and running the application! Click the buttons to increase and decrease the value in the center of the window:

A counter app built with Tkinter

Here's the full application code for your reference:

import tkinter as tk

def increase():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value + 1}"


def decrease():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value - 1}"

window = tk.Tk()

window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)

btn_decrease = tk.Button(master=window, text="-", command=decrease)
btn_decrease.grid(row=0, column=0, sticky="nsew")

lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)

btn_increase = tk.Button(master=window, text="+", command=increase)
btn_increase.grid(row=0, column=2, sticky="nsew")

window.mainloop()

This app is not particularly useful, but the skills you learned here apply to every app you'll make:

In the next two sections, you'll build apps that do something useful. First, you'll build a temperature converter that converts a temperature value from Fahrenheit to Celsius. After that, you'll build a text editor that can open, edit, and save text files!

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Write a program that simulates rolling a six-sided die. There should be one button with the text "Roll". When the user clicks the button, a random integer from 1 to 6 should be displayed.

Hint: You can generate a random number using randint() in the random module. If you're not familiar with the random module, then check out Generating Random Data in Python (Guide) for more information.

The application window should look something like this:

A Tkinter application with a

Try this exercise now.

You can expand the code block below to see a solution:

Here's one possible solution:

import random
import tkinter as tk

def roll():
    lbl_result["text"] = str(random.randint(1, 6))

window = tk.Tk()
window.columnconfigure(0, minsize=150)
window.rowconfigure([0, 1], minsize=50)

btn_roll = tk.Button(text="Roll", command=roll)
lbl_result = tk.Label()

btn_roll.grid(row=0, column=0, sticky="nsew")
lbl_result.grid(row=1, column=0)

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Building a Temperature Converter (Example App)

In this section, you'll build a temperature converter application that allows the user to input temperature in degrees Fahrenheit and push a button to convert that temperature to degrees Celsius. You'll walk through the code step by step. You can also find the full source code at the end of this section for your reference.

Note: To get the most out of this section, follow along in a Python shell.

Before you start coding, you'll first design the app. You need three elements:

  1. An Entry widget called ent_temperature to enter the Fahrenheit value
  2. A Label widget called lbl_result to display the Celsius result
  3. A Button widget called btn_convert that reads the value from the Entry widget, converts it from Fahrenheit to Celsius, and sets the text of the Label widget to the result when clicked

You can arrange these in a grid with a single row and one column for each widget. That gets you a minimally working application, but it isn't very user-friendly. Everything needs to have labels.

You'll put a label directly to the right of the ent_temperature widget containing the Fahrenheit symbol (℉) so that the user knows that the value ent_temperature should be in degrees Fahrenheit. To do this, set the label text to "\N{DEGREES FAHRENHEIT}", which uses Python's named Unicode character support to display the Fahrenheit symbol.

You can give btn_convert a little flair by setting it's text to the value "\N{RIGHTWARDS BLACK ARROW}", which displays a black arrow pointing to the right. You'll also make sure that lbl_result always has the Celsius symbol (℃) following the label text "\N{DEGREES CELSIUS}" to indicate that the result is in degrees Celsius. Here's what the final window will look like:

A temperature conversion application built with Tkinter

Now that you know what widgets you need and what the window is going to look like, you can start coding it up! First, import tkinter and create a new window:

import tkinter as tk

window = tk.Tk()
window.title("Temperature Converter")

window.title() sets the title of an existing window. When you finally run this application, the window will have the text Temperature Converter in its title bar. Next, create the ent_temperature widget with a label called lbl_temp and assign both to a Frame widget called frm_entry:

frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")

ent_temperature is where the user will enter the Fahrenheit value. lbl_temp is used to label ent_temperature with the Fahrenheit symbol. frm_entry is a container that groups ent_temperature and lbl_temp together.

You want lbl_temp to be placed directly to the right of ent_temperature. You can lay them out in the frm_entry using the .grid() geometry manager with one row and two columns:

ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")

You've set the sticky parameter to "e" for ent_temperature so that it always sticks to the right-most edge of its grid cell. You also set sticky to "w" for lbl_temp to keep it stuck to the left-most edge of its grid cell. This ensures that lbl_temp is always located immediately to the right of ent_temperature.

Now, make the btn_convert and the lbl_result for converting the temperature entered into ent_temperature and displaying the results:

btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}"
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")

Like frm_entry, both btn_convert and lbl_result are assigned to window. Together, these three widgets make up the three cells in the main application grid. Use .grid() to go ahead and lay them out now:

frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)

Finally, run the application:

window.mainloop()

That looks great! But the button doesn't do anything just yet. At the top of your script file, just below the import line, add a function called fahrenheit_to_celsius():

def fahrenheit_to_celsius():
    """Convert the value for Fahrenheit to Celsius and insert the
    result into lbl_result.
    """
    fahrenheit = ent_temperature.get()
    celsius = (5/9) * (float(fahrenheit) - 32)
    lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"

This function reads the value from ent_temperature, converts it from Fahrenheit to Celsius, and then displays the result in lbl_result.

Now go down to the line where you define btn_convert and set its command parameter to fahrenheit_to_celsius:

btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}",
    command=fahrenheit_to_celsius  # <--- Add this line
)

That's it! You've created a fully functional temperature converter app in just 26 lines of code! Pretty cool, right?

You can expand the code block below to see the full script:

Here's the full script for your reference:

import tkinter as tk

def fahrenheit_to_celsius():
    """Convert the value for Fahrenheit to Celsius and insert the
    result into lbl_result.
    """
    fahrenheit = ent_temperature.get()
    celsius = (5/9) * (float(fahrenheit) - 32)
    lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"

# Set-up the window
window = tk.Tk()
window.title("Temperature Converter")
window.resizable(width=False, height=False)

# Create the Fahrenheit entry frame with an Entry
# widget and label in it
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")

# Layout the temperature Entry and Label in frm_entry
# using the .grid() geometry manager
ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")

# Create the conversion Button and result display Label
btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}",
    command=fahrenheit_to_celsius
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")

# Set-up the layout using the .grid() geometry manager
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)

# Run the application
window.mainloop()

It's time to kick things up a notch! Read on to learn how to build a text editor.

Building a Text Editor (Example App)

In this section, you'll build a text editor application that can create, open, edit, and save text files. There are three essential elements in the application:

  1. A Button widget called btn_open for opening a file for editing
  2. A Button widget called btn_save for saving a file
  3. A TextBox widget called txt_edit for creating and editing the text file

The three widgets will be arranged so that the two buttons are on the left-hand side of the window, and the text box is on the right-hand side. The whole window should have a minimum height of 800 pixels, and txt_edit should have a minimum width of 800 pixels. The whole layout should be responsive so that if the window is resized, then txt_edit is resized as well. The width of the Frame holding the buttons should not change, however.

Here's a sketch of how the window will look:

A design sketch for a text editor application

You can achieve the desired layout using the .grid() geometry manager. The layout contains a single row and two columns:

  1. A narrow column on the left for the buttons
  2. A wider column on the right for the text box

To set the minimum sizes for the window and txt_edit, you can set the minsize parameters of the window methods .rowconfigure() and .columnconfigure() to 800. To handle resizing, you can set the weight parameters of these methods to 1.

In order to get both buttons into the same column, you'll need to create a Frame widget called fr_buttons. According to the sketch, the two buttons should be stacked vertically inside of this frame, with btn_open on top. You can do that with either the .grid() or .pack() geometry manager. For now, you'll stick with .grid() since it's a little easier to work with.

Now that you have a plan, you can start coding the application. The first step is to create the all of the widgets you need:

 1 import tkinter as tk
 2 
 3 window = tk.Tk()
 4 window.title("Simple Text Editor")
 5 
 6 window.rowconfigure(0, minsize=800, weight=1)
 7 window.columnconfigure(1, minsize=800, weight=1)
 8 
 9 txt_edit = tk.Text(window)
10 fr_buttons = tk.Frame(window)
11 btn_open = tk.Button(fr_buttons, text="Open")
12 btn_save = tk.Button(fr_buttons, text="Save As...")

Here's a breakdown of this code:

Take a look at line 6 more closely. The minsize parameter of .rowconfigure() is set to 800 and weight is set to 1:

window.rowconfigure(0, minsize=800, weight=1)

The first argument is 0, which sets the height of the first row to 800 pixels and makes sure that the height of the row grows proportionally to the height of the window. There's only one row in the application layout, so these settings apply to the entire window.

Let's also take a closer look at line 7. Here, you use .columnconfigure() to set the width and weight attributes of the column with index 1 to 800 and 1, respectively:

window.columnconfigure(1, minsize=800, weight=1)

Remember, row and column indices are zero-based, so these settings apply only to the second column. By configuring just the second column, the text box will expand and contract naturally when the window is resized, while the column containing the buttons will remain at a fixed width.

Now you can work on the application layout. First, assign the two buttons to the fr_buttons frame using the .grid() geometry manager:

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

These two lines of code create a grid with two rows and one column in the fr_buttons frame since both btn_open and btn_save have their master attribute set to fr_buttons. btn_open is put in the first row and btn_save in the second row so that btn_open appears above btn_save in the layout, just you planned in your sketch.

Both btn_open and btn_save have their sticky attributes set to "ew", which forces the buttons to expand horizontally in both directions and fill the entire frame. This makes sure both buttons are the same size.

You place 5 pixels of padding around each button by setting the padx and pady parameters to 5. Only btn_open has vertical padding. Since it's on top, the vertical padding offsets the button down from the top of the window a bit and makes sure that there's a small gap between it and btn_save.

Now that fr_buttons is laid out and ready to go, you can set up the grid layout for the rest of the window:

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

These two lines of code create a grid with one row and two columns for window. You place fr_buttons in the first column and txt_edit in the second column so that fr_buttons appears to the left of txt_edit in the window layout.

The sticky parameter for fr_buttons is set to "ns", which forces the whole frame to expand vertically and fill the entire height of its column. txt_edit fills its entire grid cell because you set its sticky parameter to "nsew", which forces it to expand in every direction.

Now that the application layout is complete, add window.mainloop() to the bottom of the program and save and run the file. The following window is displayed:

A text editor application made with Tkinter

That looks great! But it doesn't do anything just yet, so you need to start writing the commands for the buttons. btn_open needs to show a file open dialog and allow the user to select a file. It then needs to open that file and set the text of txt_edit to the contents of the file. Here's a function open_file() that does just this:

 1 def open_file():
 2     """Open a file for editing."""
 3     filepath = askopenfilename(
 4         filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
 5     )
 6     if not filepath:
 7         return
 8     txt_edit.delete("1.0", tk.END)
 9     with open(filepath, "r") as input_file:
10         text = input_file.read()
11         txt_edit.insert(tk.END, text)
12     window.title(f"Simple Text Editor - {filepath}")

Here's a breakdown of this function:

Now you can update the program so that btn_open calls open_file() whenever it's clicked. There are a few things you need to do to update the program. First, import askopenfilename() from tkinter.filedialog by adding the following import to the top of your program:

import tkinter as tk
from tkinter.filedialog import askopenfilename

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open")
btn_save = tk.Button(fr_buttons, text="Save As...")

Next, add the definition of open_file() just below the import statements:

import tkinter as tk
from tkinter.filedialog import askopenfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete("1.0", tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open")
btn_save = tk.Button(fr_buttons, text="Save As...")

Finally, set the command attribute of btn_opn to open_file:

import tkinter as tk
from tkinter.filedialog import askopenfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete("1.0", tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

Save the file and run it to check that everything is working. Then try opening a text file!

With btn_open working, it's time to work on the function for btn_save. This needs to open a save file dialog box so that the user can choose where they would like to save the file. You'll use the asksaveasfilename dialog in the tkinter.filedialog module for this. This function also needs to extract the text currently in txt_edit and write this to a file at the selected location. Here's a function that does just this:

 1 def save_file():
 2     """Save the current file as a new file."""
 3     filepath = asksaveasfilename(
 4         defaultextension="txt",
 5         filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
 6     )
 7     if not filepath:
 8         return
 9     with open(filepath, "w") as output_file:
10         text = txt_edit.get("1.0", tk.END)
11         output_file.write(text)
12     window.title(f"Simple Text Editor - {filepath}")

Here's how this code works:

Now you can update the program so that btn_save calls save_file() when it's clicked. Again, there are a few things you need to do in order to update the program. First, import asksaveasfilename() from tkinter.filedialog by updating the import at the top of your script, like so:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Next, add the definition of save_file() just below the open_file() definition:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Finally, set the command attribute of btn_save to save_file:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...", command=save_file)

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Save the file and run it. You've now got a minimal yet fully-functional text editor!

You can expand the code block below to see the full script:

Here's the full script for your reference:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...", command=save_file)

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

You've now built two GUI applications in Python and applied many of the topics you've learned about throughout this tutorial. That's no small achievement, so take some time to feel good about what you've done. You're now ready to tackle some applications on your own!

Conclusion

In this tutorial, you learned how to get started with Python GUI programming. Tkinter is a compelling choice for a Python GUI framework because it's built into the Python standard library, and it's relatively painless to make applications with this framework.

Throughout this tutorial, you've learned several important Tkinter concepts:

Now that you've mastered the foundations of Python GUI programming with Tkinter, the next step is to build some of your own applications. What will you create? Share your fun projects down in the comments below!

Additional Resources

In this tutorial, you touched on just the foundations of creating Python GUI applications with Tkinter. There are a number of additional topics that aren't covered here. In this section, you'll find some of the best resources available to help you continue on your journey.

Tkinter References

Here are some official resources to check out:

Additional Widgets

In this tutorial, you learned about the Label, Button, Entry, Text, and Frame widgets. There are several other widgets in Tkinter, all of which are essential for building real-world applications. Here are some resources to continue learning about widgets:

Application Distribution

Once you've created an application with Tkinter, you probably want to distribute that to your colleagues and friends. Here are some tutorials to get you going with that process:

Other GUI Frameworks

Tkinter is not your only choice for a Python GUI framework. If Tkinter doesn't meet the needs of your project, then here are some other frameworks to consider:


[ Improve Your Python With 🐍 Python Tricks 💌 - Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

22 Jan 2020 2:00pm GMT

Real Python: Python GUI Programming With Tkinter

Python has a lot of GUI frameworks, but Tkinter is the only framework that's built into the Python standard library. Tkinter has several strengths. It's cross-platform, so the same code works on Windows, macOS, and Linux. Visual elements are rendered using native operating system elements, so applications built with Tkinter look like they belong on the platform where they're run.

Although Tkinter is considered the de-facto Python GUI framework, it's not without criticism. One notable criticism is that GUIs built with Tkinter look outdated. If you want a shiny, modern interface, then Tkinter may not be what you're looking for.

However, Tkinter is lightweight and relatively painless to use compared to other frameworks. This makes it a compelling choice for building GUI applications in Python, especially for applications where a modern sheen is unnecessary, and the top priority is to build something that's functional and cross-platform quickly.

In this tutorial, you'll learn how to:

Once you've mastered these skills by working through the exercises at the end of each section, you'll tie everything together by building two applications. The first is a temperature converter, and the second is a text editor. It's time to dive right in and see how to build an application with Tkinter!

Note: This tutorial is adapted from the chapter "Graphical User Interfaces" of Python Basics: A Practical Introduction to Python 3.

The book uses Python's built-in IDLE editor to create and edit Python files and interact with the Python shell. In this tutorial, references to IDLE have been removed in favor of more general language.

The bulk of the material in this tutorial has been left unchanged, and you should have no problems running the example code from the editor and environment of your choice.

Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you'll need to take your Python skills to the next level.

Building Your First Python GUI Application With Tkinter

The foundational element of a Tkinter GUI is the window. Windows are the containers in which all other GUI elements live. These other GUI elements, such as text boxes, labels, and buttons, are known as widgets. Widgets are contained inside of windows.

First, create a window that contains a single widget. Start up a new Python shell session and follow along!

Note: The code examples in this tutorial have all been tested on Windows, macOS, and Ubuntu Linux 18.04 with Python versions 3.6, 3.7, and 3.8.

If you've installed Python with the official installers available for Windows and macOS from python.org, then you should have no problem running the sample code. You can safely skip the rest of this note and continue with the tutorial!

If you haven't installed Python with the official installers, or there's no official distribution for your system, then here are some tips for getting up and going.

Python on macOS with Homebrew:

The Python distribution for macOS available on Homebrew does not come bundled with the Tcl/Tk dependency Tkinter. The default system version is used instead. This version may be outdated and prevent you from importing the Python GUI Tkinter module. To avoid this problem, use the official macOS installer.

Ubuntu Linux 16.04:

The latest version of Python available in the Ubuntu Linux 16.04 Universe repository is 3.5. You can install the latest version with the deadsnakes PPA. Here are the commands to set up the PPA and download the latest version of Python with the correct Tcl/Tk version:

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt-get update
$ sudo apt-get install python3.8 python3-tk

The first two commands add the deadsnakes PPA to your system's repository list, and the last command installs Python 3.8 and the Python GUI Tkinter module.

Ubuntu Linux 18.04:

You can install the latest version of Python with the correct Tcl/Tk version from the Universe repository with the following command:

$ sudo apt-get install python3.8 python3-tk

This installs Python 3.8, as well as the Python GUI Tkinter module.

Other Linux Flavors:

If you're unable to get a working Python installation on your flavor of Linux, then you can build Python with the correct version of Tcl/Tk from the source code. For a step-by-step walkthrough of this process, check out the Python 3 Installation & Setup Guide.

With your Python shell open, the first thing you need to do is import the Python GUI Tkinter module:

>>>
>>> import tkinter as tk

A window is an instance of Tkinter's Tk class. Go ahead and create a new window and assign it to the variable window:

>>>
>>> window = tk.Tk()

When you execute the above code, a new window pops up on your screen. How it looks depends on your operating system:

A blank Tkinter application window on Windows 10, macOS, and Ubuntu Linux

Throughout the rest of this tutorial, you'll see Windows screenshots.

Adding a Widget

Now that you have a window, you can add a widget. Use the tk.Label class to add some text to a window. Create a Label widget with the text "Hello, Tkinter" and assign it to a variable called greeting:

>>>
>>> greeting = tk.Label(text="Hello, Tkinter")

The window you created earlier doesn't change. You just created a Label widget, but you haven't added it to the window yet. There are several ways to add widgets to a window. Right now, you can use the Label widget's .pack() method:

>>>
>>> greeting.pack()

The window now looks like this:

Example

When you .pack() a widget into a window, Tkinter sizes the window as small as it can while still fully encompassing the widget. Now execute the following:

>>>
>>> window.mainloop()

Nothing seems to happen, but notice that a new prompt does not appear in the shell.

window.mainloop() tells Python to run the Tkinter event loop. This method listens for events, such as button clicks or keypresses, and blocks any code that comes after it from running until the window it's called on is closed. Go ahead and close the window you've created, and you'll see a new prompt displayed in the shell.

Warning: When you work with Tkinter from a Python REPL, updates to windows are applied as each line is executed. This is not the case when a Tkinter program is executed from a Python file!

If you don't include window.mainloop() at the end of a program in a Python file, then the Tkinter application will never run, and nothing will be displayed.

Creating a window with Tkinter only takes a couple of lines of code. But blank windows aren't very useful! In the next section, you'll learn about some of the widgets available in Tkinter, and how you can customize them to meet your application's needs.

Check Your Understanding

Expand the code blocks below to check your understanding:

Write a full Python script that creates a Tkinter window with the text "Python rocks!".

The window should look like this:

A Tkinter window containing the text

Try this exercise now.

You can expand the code block below to see a solution:

Here's one possible solution:

import tkinter as tk

window = tk.Tk()
label = tk.Label(text="Python rocks!")
label.pack()

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Working With Widgets

Widgets are the bread and butter of the Python GUI framework Tkinter. They are the elements through which users interact with your program. Each widget in Tkinter is defined by a class. Here are some of the widgets available:

Widget Class Description
Label A widget used to display text on the screen
Button A button that can contain text and can perform an action when clicked
Entry A text entry widget that allows only a single line of text
Text A text entry widget that allows multiline text entry
Frame A rectangular region used to group related widgets or provide padding between widgets

You'll see how to work with each of these in the following sections. Note that Tkinter has many more widgets than the ones listed here. For a full list, check out Basic Widgets and More Widgets in the TkDocs tutorial. For now, take a closer look at the Label widget.

Displaying Text and Images With Label Widgets

Label widgets are used to display text or images. The text displayed by a Label widget can't be edited by the user. It's for display purposes only. As you saw in the example at the beginning of this tutorial, you can create a Label widget by instantiating the Label class and passing a string to the text parameter:

label = tk.Label(text="Hello, Tkinter")

Label widgets display text with the default system text color and the default system text background color. These are typically black and white, respectively, but you may see different colors if you have changed these settings in your operating system.

You can control Label text and background colors using the foreground and background parameters:

label = tk.Label(
    text="Hello, Tkinter",
    foreground="white",  # Set the text color to white
    background="black"  # Set the background color to black
)

There are numerous valid color names, including:

Many of the HTML color names work with Tkinter. A chart with most of the valid color names is available here. For a full reference, including macOS and Windows-specific system colors that are controlled by the current system theme, check out the colors manual page.

You can also specify a color using hexadecimal RGB values:

label = tk.Label(text="Hello, Tkinter", background="#34A2FE")

This sets the label background to a nice, light blue color. Hexadecimal RGB values are more cryptic than named colors, but they're also more flexible. Fortunately, there are tools available that make getting hexadecimal color codes relatively painless.

If you don't feel like typing out foreground and background all the time, then you can use the shorthand fg and bg parameters to set the foreground and background colors:

label = tk.Label(text="Hello, Tkinter", fg="white", bg="black")

You can also control the width and height of a label with the width and height parameters:

label = tk.Label(
    text="Hello, Tkinter",
    fg="white",
    bg="black",
    width=10,
    height=10
)

Here's what this label looks like in a window:

A Tkinter window containing a button with a black background and white text that reads

It may seem strange that the label in the window isn't square even though the width and height are both set to 10. This is because the width and height are measured in text units. One horizontal text unit is determined by the width of the character "0", or the number zero, in the default system font. Similarly, one vertical text unit is determined by the height of the character "0".

Note: Tkinter uses text units for width and height measurements, instead of something like inches, centimeters, or pixels, to ensure consistent behavior of the application across platforms.

Measuring units by the width of a character means that the size of a widget is relative to the default font on a user's machine. This ensures the text fits properly in labels and buttons, no matter where the application is running.

Labels are great for displaying some text, but they don't help you get input from a user. The next three widgets that you'll look at are all used to get user input.

Displaying Clickable Buttons With Button Widgets

Button widgets are used to display clickable buttons. They can be configured to call a function whenever they're clicked. You'll cover how to call functions from button clicks in the next section. For now, take a look at how to create and style a Button.

There are many similarities between Button and Label widgets. In many ways, a Button is just a Label that you can click! The same keyword arguments you use to create and style a Label will work with Button widgets. For example, the following code creates a Button with a blue background and yellow text. It also sets the width and height to 25 and 5 text units, respectively:

button = tk.Button(
    text="Click me!",
    width=25,
    height=5,
    bg="blue",
    fg="yellow",
)

Here's what the button looks like in a window:

A Tkinter window containing a button with a blue background and yellow text that reads

Pretty nifty! The next two widgets you'll see are used to collect text input from a user.

Getting User Input With Entry Widgets

When you need to get a little bit of text from a user, like a name or an email address, use an Entry widget. They display a small text box that the user can type some text into. Creating and styling an Entry widget works pretty much exactly like Label and Button widgets. For example, the following code creates a widget with a blue background, some yellow text, and a width of 50 text units:

entry = tk.Entry(fg="yellow", bg="blue", width=50)

The interesting bit about Entry widgets isn't how to style them, though. It's how to use them to get input from a user. There are three main operations that you can perform with Entry widgets:

  1. Retrieving text with .get()
  2. Deleting text with .delete()
  3. Inserting text with .insert()

The best way to get an understanding of Entry widgets is to create one and interact with it. Open up a Python shell and follow along with the examples in this section. First, import tkinter and create a new window:

>>>
>>> import tkinter as tk
>>> window = tk.Tk()

Now create a Label and an Entry widget:

>>>
>>> label = tk.Label(text="Name")
>>> entry = tk.Entry()

The Label describes what sort of text should go in the Entry widget. It doesn't enforce any sort of requirements on the Entry, but it tells the user what your program expects them to put there. You need to .pack() the widgets into the window so that they're visible:

>>>
>>> label.pack()
>>> entry.pack()

Here's what that looks like:

A Tkinter window containing an Entry widget withe Label

Notice that Tkinter automatically centers the Label above the Entry widget in the window. This is a feature of .pack(), which you'll learn more about in later sections.

Click inside the Entry widget with your mouse and type "Real Python":

A Tkinter window containing an Entry widget with the text

Now you've got some text entered into the Entry widget, but that text hasn't been sent to your program yet. You can use .get() to retrieve the text and assign it to a variable called name:

>>>
>>> name = entry.get()
>>> name
'Real Python'

You can .delete() text as well. This method takes an integer argument that tells Python which character to remove. For example, the code block below shows how .delete(0) deletes the first character from the Entry:

>>>
>>> entry.delete(0)

The text remaining in the widget is now "eal Python":

A Tkinter window containing an Entry widget with the text

Note that, just like Python string objects, text in an Entry widget is indexed starting with 0.

If you need to remove several characters from an Entry, then pass a second integer argument to .delete() indicating the index of the character where deletion should stop. For example, the following code deletes the first four letters in the Entry:

>>>
>>> entry.delete(0, 4)

The remaining text now reads "Python":

A Tkinter window containing an Entry widget with the text

Entry.delete() works just like string slicing. The first argument determines the starting index, and the deletion continues up to but not including the index passed as the second argument. Use the special constant tk.END for the second argument of .delete() to remove all text in an Entry:

>>>
>>> entry.delete(0, tk.END)

You'll now see a blank text box:

A Tkinter window containing an Entry widget withe Label

On the opposite end of the spectrum, you can also .insert() text into an Entry widget:

>>>
>>> entry.insert(0, "Python")

The window now looks like this:

A Tkinter window containing an Entry widget with the text

The first argument tells .insert() where to insert the text. If there is no text in the Entry, then the new text will always be inserted at the beginning of the widget, no matter what value you pass as the first argument. For example, calling .insert() with 100 as the first argument instead of 0, as you did above, would have generated the same output.

If an Entry already contains some text, then .insert() will insert the new text at the specified position and shift all existing text to the right:

>>>
>>> entry.insert(0, "Real ")

The widget text now reads "Real Python":

A Tkinter window containing an Entry widget with the text

Entry widgets are great for capturing small amounts of text from a user, but because they're only displayed on a single line, they're not ideal for gathering large amounts of text. That's where Text widgets come in!

Getting Multiline User Input With Text Widgets

Text widgets are used for entering text, just like Entry widgets. The difference is that Text widgets may contain multiple lines of text. With a Text widget, a user can input a whole paragraph or even several pages of text! Just like Entry widgets, there are three main operations you can perform with Text widgets:

  1. Retrieve text with .get()
  2. Delete text with .delete()
  3. Insert text with .insert()

Although the method names are the same as the Entry methods, they work a bit differently. It's time to get your hands dirty by creating a Text widget and seeing what all it can do.

Note: Do you still have the window from the previous section open?

If so, then you can close it by executing the following:

>>>
>>> window.destroy()

You can also close it manually by clicking the Close button.

In your Python shell, create a new blank window and .pack() a Text() widget into it:

>>>
>>> window = tk.Tk()
>>> text_box = tk.Text()
>>> text_box.pack()

Text boxes are much larger than Entry widgets by default. Here's what the window created above looks like:

A Tkinter window containing a Text Box widget

Click anywhere inside the window to activate the text box. Type in the word "Hello". Then press Enter and type "World" on the second line. The window should now look like this:

A Tkinter window containing a Text Box widget with the text

Just like Entry widgets, you can retrieve the text from a Text widget using .get(). However, calling .get() with no arguments doesn't return the full text in the text box like it does for Entry widgets. It raises an exception:

>>>
>>> text_box.get()
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    text_box.get()
TypeError: get() missing 1 required positional argument: 'index1'

Text.get() required at least one argument. Calling .get() with a single index returns a single character. To retrieve several characters, you need to pass a start index and an end index. Indices in Text widgets work differently than Entry widgets. Since Text widgets can have several lines of text, an index must contain two pieces of information:

  1. The line number of a character
  2. The position of a character on that line

Line numbers start with 1, and character positions start with 0. To make an index, you create a string of the form "<line>.<char>", replacing <line> with the line number and <char> with the character number. For example, "1.0" represents the first character on the first line, and "2.3" represents the fourth character on the second line.

Use the index "1.0" to get the first letter from the text box you created earlier:

>>>
>>> text_box.get("1.0")
'H'

There are five letters in the word "Hello", and the character number of o is 4, since character numbers start from 0, and the word "Hello" starts at the first position in the text box. Just like Python string slices, in order to get the entire word "Hello" from the text box, the end index must be one more than the index of the last character to be read.

So, to get the word "Hello" from the text box, use "1.0" for the first index and "1.5" for the second index:

>>>
>>> text_box.get("1.0", "1.5")
'Hello'

To get the word "World" on the second line of the text box, change the line numbers in each index to 2:

>>>
>>> text_box.get("2.0", "2.5")
'World'

To get all of the text in a text box, set the starting index in "1.0" and use the special tk.END constant for the second index:

>>>
>>> text_box.get("1.0", tk.END)
'Hello\nWorld\n'

Notice that text returned by .get() includes any newline characters. You can also see from this example that every line in a Text widget has a newline character at the end, including the last line of text in the text box.

.delete() is used to delete characters from a text box. It work just like .delete() for Entry widgets. There are two ways to use .delete():

  1. With a single argument
  2. With two arguments

Using the single-argument version, you pass to .delete() the index of a single character to be deleted. For example, the following deletes the first character H from the text box:

>>>
>>> text_box.delete("1.0")

The first line of text in the window now reads "ello":

A Tkinter window containing a Text Box widget with the text

With the two-argument version, you pass two indices to delete a range of characters starting at the first index and up to, but not including, the second index.

For example, to delete the remaining "ello" on the first line of the text box, use the indices "1.0" and "1.4":

>>>
>>> text_box.delete("1.0", "1.4")

Notice that the text is gone from the first line. This leaves a blank line followed the word World on the second line:

A Tkinter window containing a Text Box widget with a blank first line and the text

Even though you can't see it, there's still a character on the first line. It's a newline character! You can verify this using .get():

>>>
>>> text_box.get("1.0")
'\n'

If you delete that character, then the rest of the contents of the text box will shift up a line:

>>>
>>> text_box.delete("1.0")

Now, "World" is on the first line of the text box:

A Tkinter window containing a Text Box widget with the text

Try to clear out the rest of the text in the text box. Set "1.0" as the start index and use tk.END for the second index:

>>>
>>> text_box.delete("1.0", tk.END)

The text box is now empty:

A Tkinter window containing a Text Box widget

You can insert text into a text box using .insert():

>>>
>>> text_box.insert("1.0", "Hello")

This inserts the word "Hello" at the beginning of the text box, using the same "<line>.<column>" format used by .get() to specify the insertion position:

A Tkinter window containing a Text Box widget with the text

Check out what happens if you try to insert the word "World" on the second line:

>>>
>>> text_box.insert("2.0", "World")

Instead of inserting the text on the second line, the text is inserted at the end of the first line:

A Tkinter window containing a Text Box widget with the text

If you want to insert text onto a new line, then you need to insert a newline character manually into the string being inserted:

>>>
>>> text_box.insert("2.0", "\nWorld")

Now "World" is on the second line of the text box:

A Tkinter window containing a Text Box widget with the text

.insert() will do one of two things:

  1. Insert text at the specified position if there's already text at or after that position.
  2. Append text to the specified line if the character number is greater than the index of the last character in the text box.

It's usually impractical to try and keep track of what the index of the last character is. The best way to insert text at the end of a Text widget is to pass tk.END to the first parameter of .insert():

text_box.insert(tk.END, "Put me at the end!")

Don't forget to include the newline character (\n) at the beginning of the text if you want to put it on a new line:

text_box.insert(tk.END, "\nPut me on a new line!")

Label, Button, Entry, and Text widgets are just a few of the widgets available in Tkinter. There are several others, including widgets for checkboxes, radio buttons, scroll bars, and progress bars. For more information on all of the available widgets, see the Additional Widgets list in the Additional Resources section.

Assigning Widgets to Frames With Frame Widgets

In this tutorial, you're going to work with only five widgets. These are the four you've seen so far plus the Frame widget. Frame widgets are important for organizing the layout of your widgets in an application.

Before you get into the details about laying out the visual presentation of your widgets, take a closer look at how Frame widgets work, and how you can assign other widgets to them. The following script creates a blank Frame widget and assigns it to the main application window:

import tkinter as tk

window = tk.Tk()
frame = tk.Frame()
frame.pack()

window.mainloop()

frame.pack() packs the frame into the window so that the window sizes itself as small as possible to encompass the frame. When you run the above script, you get some seriously uninteresting output:

A Tkinter window containing an empty Frame widget

An empty Frame widget is practically invisible. Frames are best thought of as containers for other widgets. You can assign a widget to a frame by setting the widget's master attribute:

frame = tk.Frame()
label = tk.Label(master=frame)

To get a feel for how this works, write a script that creates two Frame widgets called frame_a and frame_b. In this script, frame_a contains a label with the text "I'm in Frame A", and frame_b contains the label "I'm in Frame B". Here's one way to do this:

import tkinter as tk

window = tk.Tk()

frame_a = tk.Frame()
frame_b = tk.Frame()

label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()

label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()

frame_a.pack()
frame_b.pack()

window.mainloop()

Note that frame_a is packed into the window before frame_b. The window that opens shows the label in frame_a above the label in frame_b:

A Tkinter window containg two Frame widgets stacked vertically, with the text

Now see what happens when you swap the order of frame_a.pack() and frame_b.pack():

import tkinter as tk

window = tk.Tk()

frame_a = tk.Frame()
label_a = tk.Label(master=frame_a, text="I'm in Frame A")
label_a.pack()

frame_b = tk.Frame()
label_b = tk.Label(master=frame_b, text="I'm in Frame B")
label_b.pack()

# Swap the order of `frame_a` and `frame_b`
frame_b.pack()
frame_a.pack()

window.mainloop()

The output looks like this:

A Tkinter window containg two Frame widgets stacked vertically, with the text

Now label_b is on top. Since label_b is assigned to frame_b, it moves to wherever frame_b is positioned.

All four of the widget types you've learned about-Label, Button, Entry, and Text-have a master attribute that's set when you instantiate them. That way, you can control which Frame a widget is assigned to. Frame widgets are great for organizing other widgets in a logical manner. Related widgets can be assigned to the same frame so that, if the frame is ever moved in the window, then the related widgets stay together.

In addition to grouping your widgets logically, Frame widgets can add a little flare to the visual presentation of your application. Read on to see how to create various borders for Frame widgets.

Adjusting Frame Appearance With Reliefs

Frame widgets can be configured with a relief attribute that creates a border around the frame. You can set relief to be any of the following values:

To apply the border effect, you must set the borderwidth attribute to a value greater than 1. This attribute adjusts the width of the border in pixels. The best way to get a feel for what each effect looks like is to see them for yourself. Here's a script that packs five Frame widgets into a window, each with a different value for the relief argument:

 1 import tkinter as tk
 2 
 3 border_effects = {
 4     "flat": tk.FLAT,
 5     "sunken": tk.SUNKEN,
 6     "raised": tk.RAISED,
 7     "groove": tk.GROOVE,
 8     "ridge": tk.RIDGE,
 9 }
10 
11 window = tk.Tk()
12 
13 for relief_name, relief in border_effects.items():
14     frame = tk.Frame(master=window, relief=relief, borderwidth=5)
15     frame.pack(side=tk.LEFT)
16     label = tk.Label(master=frame, text=relief_name)
17     label.pack()
18 
19 window.mainloop()

Here's a breakdown of this script:

The window produced by the above script looks like this:

A Tkinter window containing 5 Frame widgets, each with one of the five relief values: tk.FLAT, tk.SUNKET, tk.RAISED, tk.GROOVE, and tk.RIDGE

In this image, you can see the following effects:

These effects give your Python GUI Tkinter application a bit of visual appeal.

Understanding Widget Naming Conventions

When you create a widget, you can give it any name you like, as long as it's a valid Python identifier. It's usually a good idea to include the name of the widget class in the variable name you assign to the widget instance. For example, if a Label widget is used to display a user's name, then you might name the widget label_user_name. An Entry widget used to collect a user's age might be called entry_age.

When you include the widget class name in the variable name, you help yourself (and anyone else that needs to read your code) to understand what type of widget the variable name refers to. However, using the full name of the widget class can lead to long variable names, so you may want to adopt a shorthand for referring to each widget type. For the rest of this tutorial, you'll use the following shorthand prefixes to name widgets:

Widget Class Variable Name Prefix Example
Label lbl lbl_name
Button btn btn_submit
Entry ent ent_age
Text txt txt_notes
Frame frm frm_address

In this section, you learned how to create a window, use widgets, and work with frames. At this point, you can make some plain windows that display messages, but you've yet to create a full-blown application. In the next section, you'll learn how to control the layout of your applications using Tkinter's powerful geometry managers.

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Write a complete script that displays an Entry widget that's 40 text units wide and has a white background and black text. Use .insert() to display text in the widget that reads "What is your name?".

The output window should look like this:

A Tkinter window containing an Entry widget with the text

Try this exercise now.

You can expand the code block below to see a solution:

There are a couple of ways to solve this exercise. Here's one solution that uses the bg and fg parameters to set the Entry widget's background and foreground colors:

import tkinter as tk

window = tk.Tk()

entry = tk.Entry(width=40, bg="white", fg="black")
entry.pack()

entry.insert(0, "What is your name?")

window.mainloop()

This solution is great because it explicitly sets the background and foreground colors for the Entry widget.

On most systems, the default background color for an Entry widget is white, and the default foreground color is black. So, you might be able to generate the same window with the bg and fg parameters left out:

import tkinter as tk

window = tk.Tk()

entry = tk.Entry(width=40)
entry.pack()

entry.insert(0, "What is your name?")

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Controlling Layout With Geometry Managers

Up until now, you've been adding widgets to windows and Frame widgets using .pack(), but you haven't learned what exactly this method does. Let's clear things up! Application layout in Tkinter is controlled with geometry managers. While .pack() is an example of a geometry manager, it isn't the only one. Tkinter has two others:

Each window and Frame in your application can use only one geometry manager. However, different frames can use different geometry managers, even if they're assigned to a frame or window using another geometry manager. Start by taking a closer look at .pack().

The .pack() Geometry Manager

.pack() uses a packing algorithm to place widgets in a Frame or window in a specified order. For a given widget, the packing algorithm has two primary steps:

  1. Compute a rectangular area called a parcel that's just tall (or wide) enough to hold the widget and fills the remaining width (or height) in the window with blank space.
  2. Center the widget in the parcel unless a different location is specified.

.pack() is powerful, but it can be difficult to visualize. The best way to get a feel for .pack() is to look at some examples. See what happens when you .pack() three Label widgets into a Frame:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=100, height=100, bg="red")
frame1.pack()

frame2 = tk.Frame(master=window, width=50, height=50, bg="yellow")
frame2.pack()

frame3 = tk.Frame(master=window, width=25, height=25, bg="blue")
frame3.pack()

window.mainloop()

.pack() places each Frame below the previous one by default, in the order that they're assigned to the window:

A Tkinter window with three colored squares packed vertically

Each Frame is placed at the top-most available position. The red Frame is placed at the top of the window. Then the yellow Frame is placed just below the red one and the blue Frame just below the yellow one.

There are three invisible parcels containing each of the three Frame widgets. Each parcel is as wide as the window and as tall as the Frame that it contains. Since no anchor point was specified when .pack() was called for each Frame, they're all centered inside of their parcels. That's why each Frame is centered in the window.

.pack() accepts some keyword arguments for more precisely configuring widget placement. For example, you can set the fill keyword argument to specify in which direction the frames should fill. The options are tk.X to fill in the horizontal direction, tk.Y to fill vertically, and tk.BOTH to fill in both directions. Here's how you would stack the three frames so that each one fills the whole window horizontally:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, height=100, bg="red")
frame1.pack(fill=tk.X)

frame2 = tk.Frame(master=window, height=50, bg="yellow")
frame2.pack(fill=tk.X)

frame3 = tk.Frame(master=window, height=25, bg="blue")
frame3.pack(fill=tk.X)

window.mainloop()

Notice that the width is not set on any of the Frame widgets. width is no longer necessary because each frame sets .pack() to fill horizontally, overriding any width you may set.

The window produced by this script looks like this:

A Tkinter window containing three colored frames packed vertically and expanded horizontally to fill the entire window

One of the nice things about filling the window with .pack() is that the fill is responsive to window resizing. Try widening the window generated by the previous script to see how this works. As you widen the window, the width of the three Frame widgets grow to fill the window:

A Tkinter window that expands horizontally with window resizing

Notice, though, that the Frame widgets don't expand in the vertical direction.

The side keyword argument of .pack() specifies on which side of the window the widget should be placed. These are the available options:

If you don't set side, then .pack() will automatically use tk.TOP and place new widgets at the top of the window, or at the top-most portion of the window that isn't already occupied by a widget. For example, the following script places three frames side-by-side from left to right and expands each frame to fill the window vertically:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.Y, side=tk.LEFT)

frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.Y, side=tk.LEFT)

frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.Y, side=tk.LEFT)

window.mainloop()

This time, you have to specify the height keyword argument on at least one of the frames to force the window to have some height.

The resulting window looks like this:

A Tkinter window containing three colored frames packed horizontally and expanded vertically to fill the entire window

Just like when you set fill=tk.X to make the frames responsive when you resized the window horizontally, you can set fill=tk.Y to make the frames responsive when you resize the window vertically:

A Tkinter window that expands vertically with window resizing

To make the layout truly responsive, you can set an initial size for your frames using the width and height attributes. Then, set the fill keyword argument of .pack() to tk.BOTH and set the expand keyword argument to True:

import tkinter as tk

window = tk.Tk()

frame1 = tk.Frame(master=window, width=200, height=100, bg="red")
frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

frame2 = tk.Frame(master=window, width=100, bg="yellow")
frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

frame3 = tk.Frame(master=window, width=50, bg="blue")
frame3.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)

window.mainloop()

When you run the above script, you'll see a window that initially looks the same as the one you generated in the previous example. The difference is that now you can resize the window however you want and the frames will expand and fill the window responsively:

A Tkinter window that expands both horizontally and vertically with window resizing

Pretty cool!

The .place() Geometry Manager

You can use .place() to control the precise location that a widget should occupy in a window or Frame. You must provide two keyword arguments, x and y, which specify the x- and y-coordinates for the top-left corner of the widget. Both x and y are measured in pixels, not text units.

Keep in mind that the origin (where x and y are both 0) is the top-left corner of the Frame or window. So, you can think of the y argument of .place() as the number of pixels from the top of the window, and the x argument as the number of pixels from the left of the window.

Here's an example of how the .place() geometry manager works:

 1 import tkinter as tk
 2 
 3 window = tk.Tk()
 4 
 5 frame = tk.Frame(master=window, width=150, height=150)
 6 frame.pack()
 7 
 8 label1 = tk.Label(master=frame, text="I'm at (0, 0)", bg="red")
 9 label1.place(x=0, y=0)
10 
11 label2 = tk.Label(master=frame, text="I'm at (75, 75)", bg="yellow")
12 label2.place(x=75, y=75)
13 
14 window.mainloop()

Here's how this code works:

Here's the window the code produces:

A Tkinter window containing two Label widgets laid out using the .place() geometry manager

.place() is not used often. It has two main drawbacks:

  1. Layout can be difficult to manage with .place(). This is especially true if your application has lots of widgets.
  2. Layouts created with .place() are not responsive. They don't change as the window is resized.

One of the main challenges of cross-platform GUI development is making layouts that look good no matter which platform they are viewed on, and .place() is a poor choice for making responsive and cross-platform layouts.

That's not to say .place() should never be used! In some cases, it might be just what you need. For example, if you're creating a GUI interface for a map, then .place() might be the perfect choice to ensure widgets are placed at the correct distance from each other on the map.

.pack() is usually a better choice than .place(), but even .pack() has some downsides. The placement of widgets depends on the order in which .pack() is called, so it can be difficult to modify existing applications without fully understanding the code controlling the layout. The .grid() geometry manager solves a lot of these issues, as you'll see in the next section.

The .grid() Geometry Manager

The geometry manager you'll likely use most often is .grid(), which provides all the power of .pack() in a format that's easier to understand and maintain.

.grid() works by splitting a window or Frame into rows and columns. You specify the location of a widget by calling .grid() and passing the row and column indices to the row and column keyword arguments, respectively. Both row and column indices start at 0, so a row index of 1 and a column index of 2 tells .grid() to place a widget in the third column of the second row.

The following script creates a 3 × 3 grid of frames with Label widgets packed into them:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Here's what the resulting window looks like:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them

Two geometry managers are being used in this example. Each Frame is attached to the window with the .grid() geometry manager:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Each label is attached to its master Frame with .pack():

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

The important thing to realize here is that even though .grid() is called on each Frame object, the geometry manager applies to the window object. Similarly, the layout of each frame is controlled with the .pack() geometry manager.

The frames in the previous example are placed tightly next to one another. To add some space around each Frame, you can set the padding of each cell in the grid. Padding is just some blank space that surrounds a widget and separates it visually from its contents.

The two types of padding are external and internal padding. External padding adds some space around the outside of a grid cell. It's controlled with two keyword arguments to .grid():

  1. padx adds padding in the horizontal direction.
  2. pady adds padding in the vertical direction.

Both padx and pady are measured in pixels, not text units, so setting both of them to the same value will create the same amount of padding in both directions. Try to add some padding around the outside of the frames in the previous example:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)
        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack()

window.mainloop()

Here's the resulting window:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them. Each grid cell has 5 pixels of exterior padding.

.pack() also has padx and pady parameters. The following code is nearly identical to the previous code, except that you add 5 pixels of additional padding around each Label in both the x and y directions:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    for j in range(3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)

        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack(padx=5, pady=5)

window.mainloop()

The extra padding around the Label widgets gives each cell in the grid a little bit of breathing room between the Frame border and the text in the Label:

A Tkinter window containing a 3 x 3 grid of Frame widgets with Label widgets packed into them. Each grid cell and Label widget has 5 pixels of exterior padding.

That looks pretty nice! But if you try and expand the window in any direction, then you'll notice that the layout isn't very responsive:

A Tkinter window containing a 3 x 3 grid that does not expand with window resizing

The whole grid stays at the top-left corner as the window expands.

You can adjust how the rows and columns of the grid grow as the window is resized using .columnconfigure() and .rowconfigure() on the window object. Remember, the grid is attached to window, even though you're calling .grid() on each Frame widget. Both .columnconfigure() and .rowconfigure() take three essential arguments:

  1. The index of the grid column or row that you want to configure (or a list of indices to configure multiple rows or columns at the same time)
  2. A keyword argument called weight that determines how the column or row should respond to window resizing, relative to the other columns and rows
  3. A keyword argument called minsize that sets the minimum size of the row height or column width in pixels

weight is set to 0 by default, which means that the column or row doesn't expand as the window resizes. If every column and row is given a weight of 1, then they all grow at the same rate. If one column has a weight of 1 and another a weight of 2, then the second column expands at twice the rate of the first. Adjust the previous script to better handle window resizing:

import tkinter as tk

window = tk.Tk()

for i in range(3):
    window.columnconfigure(i, weight=1, minsize=75)
    window.rowconfigure(i, weight=1, minsize=50)

    for j in range(0, 3):
        frame = tk.Frame(
            master=window,
            relief=tk.RAISED,
            borderwidth=1
        )
        frame.grid(row=i, column=j, padx=5, pady=5)

        label = tk.Label(master=frame, text=f"Row {i}\nColumn {j}")
        label.pack(padx=5, pady=5)

window.mainloop()

.columnconfigure() and .rowconfigure() are placed in the body of the outer for loop. (You could explicitly configure each column and row outside of the for loop, but that would require writing an additional six lines of code.)

On each iteration of the loop, the i-th column and row are configured to have a weight of 1. This ensures that each row and column expands at the same rate whenever the window is resized. The minsize argument is set to 75 for each column and 50 for each row. This makes sure the Label widget always displays its text without chopping off any characters, even if the window size is extremely small.

The result is a grid layout that expands and contracts smoothly as the window is resized:

A Tkinter window containing a fully responsive 3 x 3 grid layout

Try it yourself to get a feel for how it works! Play around with the weight and minsize parameters to see how they affect the grid.

By default, widgets are centered in their grid cells. For example, the following code creates two Label widgets and places them in a grid with one column and two rows:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0)

label2 = tk.Label(text="B")
label2.grid(row=1, column=0)

window.mainloop()

Each grid cell is 250 pixels wide and 100 pixels tall. The labels are placed in the center of each cell, as you can see in the following figure:

A Tkinter window with grid geometry manager and custom row and column sizes

You can change the location of each label inside of the grid cell using the sticky parameter. sticky accepts a string containing one or more of the following letters:

The letters "n", "s", "e", and "w" come from the cardinal directions north, south, east, and west. Setting sticky to "n" on both Labels in the previous code positions each Label at the top-center of its grid cell:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="n")

label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="n")

window.mainloop()

Here's the output:

A Tkinter window with grid geometry manager and sticky set to

You can combine multiple letters in a single string to position each Label in the corner of its grid cell:

import tkinter as tk

window = tk.Tk()
window.columnconfigure(0, minsize=250)
window.rowconfigure([0, 1], minsize=100)

label1 = tk.Label(text="A")
label1.grid(row=0, column=0, sticky="ne")

label2 = tk.Label(text="B")
label2.grid(row=1, column=0, sticky="sw")

window.mainloop()

In this example, the sticky parameter of label1 is set to "ne", which places the label at the top-right corner of its grid cell. label2 is positioned in the bottom-left corner by passing "sw" to sticky. Here's what that looks like in the window:

A Tkinter window with grid geometry manager and sticky set to

When a widget is positioned with sticky, the size of the widget itself is just big enough to contain any text and other contents inside of it. It won't fill the entire grid cell. In order to fill the grid, you can specify "ns" to force the widget to fill the cell in the vertical direction, or "ew" to fill the cell in the vertical direction. To fill the entire cell, set sticky to "nsew". The following example illustrates each of these options:

import tkinter as tk

window = tk.Tk()

window.rowconfigure(0, minsize=50)
window.columnconfigure([0, 1, 2, 3], minsize=50)

label1 = tk.Label(text="1", bg="black", fg="white")
label2 = tk.Label(text="2", bg="black", fg="white")
label3 = tk.Label(text="3", bg="black", fg="white")
label4 = tk.Label(text="4", bg="black", fg="white")

label1.grid(row=0, column=0)
label2.grid(row=0, column=1, sticky="ew")
label3.grid(row=0, column=2, sticky="ns")
label4.grid(row=0, column=3, sticky="nsew")

window.mainloop()

Here's what the output looks like:

A Tkinter window with grid geometry manager and sticky used to fill horizontally, vertically, and along both axes.

What the above example illustrates is that the .grid() geometry manager's sticky parameter can be used to achieve the same effects as the .pack() geometry manager's fill parameter. The correspondence between the sticky and fill parameters is summarized in the following table:

.grid() .pack()
sticky="ns" fill=tk.Y
sticky="ew" fill=tk.X
sticky="nsew" fill=tk.BOTH

.grid() is a powerful geometry manager. It's often easier to understand than .pack() and is much more flexible than .place(). When you're creating new Tkinter applications, you should consider using .grid() as your primary geometry manager.

Note: .grid() offers much more flexibility than you've seen here. For example, you can configure cells to span multiple rows and columns. For more information, check out the Grid Geometry Manager section of the TkDocs tutorial.

Now that you've got the fundamentals of geometry managers down for the Python GUI framework Tkinter, the next step is to assign actions to buttons to bring your applications to life.

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Below is an image of an address entry form made with Tkinter.

An address entry form window built with Tkinter

Write a complete script that re-creates the window. You may use any geometry manager you like.

You can expand the code block below to see a solution:

There are many different ways to solve this exercise. If your solution generates a window identical to the one in the exercise statement, then congratulations! You've successfully solved the exercise! Below, you can look at two solutions that use the .grid() geometry manager.

One solution creates a Label and Entry widget for each field with the desired settings:

import tkinter as tk

# Create a new window with the title "Address Entry Form"
window = tk.Tk()
window.title("Address Entry Form")

# Create a new frame `frm_form` to contain the Label
# and Entry widgets for entering address information.
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
# Pack the frame into the window
frm_form.pack()

# Create the Label and Entry widgets for "First Name"
lbl_first_name = tk.Label(master=frm_form, text="First Name:")
ent_first_name = tk.Entry(master=frm_form, width=50)
# Use the grid geometry manager to place the Label and
# Entry widgets in the first and second columns of the
# first row of the grid
lbl_first_name.grid(row=0, column=0, sticky="e")
ent_first_name.grid(row=0, column=1)

# Create the Label and Entry widgets for "Last Name"
lbl_last_name = tk.Label(master=frm_form, text="Last Name:")
ent_last_name = tk.Entry(master=frm_form, width=50)
# Place the widgets in the second row of the grid
lbl_last_name.grid(row=1, column=0, sticky="e")
ent_last_name.grid(row=1, column=1)

# Create the Label and Entry widgets for "Address Line 1"
lbl_address1 = tk.Label(master=frm_form, text="Address Line 1:")
ent_address1 = tk.Entry(master=frm_form, width=50)
# Place the widgets in the third row of the grid
lbl_address1.grid(row=2, column=0, sticky="e")
ent_address1.grid(row=2, column=1)

# Create the Label and Entry widgets for "Address Line 2"
lbl_address2 = tk.Label(master=frm_form, text="Address Line 2:")
ent_address2 = tk.Entry(master=frm_form, width=5)
# Place the widgets in the fourth row of the grid
lbl_address2.grid(row=3, column=0, sticky=tk.E)
ent_address2.grid(row=3, column=1)

# Create the Label and Entry widgets for "City"
lbl_city = tk.Label(master=frm_form, text="City:")
ent_city = tk.Entry(master=frm_form, width=50)
# Place the widgets in the fifth row of the grid
lbl_city.grid(row=4, column=0, sticky=tk.E)
ent_city.grid(row=4, column=1)

# Create the Label and Entry widgets for "State/Province"
lbl_state = tk.Label(master=frm_form, text="State/Province:")
ent_state = tk.Entry(master=frm_form, width=50)
# Place the widgets in the sixth row of the grid
lbl_state.grid(row=5, column=0, sticky=tk.E)
ent_state.grid(row=5, column=1)

# Create the Label and Entry widgets for "Postal Code"
lbl_postal_code = tk.Label(master=frm_form, text="Postal Code:")
ent_postal_code = tk.Entry(master=frm_form, width=50)
# Place the widgets in the seventh row of the grid
lbl_postal_code.grid(row=6, column=0, sticky=tk.E)
ent_postal_code.grid(row=6, column=1)

# Create the Label and Entry widgets for "Country"
lbl_country = tk.Label(master=frm_form, text="Country:")
ent_country = tk.Entry(master=frm_form, width=50)
# Place the widgets in the eight row of the grid
lbl_country.grid(row=7, column=0, sticky=tk.E)
ent_country.grid(row=7, column=1)

# Create a new frame `frm_buttons` to contain the
# Submit and Clear buttons. This frame fills the
# whole window in the horizontal direction and has
# 5 pixels of horizontal and vertical padding.
frm_buttons = tk.Frame()
frm_buttons.pack(fill=tk.X, ipadx=5, ipady=5)

# Create the "Submit" button and pack it to the
# right side of `frm_buttons`
btn_submit = tk.Button(master=frm_buttons, text="Submit")
btn_submit.pack(side=tk.RIGHT, padx=10, ipadx=10)

# Create the "Clear" button and pack it to the
# right side of `frm_buttons`
btn_clear = tk.Button(master=frm_buttons, text="Clear")
btn_clear.pack(side=tk.RIGHT, ipadx=10)

# Start the application
window.mainloop()

There's nothing wrong with this solution. It's a bit long, but everything is very explicit. If you want to change something, then it's clear to see exactly where to do so.

That said, the solution can be considerably shortened by recognizing that each Entry has the same width, and that all you need for each Label is the text:

import tkinter as tk

# Create a new window with the title "Address Entry Form"
window = tk.Tk()
window.title("Address Entry Form")

# Create a new frame `frm_form` to contain the Label
# and Entry widgets for entering address information.
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
# Pack the frame into the window
frm_form.pack()

# List of field labels
labels = [
    "First Name:",
    "Last Name:",
    "Address Line 1:",
    "Address Line 2:",
    "City:",
    "State/Province:",
    "Postal Code:",
    "Country:",
]

# Loop over the list of field labels
for idx, text in enumerate(labels):
    # Create a Label widget with the text from the labels list
    label = tk.Label(master=frm_form, text=text)
    # Create an Entry widget
    entry = tk.Entry(master=frm_form, width=50)
    # Use the grid geometry manager to place the Label and
    # Entry widgets in the row whose index is idx
    label.grid(row=idx, column=0, sticky="e")
    entry.grid(row=idx, column=1)

# Create a new frame `frm_buttons` to contain the
# Submit and Clear buttons. This frame fills the
# whole window in the horizontal direction and has
# 5 pixels of horizontal and vertical padding.
frm_buttons = tk.Frame()
frm_buttons.pack(fill=tk.X, ipadx=5, ipady=5)

# Create the "Submit" button and pack it to the
# right side of `frm_buttons`
btn_submit = tk.Button(master=frm_buttons, text="Submit")
btn_submit.pack(side=tk.RIGHT, padx=10, ipadx=10)

# Create the "Clear" button and pack it to the
# right side of `frm_buttons`
btn_clear = tk.Button(master=frm_buttons, text="Clear")
btn_clear.pack(side=tk.RIGHT, ipadx=10)

# Start the application
window.mainloop()

In this solution, a list is used to store the strings for each Label in the form. They're stored in the order that each form field should appear. Then, enumerate() gets both the index and string from each value in the labels list.

When you're ready, you can move on to the next section.

Making Your Applications Interactive

By now, you have a pretty good idea of how to create a window with Tkinter, add some widgets, and control the application layout. That's great, but applications shouldn't just look good-they actually need to do something! In this section, you'll learn how to bring your applications to life by performing actions whenever certain events occur.

Using Events and Event Handlers

When you create a Tkinter application, you must call window.mainloop() to start the event loop. During the event loop, your application checks if an event has occurred. If so, then some code can be executed in response.

The event loop is provided for you with Tkinter, so you don't have to write any code that checks for events yourself. However, you do have to write the code that will be executed in response to an event. In Tkinter, you write functions called event handlers for the events that you use in your application.

Note: An event is any action that occurs during the event loop that might trigger some behavior in the application, such as when a key or mouse button is pressed.

When an event occurs, an event object is emitted, which means that an instance of a class representing the event is instantiated. You don't need to worry about creating these classes yourself. Tkinter will create instances of event classes for you automatically.

You'll write your own event loop in order to understand better how Tkinter's event loop works. That way, you can see how Tkinter's event loop fits into your application, and which parts you need to write yourself.

Assume there's a list called events_list that contains event objects. A new event object is automatically appended to events_list every time an event occurs in your program. (You don't need to implement this updating mechanism. It just automatically happens for you in this conceptual example.) Using an infinite loop, you can continually check if there are any event objects in events_list:

# Assume that this list gets updated automatically
events_list = []

# Run the event loop
while True:
    # If events_list is empty, then no events have occurred and you
    # can skip to the next iteration of the loop
    if events_list == []:
        continue

    # If execution reaches this point, then there is at least one
    # event object in events_list
    event = events_list[0]

Right now, the event loop you've created doesn't do anything with event. Let's change that. Suppose your application needs to respond to keypresses. You need to check that event was generated by a user pressing a key on their keyboard, and, if so, pass event to an event handler function for key presses.

Assume that event has a .type attribute set to the string "keypress" if the event is a keypress event object, and a .char attribute containing the character of the key that was pressed. Create a new function handle_keypress() and update your event loop code:

events_list = []

# Create an event handler
def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

while True:
    if events_list == []:
        continue
    event = events_list[0]

    # If event is a keypress event object
    if event.type == "keypress":
        # Call the keypress event handler
        handle_keypress(event)

When you call window.mainloop(), something like the above loop is run for you. This method takes care of two parts of the loop for you:

  1. It maintains a list of events that have occurred.
  2. It runs an event handler any time a new event is added to that list.

Update your event loop to use window.mainloop() instead of your own event loop:

import tkinter as tk

# Create a window object
window = tk.Tk()

# Create an event handler
def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

# Run the event loop
window.mainloop()

.mainloop() takes care of a lot for you, but there's something missing from the above code. How does Tkinter know when to use handle_keypress()? Tkinter widgets have a method called .bind() for just this purpose.

Using .bind()

To call an event handler whenever an event occurs on a widget, use .bind(). The event handler is said to be bound to the event because it's called every time the event occurs. You'll continue with the keypress example from the previous section and use .bind() to bind handle_keypress() to the keypress event:

import tkinter as tk

window = tk.Tk()

def handle_keypress(event):
    """Print the character associated to the key pressed"""
    print(event.char)

# Bind keypress event to handle_keypress()
window.bind("<Key>", handle_keypress)

window.mainloop()

Here, the handle_keypress() event handler is bound to a "<Key>" event using window.bind(). Whenever a key is pressed while the application is running, your program will print the character of the key pressed.

Note: The output of the above program is not printed in the Tkinter application window. It is printed to stdout.

If you run the program in IDLE, you'll see the output in the interactive window. If you run the program from a terminal, you should see the output in your terminal.

.bind() always takes at least two arguments:

  1. An event that's represented by a string of the form "<event_name>", where event_name can be any of Tkinter's events
  2. An event handler that's the name of the function to be called whenever the event occurs

The event handler is bound to the widget on which .bind() is called. When the event handler is called, the event object is passed to the event handler function.

In the example above, the event handler is bound to the window itself, but you can bind an event handler to any widget in your application. For example, you can bind an event handler to a Button widget that will perform some action whenever the button is pressed:

def handle_click(event):
    print("The button was clicked!")

button = tk.Button(text="Click me!")

button.bind("<Button-1>", handle_click)

In this example, the "<Button-1>" event on the button widget is bound to the handle_click event handler. The "<Button-1>" event occurs whenever the left mouse button is pressed while the mouse is over the widget. There are other events for mouse button clicks, including "<Button-2>" for the middle mouse button and "<Button-3>" for the right mouse button.

Note: For a list of commonly used events, see the Event types section of the Tkinter 8.5 reference.

You can bind any event handler to any kind of widget with .bind(), but there's an easier way to bind event handlers to button clicks using the Button widget's command attribute.

Using command

Every Button widget has a command attribute that you can assign to a function. Whenever the button is pressed, the function is executed.

Take a look at an example. First, you'll create a window with a Label widget that holds a numerical value. You'll put buttons on the left and right side of the label. The left button will be used to decrease the value in the Label, and the right one will increase the value. Here's the code for the window:

import tkinter as tk

window = tk.Tk()

window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)

btn_decrease = tk.Button(master=window, text="-")
btn_decrease.grid(row=0, column=0, sticky="nsew")

lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)

btn_increase = tk.Button(master=window, text="+")
btn_increase.grid(row=0, column=2, sticky="nsew")

window.mainloop()

The window looks like this:

A Tkinter application with increase and decrease buttons that increase and decrease a counter

With the app layout defined, you can bring it to life by giving the buttons some commands. Start with the left button. When this button is pressed, it should decrease the value in the label by 1. There are two things you need to know how to do in order to do this:

  1. How do you get the text in a Label?
  2. How do you update the text in a Label?

Label widgets don't have .get() like Entry and Text widgets do. However, you can retrieve the text from the label by accessing the text attribute with a dictionary-style subscript notation:

label = Tk.Label(text="Hello")

# Retrieve a Label's text
text = label["text"]

# Set new text for the label
label["text"] = "Good bye"

Now that you know how to get and set a label's text, write a function increase() that increases the value in the lbl_value by 1:

def increase():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value + 1}"

increase() gets the text from lbl_value and converts it to an integer with int(). Then, it increases this value by 1 and sets the label's text attribute to this new value.

You'll also need decrease() to decrease the value in value_label by 1:

def decrease():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value - 1}"

Put increase() and decrease() in your code just after the import statement.

To connect the buttons to the functions, assign the function to the button's command attribute. You can do this when you instantiate the button. For example, to assign increase() to increase_button, update the line that instantiates the button to the following:

btn_increase = tk.Button(master=window, text="+", command=increase)

Now assign decrease() to decrease_button:

btn_decrease = tk.Button(master=window, text="-", command=decrease)

That's all you need to do to bind the buttons to increase() and decrease() and make the program functional. Try saving your changes and running the application! Click the buttons to increase and decrease the value in the center of the window:

A counter app built with Tkinter

Here's the full application code for your reference:

import tkinter as tk

def increase():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value + 1}"


def decrease():
    value = int(lbl_value["text"])
    lbl_value["text"] = f"{value - 1}"

window = tk.Tk()

window.rowconfigure(0, minsize=50, weight=1)
window.columnconfigure([0, 1, 2], minsize=50, weight=1)

btn_decrease = tk.Button(master=window, text="-", command=decrease)
btn_decrease.grid(row=0, column=0, sticky="nsew")

lbl_value = tk.Label(master=window, text="0")
lbl_value.grid(row=0, column=1)

btn_increase = tk.Button(master=window, text="+", command=increase)
btn_increase.grid(row=0, column=2, sticky="nsew")

window.mainloop()

This app is not particularly useful, but the skills you learned here apply to every app you'll make:

In the next two sections, you'll build apps that do something useful. First, you'll build a temperature converter that converts a temperature value from Fahrenheit to Celsius. After that, you'll build a text editor that can open, edit, and save text files!

Check Your Understanding

Expand the code block below for an exercise to check your understanding:

Write a program that simulates rolling a six-sided die. There should be one button with the text "Roll". When the user clicks the button, a random integer from 1 to 6 should be displayed.

Hint: You can generate a random number using randint() in the random module. If you're not familiar with the random module, then check out Generating Random Data in Python (Guide) for more information.

The application window should look something like this:

A Tkinter application with a

Try this exercise now.

You can expand the code block below to see a solution:

Here's one possible solution:

import random
import tkinter as tk

def roll():
    lbl_result["text"] = str(random.randint(1, 6))

window = tk.Tk()
window.columnconfigure(0, minsize=150)
window.rowconfigure([0, 1], minsize=50)

btn_roll = tk.Button(text="Roll", command=roll)
lbl_result = tk.Label()

btn_roll.grid(row=0, column=0, sticky="nsew")
lbl_result.grid(row=1, column=0)

window.mainloop()

Keep in mind your code may look different.

When you're ready, you can move on to the next section.

Building a Temperature Converter (Example App)

In this section, you'll build a temperature converter application that allows the user to input temperature in degrees Fahrenheit and push a button to convert that temperature to degrees Celsius. You'll walk through the code step by step. You can also find the full source code at the end of this section for your reference.

Note: To get the most out of this section, follow along in a Python shell.

Before you start coding, you'll first design the app. You need three elements:

  1. An Entry widget called ent_temperature to enter the Fahrenheit value
  2. A Label widget called lbl_result to display the Celsius result
  3. A Button widget called btn_convert that reads the value from the Entry widget, converts it from Fahrenheit to Celsius, and sets the text of the Label widget to the result when clicked

You can arrange these in a grid with a single row and one column for each widget. That gets you a minimally working application, but it isn't very user-friendly. Everything needs to have labels.

You'll put a label directly to the right of the ent_temperature widget containing the Fahrenheit symbol (℉) so that the user knows that the value ent_temperature should be in degrees Fahrenheit. To do this, set the label text to "\N{DEGREES FAHRENHEIT}", which uses Python's named Unicode character support to display the Fahrenheit symbol.

You can give btn_convert a little flair by setting it's text to the value "\N{RIGHTWARDS BLACK ARROW}", which displays a black arrow pointing to the right. You'll also make sure that lbl_result always has the Celsius symbol (℃) following the label text "\N{DEGREES CELSIUS}" to indicate that the result is in degrees Celsius. Here's what the final window will look like:

A temperature conversion application built with Tkinter

Now that you know what widgets you need and what the window is going to look like, you can start coding it up! First, import tkinter and create a new window:

import tkinter as tk

window = tk.Tk()
window.title("Temperature Converter")

window.title() sets the title of an existing window. When you finally run this application, the window will have the text Temperature Converter in its title bar. Next, create the ent_temperature widget with a label called lbl_temp and assign both to a Frame widget called frm_entry:

frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")

ent_temperature is where the user will enter the Fahrenheit value. lbl_temp is used to label ent_temperature with the Fahrenheit symbol. frm_entry is a container that groups ent_temperature and lbl_temp together.

You want lbl_temp to be placed directly to the right of ent_temperature. You can lay them out in the frm_entry using the .grid() geometry manager with one row and two columns:

ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")

You've set the sticky parameter to "e" for ent_temperature so that it always sticks to the right-most edge of its grid cell. You also set sticky to "w" for lbl_temp to keep it stuck to the left-most edge of its grid cell. This ensures that lbl_temp is always located immediately to the right of ent_temperature.

Now, make the btn_convert and the lbl_result for converting the temperature entered into ent_temperature and displaying the results:

btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}"
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")

Like frm_entry, both btn_convert and lbl_result are assigned to window. Together, these three widgets make up the three cells in the main application grid. Use .grid() to go ahead and lay them out now:

frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)

Finally, run the application:

window.mainloop()

That looks great! But the button doesn't do anything just yet. At the top of your script file, just below the import line, add a function called fahrenheit_to_celsius():

def fahrenheit_to_celsius():
    """Convert the value for Fahrenheit to Celsius and insert the
    result into lbl_result.
    """
    fahrenheit = ent_temperature.get()
    celsius = (5/9) * (float(fahrenheit) - 32)
    lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"

This function reads the value from ent_temperature, converts it from Fahrenheit to Celsius, and then displays the result in lbl_result.

Now go down to the line where you define btn_convert and set its command parameter to fahrenheit_to_celsius:

btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}",
    command=fahrenheit_to_celsius  # <--- Add this line
)

That's it! You've created a fully functional temperature converter app in just 26 lines of code! Pretty cool, right?

You can expand the code block below to see the full script:

Here's the full script for your reference:

import tkinter as tk

def fahrenheit_to_celsius():
    """Convert the value for Fahrenheit to Celsius and insert the
    result into lbl_result.
    """
    fahrenheit = ent_temperature.get()
    celsius = (5/9) * (float(fahrenheit) - 32)
    lbl_result["text"] = f"{round(celsius, 2)} \N{DEGREE CELSIUS}"

# Set-up the window
window = tk.Tk()
window.title("Temperature Converter")
window.resizable(width=False, height=False)

# Create the Fahrenheit entry frame with an Entry
# widget and label in it
frm_entry = tk.Frame(master=window)
ent_temperature = tk.Entry(master=frm_entry, width=10)
lbl_temp = tk.Label(master=frm_entry, text="\N{DEGREE FAHRENHEIT}")

# Layout the temperature Entry and Label in frm_entry
# using the .grid() geometry manager
ent_temperature.grid(row=0, column=0, sticky="e")
lbl_temp.grid(row=0, column=1, sticky="w")

# Create the conversion Button and result display Label
btn_convert = tk.Button(
    master=window,
    text="\N{RIGHTWARDS BLACK ARROW}",
    command=fahrenheit_to_celsius
)
lbl_result = tk.Label(master=window, text="\N{DEGREE CELSIUS}")

# Set-up the layout using the .grid() geometry manager
frm_entry.grid(row=0, column=0, padx=10)
btn_convert.grid(row=0, column=1, pady=10)
lbl_result.grid(row=0, column=2, padx=10)

# Run the application
window.mainloop()

It's time to kick things up a notch! Read on to learn how to build a text editor.

Building a Text Editor (Example App)

In this section, you'll build a text editor application that can create, open, edit, and save text files. There are three essential elements in the application:

  1. A Button widget called btn_open for opening a file for editing
  2. A Button widget called btn_save for saving a file
  3. A TextBox widget called txt_edit for creating and editing the text file

The three widgets will be arranged so that the two buttons are on the left-hand side of the window, and the text box is on the right-hand side. The whole window should have a minimum height of 800 pixels, and txt_edit should have a minimum width of 800 pixels. The whole layout should be responsive so that if the window is resized, then txt_edit is resized as well. The width of the Frame holding the buttons should not change, however.

Here's a sketch of how the window will look:

A design sketch for a text editor application

You can achieve the desired layout using the .grid() geometry manager. The layout contains a single row and two columns:

  1. A narrow column on the left for the buttons
  2. A wider column on the right for the text box

To set the minimum sizes for the window and txt_edit, you can set the minsize parameters of the window methods .rowconfigure() and .columnconfigure() to 800. To handle resizing, you can set the weight parameters of these methods to 1.

In order to get both buttons into the same column, you'll need to create a Frame widget called fr_buttons. According to the sketch, the two buttons should be stacked vertically inside of this frame, with btn_open on top. You can do that with either the .grid() or .pack() geometry manager. For now, you'll stick with .grid() since it's a little easier to work with.

Now that you have a plan, you can start coding the application. The first step is to create the all of the widgets you need:

 1 import tkinter as tk
 2 
 3 window = tk.Tk()
 4 window.title("Simple Text Editor")
 5 
 6 window.rowconfigure(0, minsize=800, weight=1)
 7 window.columnconfigure(1, minsize=800, weight=1)
 8 
 9 txt_edit = tk.Text(window)
10 fr_buttons = tk.Frame(window)
11 btn_open = tk.Button(fr_buttons, text="Open")
12 btn_save = tk.Button(fr_buttons, text="Save As...")

Here's a breakdown of this code:

Take a look at line 6 more closely. The minsize parameter of .rowconfigure() is set to 800 and weight is set to 1:

window.rowconfigure(0, minsize=800, weight=1)

The first argument is 0, which sets the height of the first row to 800 pixels and makes sure that the height of the row grows proportionally to the height of the window. There's only one row in the application layout, so these settings apply to the entire window.

Let's also take a closer look at line 7. Here, you use .columnconfigure() to set the width and weight attributes of the column with index 1 to 800 and 1, respectively:

window.columnconfigure(1, minsize=800, weight=1)

Remember, row and column indices are zero-based, so these settings apply only to the second column. By configuring just the second column, the text box will expand and contract naturally when the window is resized, while the column containing the buttons will remain at a fixed width.

Now you can work on the application layout. First, assign the two buttons to the fr_buttons frame using the .grid() geometry manager:

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

These two lines of code create a grid with two rows and one column in the fr_buttons frame since both btn_open and btn_save have their master attribute set to fr_buttons. btn_open is put in the first row and btn_save in the second row so that btn_open appears above btn_save in the layout, just you planned in your sketch.

Both btn_open and btn_save have their sticky attributes set to "ew", which forces the buttons to expand horizontally in both directions and fill the entire frame. This makes sure both buttons are the same size.

You place 5 pixels of padding around each button by setting the padx and pady parameters to 5. Only btn_open has vertical padding. Since it's on top, the vertical padding offsets the button down from the top of the window a bit and makes sure that there's a small gap between it and btn_save.

Now that fr_buttons is laid out and ready to go, you can set up the grid layout for the rest of the window:

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

These two lines of code create a grid with one row and two columns for window. You place fr_buttons in the first column and txt_edit in the second column so that fr_buttons appears to the left of txt_edit in the window layout.

The sticky parameter for fr_buttons is set to "ns", which forces the whole frame to expand vertically and fill the entire height of its column. txt_edit fills its entire grid cell because you set its sticky parameter to "nsew", which forces it to expand in every direction.

Now that the application layout is complete, add window.mainloop() to the bottom of the program and save and run the file. The following window is displayed:

A text editor application made with Tkinter

That looks great! But it doesn't do anything just yet, so you need to start writing the commands for the buttons. btn_open needs to show a file open dialog and allow the user to select a file. It then needs to open that file and set the text of txt_edit to the contents of the file. Here's a function open_file() that does just this:

 1 def open_file():
 2     """Open a file for editing."""
 3     filepath = askopenfilename(
 4         filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
 5     )
 6     if not filepath:
 7         return
 8     txt_edit.delete("1.0", tk.END)
 9     with open(filepath, "r") as input_file:
10         text = input_file.read()
11         txt_edit.insert(tk.END, text)
12     window.title(f"Simple Text Editor - {filepath}")

Here's a breakdown of this function:

Now you can update the program so that btn_open calls open_file() whenever it's clicked. There are a few things you need to do to update the program. First, import askopenfilename() from tkinter.filedialog by adding the following import to the top of your program:

import tkinter as tk
from tkinter.filedialog import askopenfilename

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open")
btn_save = tk.Button(fr_buttons, text="Save As...")

Next, add the definition of open_file() just below the import statements:

import tkinter as tk
from tkinter.filedialog import askopenfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete("1.0", tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open")
btn_save = tk.Button(fr_buttons, text="Save As...")

Finally, set the command attribute of btn_opn to open_file:

import tkinter as tk
from tkinter.filedialog import askopenfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete("1.0", tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")

window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

Save the file and run it to check that everything is working. Then try opening a text file!

With btn_open working, it's time to work on the function for btn_save. This needs to open a save file dialog box so that the user can choose where they would like to save the file. You'll use the asksaveasfilename dialog in the tkinter.filedialog module for this. This function also needs to extract the text currently in txt_edit and write this to a file at the selected location. Here's a function that does just this:

 1 def save_file():
 2     """Save the current file as a new file."""
 3     filepath = asksaveasfilename(
 4         defaultextension="txt",
 5         filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
 6     )
 7     if not filepath:
 8         return
 9     with open(filepath, "w") as output_file:
10         text = txt_edit.get("1.0", tk.END)
11         output_file.write(text)
12     window.title(f"Simple Text Editor - {filepath}")

Here's how this code works:

Now you can update the program so that btn_save calls save_file() when it's clicked. Again, there are a few things you need to do in order to update the program. First, import asksaveasfilename() from tkinter.filedialog by updating the import at the top of your script, like so:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Next, add the definition of save_file() just below the open_file() definition:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...")

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Finally, set the command attribute of btn_save to save_file:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...", command=save_file)

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

Save the file and run it. You've now got a minimal yet fully-functional text editor!

You can expand the code block below to see the full script:

Here's the full script for your reference:

import tkinter as tk
from tkinter.filedialog import askopenfilename, asksaveasfilename

def open_file():
    """Open a file for editing."""
    filepath = askopenfilename(
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
    )
    if not filepath:
        return
    txt_edit.delete(1.0, tk.END)
    with open(filepath, "r") as input_file:
        text = input_file.read()
        txt_edit.insert(tk.END, text)
    window.title(f"Simple Text Editor - {filepath}")

def save_file():
    """Save the current file as a new file."""
    filepath = asksaveasfilename(
        defaultextension="txt",
        filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")],
    )
    if not filepath:
        return
    with open(filepath, "w") as output_file:
        text = txt_edit.get(1.0, tk.END)
        output_file.write(text)
    window.title(f"Simple Text Editor - {filepath}")

window = tk.Tk()
window.title("Simple Text Editor")
window.rowconfigure(0, minsize=800, weight=1)
window.columnconfigure(1, minsize=800, weight=1)

txt_edit = tk.Text(window)
fr_buttons = tk.Frame(window, relief=tk.RAISED, bd=2)
btn_open = tk.Button(fr_buttons, text="Open", command=open_file)
btn_save = tk.Button(fr_buttons, text="Save As...", command=save_file)

btn_open.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
btn_save.grid(row=1, column=0, sticky="ew", padx=5)

fr_buttons.grid(row=0, column=0, sticky="ns")
txt_edit.grid(row=0, column=1, sticky="nsew")

window.mainloop()

You've now built two GUI applications in Python and applied many of the topics you've learned about throughout this tutorial. That's no small achievement, so take some time to feel good about what you've done. You're now ready to tackle some applications on your own!

Conclusion

In this tutorial, you learned how to get started with Python GUI programming. Tkinter is a compelling choice for a Python GUI framework because it's built into the Python standard library, and it's relatively painless to make applications with this framework.

Throughout this tutorial, you've learned several important Tkinter concepts:

Now that you've mastered the foundations of Python GUI programming with Tkinter, the next step is to build some of your own applications. What will you create? Share your fun projects down in the comments below!

Additional Resources

In this tutorial, you touched on just the foundations of creating Python GUI applications with Tkinter. There are a number of additional topics that aren't covered here. In this section, you'll find some of the best resources available to help you continue on your journey.

Tkinter References

Here are some official resources to check out:

Additional Widgets

In this tutorial, you learned about the Label, Button, Entry, Text, and Frame widgets. There are several other widgets in Tkinter, all of which are essential for building real-world applications. Here are some resources to continue learning about widgets:

Application Distribution

Once you've created an application with Tkinter, you probably want to distribute that to your colleagues and friends. Here are some tutorials to get you going with that process:

Other GUI Frameworks

Tkinter is not your only choice for a Python GUI framework. If Tkinter doesn't meet the needs of your project, then here are some other frameworks to consider:


[ Improve Your Python With 🐍 Python Tricks 💌 - Get a short & sweet Python Trick delivered to your inbox every couple of days. >> Click here to learn more and see examples ]

22 Jan 2020 2:00pm GMT

Python Circle: Using IF ELSE condition in Django template

how to use IF ELSE in Django template, Syntax of IF, ELIF and ELSE in Django, Using filters within IF condition in Django template, Multiple elif (else if) confitions in Django template

22 Jan 2020 1:46pm GMT

Python Circle: Using IF ELSE condition in Django template

how to use IF ELSE in Django template, Syntax of IF, ELIF and ELSE in Django, Using filters within IF condition in Django template, Multiple elif (else if) confitions in Django template

22 Jan 2020 1:46pm GMT

Stack Abuse: Ensemble/Voting Classification in Python with Scikit-Learn

Introduction

Ensemble classification models can be powerful machine learning tools capable of achieving excellent performance and generalizing well to new, unseen datasets.

The value of an ensemble classifier is that, in joining together the predictions of multiple classifiers, it can correct for errors made by any individual classifier, leading to better accuracy overall. Let's take a look at the different ensemble classification methods and see how these classifiers can be implemented in Scikit-Learn.

What are Ensemble Models in Machine Learning?

alt
Credit: Pixabay

Ensemble models are an ensemble learning method that combines different algorithms together. In this sense, it is a meta-algorithm rather than an algorithm itself. Ensemble learning methods are valuable because they can improve the performance of a predictive model.

Ensemble learning methods work off of the idea that tying the predictions of multiple classifiers together will lead to better performance by either improving prediction accuracy or reducing aspects like bias and variance.

In general, an ensemble model falls into one of two categories: sequential approaches and parallel approaches.

A sequential ensemble model operates by having the base learners/models generated in sequence. Sequential ensemble methods are typically used to try and increase overall performance, as the ensemble model can compensate for inaccurate predictions by re-weighting the examples that were previously misclassified. A notable example of this is AdaBoost.

A parallel model is, as you may be able to guess, methods that rely on creating and training the base learners in parallel. Parallel methods aim to reduce the error rate by training many models in parallel and averaging the results together. A notable example of a parallel method is the Random Forest Classifier.

Another way of thinking about this is a distinction between homogenous and heterogeneous learners. While most of the ensemble learning methods use homogeneous base learners (many of the same type of learners), some ensemble methods use heterogeneous learners (different learning algorithms joined together).

To recap:

We'll now cover different methods of employing these models to solve machine learning classification problems.

Different Ensemble Classification Methods

Bagging

alt
Credit: Wikimedia Commons

Bagging, also known as bootstrap aggregating, is a classification method that aims to reduce the variance of estimates by averaging multiple estimates together. Bagging creates subsets from the main dataset that the learners are trained on.

In order for the predictions of the different classifiers to be aggregated, either an averaging is used for regression, or a voting approach is used for classification (based on the decision of the majority).

One example of a bagging classification method is the Random Forests Classifier. In the case of the random forests classifier, all the individual trees are trained on a different sample of the dataset.

The tree is also trained using random selections of features. When the results are averaged together, the overall variance decreases and the model performs better as a result.

Boosting

Boosting algorithms are capable of taking weak, underperforming models and converting them into strong models. The idea behind boosting algorithms is that you assign many weak learning models to the datasets, and then the weights for misclassified examples are tweaked during subsequent rounds of learning.

The predictions of the classifiers are aggregated and then the final predictions are made through a weighted sum (in the case of regressions), or a weighted majority vote (in the case of classification).

AdaBoost is one example of a boosting classifier method, as is Gradient Boosting, which was derived from the aforementioned algorithm.

If you'd like to read more about Gradient Boosting and the theory behind it, we've already covered that in a previous article.

Stacking

alt
Credit: Wikimedia Commons

Stacking algorithms are an ensemble learning method that combines the decision of different regression or classification algorithms. The component models are trained on the entire training dataset. After these component models are trained, a meta-model is assembled from the different models and then it's trained on the outputs of the component models. This approach typically creates a heterogeneous ensemble because the component models are usually different algorithms.

Example Implementations

Now that we've explored different methods we can use to create ensemble models, let's take a look at how we could implement a classifier using the different methods.

Though, before we can take a look at different ways of implementing ensemble classifiers, we need to select a dataset to use and do some preprocessing of the dataset.

We'll be using the Titanic dataset, which can be downloaded here. Let's do some preprocessing of the data in order to get rid of missing values and scale the data to a uniform range. Then we can go about setting up the ensemble classifiers.

Data Preprocessing

To begin with, we'll start by importing all functions we need from their respective libraries. We'll be using Pandas and Numpy to load and transform the data, as well as the LabelEncoder and StandardScaler tools.

We'll also need the machine learning metrics and the train_test_split function. Finally, we'll need the classifiers we want to use:

import pandas as pd
import numpy as np
import warnings

from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import accuracy_score, f1_score, log_loss
from sklearn.model_selection import train_test_split, KFold, cross_val_score

from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier, ExtraTreesClassifier

We'll start by loading in the training and testing data and then creating a function to check for the presence of any null values:

training_data = pd.read_csv("train.csv")
testing_data = pd.read_csv("test.csv")

def get_nulls(training, testing):
    print("Training Data:")
    print(pd.isnull(training).sum())
    print("Testing Data:")
    print(pd.isnull(testing).sum())

get_nulls(training_data, testing_data)

As it happens, there are a lot of missing values in the Age and Cabin categories.

Training Data:
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
Testing Data:
PassengerId      0
Pclass           0
Name             0
Sex              0
Age             86
SibSp            0
Parch            0
Ticket           0
Fare             1
Cabin          327
Embarked         0
dtype: int64

We're going to start by dropping some of the columns that will likely be useless - the Cabin column and the Ticket column. The Cabin column has far too many missing values and the Ticket column is simply comprised of too many categories to be useful.

After that we will need to impute some missing values. When we do so, we must account for how the dataset is slightly right skewed (young ages are slightly more prominent than older ages). We'll use the median values when we impute the data because due to large outliers taking the average values would give us imputed values that are far from the center of the dataset:

# Drop the cabin column, as there are too many missing values
# Drop the ticket numbers too, as there are too many categories
# Drop names as they won't really help predict survivors

training_data.drop(labels=['Cabin', 'Ticket', 'Name'], axis=1, inplace=True)
testing_data.drop(labels=['Cabin', 'Ticket', 'Name'], axis=1, inplace=True)

# Taking the mean/average value would be impacted by the skew
# so we should use the median value to impute missing values

training_data["Age"].fillna(training_data["Age"].median(), inplace=True)
testing_data["Age"].fillna(testing_data["Age"].median(), inplace=True)
training_data["Embarked"].fillna("S", inplace=True)
testing_data["Fare"].fillna(testing_data["Fare"].median(), inplace=True)

get_nulls(training_data, testing_data)

Now we can see there's no more missing values:

Training Data:
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Fare           0
Embarked       0
dtype: int64
Testing Data:
PassengerId    0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Fare           0
Embarked       0
dtype: int64

We're now going to need to encode the non-numerical data. Let's set up a LabelEncoder and fit it on the Sex feature and then transform the data with the encoder. We'll then replace the values in the Sex feature with those that have been encoded and then do the same for the Embarked feature.

Finally, let's scale the data using the StandardScaler, so there aren't huge fluctuations in values.

encoder_1 = LabelEncoder()
# Fit the encoder on the data
encoder_1.fit(training_data["Sex"])

# Transform and replace training data
training_sex_encoded = encoder_1.transform(training_data["Sex"])
training_data["Sex"] = training_sex_encoded
test_sex_encoded = encoder_1.transform(testing_data["Sex"])
testing_data["Sex"] = test_sex_encoded

encoder_2 = LabelEncoder()
encoder_2.fit(training_data["Embarked"])

training_embarked_encoded = encoder_2.transform(training_data["Embarked"])
training_data["Embarked"] = training_embarked_encoded
testing_embarked_encoded = encoder_2.transform(testing_data["Embarked"])
testing_data["Embarked"] = testing_embarked_encoded

# Any value we want to reshape needs be turned into array first
ages_train = np.array(training_data["Age"]).reshape(-1, 1)
fares_train = np.array(training_data["Fare"]).reshape(-1, 1)
ages_test = np.array(testing_data["Age"]).reshape(-1, 1)
fares_test = np.array(testing_data["Fare"]).reshape(-1, 1)

# Scaler takes arrays
scaler = StandardScaler()

training_data["Age"] = scaler.fit_transform(ages_train)
training_data["Fare"] = scaler.fit_transform(fares_train)
testing_data["Age"] = scaler.fit_transform(ages_test)
testing_data["Fare"] = scaler.fit_transform(fares_test)

Now that our data has been preprocessed, we can select our features and labels and then use the train_test_split function to divide our entire training data up into training and testing sets:

# Now to select our training/testing data
X_features = training_data.drop(labels=['PassengerId', 'Survived'], axis=1)
y_labels = training_data['Survived']

print(X_features.head(5))

# Make the train/test data from validation

X_train, X_val, y_train, y_val = train_test_split(X_features, y_labels, test_size=0.1, random_state=27)

We're now ready to start implementing ensemble classification methods.

Simple Averaging Approach

Before we get into the big three ensemble methods we covered earlier, let's cover a very quick and easy method of using an ensemble approach - averaging predictions. We simply add the different predicted values of our chosen classifiers together and then divide by the total number of classifiers, using floor division to get a whole value.

In this test case we'll be using logistic regression, a Decision Tree Classifier, and the Support Vector Classifier. We fit the classifiers on the data and then save the predictions as variables. Then we simply add the predictions together and divide:

LogReg_clf = LogisticRegression()
DTree_clf = DecisionTreeClassifier()
SVC_clf = SVC()

LogReg_clf.fit(X_train, y_train)
DTree_clf.fit(X_train, y_train)
SVC_clf.fit(X_train, y_train)

LogReg_pred = LogReg_clf.predict(X_val)
DTree_pred = DTree_clf.predict(X_val)
SVC_pred = SVC_clf.predict(X_val)

averaged_preds = (LogReg_pred + DTree_pred + SVC_pred)//3
acc = accuracy_score(y_val, averaged_preds)
print(acc)

Here's the accuracy we got from this method:

0.8444444444444444

Voting\Stacking Classification Example

When it comes to creating a stacking/voting classifier, Scikit-Learn provides us with some handy functions that we can use to accomplish this.

The VotingClassifier takes in a list of different estimators as arguments and a voting method. The hard voting method uses the predicted labels and a majority rules system, while the soft voting method predicts a label based on the argmax/largest predicted value of the sum of the predicted probabilities.

After we provide the desired classifiers, we need to fit the resulting ensemble classifier object. We can then get predictions and use accuracy metrics:

voting_clf = VotingClassifier(estimators=[('SVC', SVC_clf), ('DTree', DTree_clf), ('LogReg', LogReg_clf)], voting='hard')
voting_clf.fit(X_train, y_train)
preds = voting_clf.predict(X_val)
acc = accuracy_score(y_val, preds)
l_loss = log_loss(y_val, preds)
f1 = f1_score(y_val, preds)

print("Accuracy is: " + str(acc))
print("Log Loss is: " + str(l_loss))
print("F1 Score is: " + str(f1))

Here's what the metrics have to say about the VotingClassifier's performance:

Accuracy is: 0.8888888888888888
Log Loss is: 3.8376684749044165
F1 Score is: 0.8484848484848486

Bagging Classification Example

Here's how we can implement bagging classification with Scikit-Learn. Sklearn's BaggingClassifier takes in a chosen classification model as well as the number of estimators that you want to use - you can use a model like Logistic Regression or Decision Trees.

Sklearn also provides access to the RandomForestClassifier and the ExtraTreesClassifier, which are modifications of the decision tree classification. These classifiers can also be used alongside the K-folds cross-validation tool.

We'll compare several different bagging classification approaches here, printing out the mean results of the K-fold cross validation score:

logreg_bagging_model = BaggingClassifier(base_estimator=LogReg_clf, n_estimators=50, random_state=12)
dtree_bagging_model = BaggingClassifier(base_estimator=DTree_clf, n_estimators=50, random_state=12)
random_forest = RandomForestClassifier(n_estimators=100, random_state=12)
extra_trees = ExtraTreesClassifier(n_estimators=100, random_state=12)

def bagging_ensemble(model):
    k_folds = KFold(n_splits=20, random_state=12)
    results = cross_val_score(model, X_train, y_train, cv=k_folds)
    print(results.mean())

bagging_ensemble(logreg_bagging_model)
bagging_ensemble(dtree_bagging_model)
bagging_ensemble(random_forest)
bagging_ensemble(extra_trees)

Here's the results we got from the classifiers:

0.7865853658536585
0.8102439024390244
0.8002439024390245
0.7902439024390244

Boosting Classification Example

Finally, we'll take a look at how to use a boosting classification method. As mentioned, there's a separate article on the topic of Gradient Boosting you can read here.

Scikit-Learn has a built-in AdaBoost classifier, which takes in a given number of estimators as the first argument. We can try using a for loop to see how the classification performance changes at different values, and we can also combine it with the K-Folds cross validation tool:

k_folds = KFold(n_splits=20, random_state=12)

num_estimators = [20, 40, 60, 80, 100]

for i in num_estimators:
    ada_boost = AdaBoostClassifier(n_estimators=i, random_state=12)
    results = cross_val_score(ada_boost, X_train, y_train, cv=k_folds)
    print("Results for {} estimators:".format(i))
    print(results.mean())

Here's the results we got:

Results for 20 estimators:
0.8015243902439024
Results for 40 estimators:
0.8052743902439025
Results for 60 estimators:
0.8053048780487805
Results for 80 estimators:
0.8040243902439024
Results for 100 estimators:
0.8027743902439024

Summing Up

We've covered the ideas behind three different ensemble classification techniques: voting\stacking, bagging, and boosting.

Scikit-Learn allows you to easily create instances of the different ensemble classifiers. These ensemble objects can be combined with other Scikit-Learn tools like K-Folds cross validation.

If you'd like to learn more about appropriate uses for ensemble classifiers, and the theories behind them, I suggest checking out the links found here or here.

22 Jan 2020 1:36pm GMT

Stack Abuse: Ensemble/Voting Classification in Python with Scikit-Learn

Introduction

Ensemble classification models can be powerful machine learning tools capable of achieving excellent performance and generalizing well to new, unseen datasets.

The value of an ensemble classifier is that, in joining together the predictions of multiple classifiers, it can correct for errors made by any individual classifier, leading to better accuracy overall. Let's take a look at the different ensemble classification methods and see how these classifiers can be implemented in Scikit-Learn.

What are Ensemble Models in Machine Learning?

alt
Credit: Pixabay

Ensemble models are an ensemble learning method that combines different algorithms together. In this sense, it is a meta-algorithm rather than an algorithm itself. Ensemble learning methods are valuable because they can improve the performance of a predictive model.

Ensemble learning methods work off of the idea that tying the predictions of multiple classifiers together will lead to better performance by either improving prediction accuracy or reducing aspects like bias and variance.

In general, an ensemble model falls into one of two categories: sequential approaches and parallel approaches.

A sequential ensemble model operates by having the base learners/models generated in sequence. Sequential ensemble methods are typically used to try and increase overall performance, as the ensemble model can compensate for inaccurate predictions by re-weighting the examples that were previously misclassified. A notable example of this is AdaBoost.

A parallel model is, as you may be able to guess, methods that rely on creating and training the base learners in parallel. Parallel methods aim to reduce the error rate by training many models in parallel and averaging the results together. A notable example of a parallel method is the Random Forest Classifier.

Another way of thinking about this is a distinction between homogenous and heterogeneous learners. While most of the ensemble learning methods use homogeneous base learners (many of the same type of learners), some ensemble methods use heterogeneous learners (different learning algorithms joined together).

To recap:

We'll now cover different methods of employing these models to solve machine learning classification problems.

Different Ensemble Classification Methods

Bagging

alt
Credit: Wikimedia Commons

Bagging, also known as bootstrap aggregating, is a classification method that aims to reduce the variance of estimates by averaging multiple estimates together. Bagging creates subsets from the main dataset that the learners are trained on.

In order for the predictions of the different classifiers to be aggregated, either an averaging is used for regression, or a voting approach is used for classification (based on the decision of the majority).

One example of a bagging classification method is the Random Forests Classifier. In the case of the random forests classifier, all the individual trees are trained on a different sample of the dataset.

The tree is also trained using random selections of features. When the results are averaged together, the overall variance decreases and the model performs better as a result.

Boosting

Boosting algorithms are capable of taking weak, underperforming models and converting them into strong models. The idea behind boosting algorithms is that you assign many weak learning models to the datasets, and then the weights for misclassified examples are tweaked during subsequent rounds of learning.

The predictions of the classifiers are aggregated and then the final predictions are made through a weighted sum (in the case of regressions), or a weighted majority vote (in the case of classification).

AdaBoost is one example of a boosting classifier method, as is Gradient Boosting, which was derived from the aforementioned algorithm.

If you'd like to read more about Gradient Boosting and the theory behind it, we've already covered that in a previous article.

Stacking

alt
Credit: Wikimedia Commons

Stacking algorithms are an ensemble learning method that combines the decision of different regression or classification algorithms. The component models are trained on the entire training dataset. After these component models are trained, a meta-model is assembled from the different models and then it's trained on the outputs of the component models. This approach typically creates a heterogeneous ensemble because the component models are usually different algorithms.

Example Implementations

Now that we've explored different methods we can use to create ensemble models, let's take a look at how we could implement a classifier using the different methods.

Though, before we can take a look at different ways of implementing ensemble classifiers, we need to select a dataset to use and do some preprocessing of the dataset.

We'll be using the Titanic dataset, which can be downloaded here. Let's do some preprocessing of the data in order to get rid of missing values and scale the data to a uniform range. Then we can go about setting up the ensemble classifiers.

Data Preprocessing

To begin with, we'll start by importing all functions we need from their respective libraries. We'll be using Pandas and Numpy to load and transform the data, as well as the LabelEncoder and StandardScaler tools.

We'll also need the machine learning metrics and the train_test_split function. Finally, we'll need the classifiers we want to use:

import pandas as pd
import numpy as np
import warnings

from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.metrics import accuracy_score, f1_score, log_loss
from sklearn.model_selection import train_test_split, KFold, cross_val_score

from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import VotingClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier, RandomForestClassifier, ExtraTreesClassifier

We'll start by loading in the training and testing data and then creating a function to check for the presence of any null values:

training_data = pd.read_csv("train.csv")
testing_data = pd.read_csv("test.csv")

def get_nulls(training, testing):
    print("Training Data:")
    print(pd.isnull(training).sum())
    print("Testing Data:")
    print(pd.isnull(testing).sum())

get_nulls(training_data, testing_data)

As it happens, there are a lot of missing values in the Age and Cabin categories.

Training Data:
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64
Testing Data:
PassengerId      0
Pclass           0
Name             0
Sex              0
Age             86
SibSp            0
Parch            0
Ticket           0
Fare             1
Cabin          327
Embarked         0
dtype: int64

We're going to start by dropping some of the columns that will likely be useless - the Cabin column and the Ticket column. The Cabin column has far too many missing values and the Ticket column is simply comprised of too many categories to be useful.

After that we will need to impute some missing values. When we do so, we must account for how the dataset is slightly right skewed (young ages are slightly more prominent than older ages). We'll use the median values when we impute the data because due to large outliers taking the average values would give us imputed values that are far from the center of the dataset:

# Drop the cabin column, as there are too many missing values
# Drop the ticket numbers too, as there are too many categories
# Drop names as they won't really help predict survivors

training_data.drop(labels=['Cabin', 'Ticket', 'Name'], axis=1, inplace=True)
testing_data.drop(labels=['Cabin', 'Ticket', 'Name'], axis=1, inplace=True)

# Taking the mean/average value would be impacted by the skew
# so we should use the median value to impute missing values

training_data["Age"].fillna(training_data["Age"].median(), inplace=True)
testing_data["Age"].fillna(testing_data["Age"].median(), inplace=True)
training_data["Embarked"].fillna("S", inplace=True)
testing_data["Fare"].fillna(testing_data["Fare"].median(), inplace=True)

get_nulls(training_data, testing_data)

Now we can see there's no more missing values:

Training Data:
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Fare           0
Embarked       0
dtype: int64
Testing Data:
PassengerId    0
Pclass         0
Name           0
Sex            0
Age            0
SibSp          0
Parch          0
Fare           0
Embarked       0
dtype: int64

We're now going to need to encode the non-numerical data. Let's set up a LabelEncoder and fit it on the Sex feature and then transform the data with the encoder. We'll then replace the values in the Sex feature with those that have been encoded and then do the same for the Embarked feature.

Finally, let's scale the data using the StandardScaler, so there aren't huge fluctuations in values.

encoder_1 = LabelEncoder()
# Fit the encoder on the data
encoder_1.fit(training_data["Sex"])

# Transform and replace training data
training_sex_encoded = encoder_1.transform(training_data["Sex"])
training_data["Sex"] = training_sex_encoded
test_sex_encoded = encoder_1.transform(testing_data["Sex"])
testing_data["Sex"] = test_sex_encoded

encoder_2 = LabelEncoder()
encoder_2.fit(training_data["Embarked"])

training_embarked_encoded = encoder_2.transform(training_data["Embarked"])
training_data["Embarked"] = training_embarked_encoded
testing_embarked_encoded = encoder_2.transform(testing_data["Embarked"])
testing_data["Embarked"] = testing_embarked_encoded

# Any value we want to reshape needs be turned into array first
ages_train = np.array(training_data["Age"]).reshape(-1, 1)
fares_train = np.array(training_data["Fare"]).reshape(-1, 1)
ages_test = np.array(testing_data["Age"]).reshape(-1, 1)
fares_test = np.array(testing_data["Fare"]).reshape(-1, 1)

# Scaler takes arrays
scaler = StandardScaler()

training_data["Age"] = scaler.fit_transform(ages_train)
training_data["Fare"] = scaler.fit_transform(fares_train)
testing_data["Age"] = scaler.fit_transform(ages_test)
testing_data["Fare"] = scaler.fit_transform(fares_test)

Now that our data has been preprocessed, we can select our features and labels and then use the train_test_split function to divide our entire training data up into training and testing sets:

# Now to select our training/testing data
X_features = training_data.drop(labels=['PassengerId', 'Survived'], axis=1)
y_labels = training_data['Survived']

print(X_features.head(5))

# Make the train/test data from validation

X_train, X_val, y_train, y_val = train_test_split(X_features, y_labels, test_size=0.1, random_state=27)

We're now ready to start implementing ensemble classification methods.

Simple Averaging Approach

Before we get into the big three ensemble methods we covered earlier, let's cover a very quick and easy method of using an ensemble approach - averaging predictions. We simply add the different predicted values of our chosen classifiers together and then divide by the total number of classifiers, using floor division to get a whole value.

In this test case we'll be using logistic regression, a Decision Tree Classifier, and the Support Vector Classifier. We fit the classifiers on the data and then save the predictions as variables. Then we simply add the predictions together and divide:

LogReg_clf = LogisticRegression()
DTree_clf = DecisionTreeClassifier()
SVC_clf = SVC()

LogReg_clf.fit(X_train, y_train)
DTree_clf.fit(X_train, y_train)
SVC_clf.fit(X_train, y_train)

LogReg_pred = LogReg_clf.predict(X_val)
DTree_pred = DTree_clf.predict(X_val)
SVC_pred = SVC_clf.predict(X_val)

averaged_preds = (LogReg_pred + DTree_pred + SVC_pred)//3
acc = accuracy_score(y_val, averaged_preds)
print(acc)

Here's the accuracy we got from this method:

0.8444444444444444

Voting\Stacking Classification Example

When it comes to creating a stacking/voting classifier, Scikit-Learn provides us with some handy functions that we can use to accomplish this.

The VotingClassifier takes in a list of different estimators as arguments and a voting method. The hard voting method uses the predicted labels and a majority rules system, while the soft voting method predicts a label based on the argmax/largest predicted value of the sum of the predicted probabilities.

After we provide the desired classifiers, we need to fit the resulting ensemble classifier object. We can then get predictions and use accuracy metrics:

voting_clf = VotingClassifier(estimators=[('SVC', SVC_clf), ('DTree', DTree_clf), ('LogReg', LogReg_clf)], voting='hard')
voting_clf.fit(X_train, y_train)
preds = voting_clf.predict(X_val)
acc = accuracy_score(y_val, preds)
l_loss = log_loss(y_val, preds)
f1 = f1_score(y_val, preds)

print("Accuracy is: " + str(acc))
print("Log Loss is: " + str(l_loss))
print("F1 Score is: " + str(f1))

Here's what the metrics have to say about the VotingClassifier's performance:

Accuracy is: 0.8888888888888888
Log Loss is: 3.8376684749044165
F1 Score is: 0.8484848484848486

Bagging Classification Example

Here's how we can implement bagging classification with Scikit-Learn. Sklearn's BaggingClassifier takes in a chosen classification model as well as the number of estimators that you want to use - you can use a model like Logistic Regression or Decision Trees.

Sklearn also provides access to the RandomForestClassifier and the ExtraTreesClassifier, which are modifications of the decision tree classification. These classifiers can also be used alongside the K-folds cross-validation tool.

We'll compare several different bagging classification approaches here, printing out the mean results of the K-fold cross validation score:

logreg_bagging_model = BaggingClassifier(base_estimator=LogReg_clf, n_estimators=50, random_state=12)
dtree_bagging_model = BaggingClassifier(base_estimator=DTree_clf, n_estimators=50, random_state=12)
random_forest = RandomForestClassifier(n_estimators=100, random_state=12)
extra_trees = ExtraTreesClassifier(n_estimators=100, random_state=12)

def bagging_ensemble(model):
    k_folds = KFold(n_splits=20, random_state=12)
    results = cross_val_score(model, X_train, y_train, cv=k_folds)
    print(results.mean())

bagging_ensemble(logreg_bagging_model)
bagging_ensemble(dtree_bagging_model)
bagging_ensemble(random_forest)
bagging_ensemble(extra_trees)

Here's the results we got from the classifiers:

0.7865853658536585
0.8102439024390244
0.8002439024390245
0.7902439024390244

Boosting Classification Example

Finally, we'll take a look at how to use a boosting classification method. As mentioned, there's a separate article on the topic of Gradient Boosting you can read here.

Scikit-Learn has a built-in AdaBoost classifier, which takes in a given number of estimators as the first argument. We can try using a for loop to see how the classification performance changes at different values, and we can also combine it with the K-Folds cross validation tool:

k_folds = KFold(n_splits=20, random_state=12)

num_estimators = [20, 40, 60, 80, 100]

for i in num_estimators:
    ada_boost = AdaBoostClassifier(n_estimators=i, random_state=12)
    results = cross_val_score(ada_boost, X_train, y_train, cv=k_folds)
    print("Results for {} estimators:".format(i))
    print(results.mean())

Here's the results we got:

Results for 20 estimators:
0.8015243902439024
Results for 40 estimators:
0.8052743902439025
Results for 60 estimators:
0.8053048780487805
Results for 80 estimators:
0.8040243902439024
Results for 100 estimators:
0.8027743902439024

Summing Up

We've covered the ideas behind three different ensemble classification techniques: voting\stacking, bagging, and boosting.

Scikit-Learn allows you to easily create instances of the different ensemble classifiers. These ensemble objects can be combined with other Scikit-Learn tools like K-Folds cross validation.

If you'd like to learn more about appropriate uses for ensemble classifiers, and the theories behind them, I suggest checking out the links found here or here.

22 Jan 2020 1:36pm GMT

PyCharm: PyCharm 2019.3.2

We've been taking some time to polish PyCharm further, so be sure to update to the newest version! You can get it from within PyCharm (Help | Check for Updates), using JetBrains Toolbox, or by downloading the new version from our website.

Improved in PyCharm

And many more small fixes, see our release notes for details.

Getting the New Version

You can update PyCharm by choosing Help | Check for Updates (or PyCharm | Check for Updates on macOS) in the IDE. PyCharm will be able to patch itself to the new version, there should no longer be a need to run the full installer.

If you're on Ubuntu 16.04 or later, or any other Linux distribution that supports snap, you should not need to upgrade manually, you'll automatically receive the new version.

22 Jan 2020 12:42pm GMT

PyCharm: PyCharm 2019.3.2

We've been taking some time to polish PyCharm further, so be sure to update to the newest version! You can get it from within PyCharm (Help | Check for Updates), using JetBrains Toolbox, or by downloading the new version from our website.

Improved in PyCharm

And many more small fixes, see our release notes for details.

Getting the New Version

You can update PyCharm by choosing Help | Check for Updates (or PyCharm | Check for Updates on macOS) in the IDE. PyCharm will be able to patch itself to the new version, there should no longer be a need to run the full installer.

If you're on Ubuntu 16.04 or later, or any other Linux distribution that supports snap, you should not need to upgrade manually, you'll automatically receive the new version.

22 Jan 2020 12:42pm GMT

EuroPython: EuroPython 2020: Pre-launch Website Ready

In the last couple of weeks we have put together a pre-launch site for EuroPython 2020, which has all the information around the event, as we currently know and can share with you.

image

https://ep2020.europython.eu/

The main website will go online around early in March and we plan to also open the CFP and ticket sales around that time. It will use the same URL, so you can keep this bookmarked.

Some additional updates:

Enjoy,
-
EuroPython 2020 Team
https://ep2020.europython.eu/

22 Jan 2020 10:59am GMT

EuroPython: EuroPython 2020: Pre-launch Website Ready

In the last couple of weeks we have put together a pre-launch site for EuroPython 2020, which has all the information around the event, as we currently know and can share with you.

image

https://ep2020.europython.eu/

The main website will go online around early in March and we plan to also open the CFP and ticket sales around that time. It will use the same URL, so you can keep this bookmarked.

Some additional updates:

Enjoy,
-
EuroPython 2020 Team
https://ep2020.europython.eu/

22 Jan 2020 10:59am GMT

10 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: King Willams Town Bahnhof

Gestern musste ich morgens zur Station nach KWT um unsere Rerservierten Bustickets für die Weihnachtsferien in Capetown abzuholen. Der Bahnhof selber ist seit Dezember aus kostengründen ohne Zugverbindung - aber Translux und co - die langdistanzbusse haben dort ihre Büros.


Größere Kartenansicht




© benste CC NC SA

10 Nov 2011 10:57am GMT

09 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein

Niemand ist besorgt um so was - mit dem Auto fährt man einfach durch, und in der City - nahe Gnobie- "ne das ist erst gefährlich wenn die Feuerwehr da ist" - 30min später auf dem Rückweg war die Feuerwehr da.




© benste CC NC SA

09 Nov 2011 8:25pm GMT

08 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Brai Party

Brai = Grillabend o.ä.

Die möchte gern Techniker beim Flicken ihrer SpeakOn / Klinke Stecker Verzweigungen...

Die Damen "Mamas" der Siedlung bei der offiziellen Eröffnungsrede

Auch wenn weniger Leute da waren als erwartet, Laute Musik und viele Leute ...

Und natürlich ein Feuer mit echtem Holz zum Grillen.

© benste CC NC SA

08 Nov 2011 2:30pm GMT

07 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Lumanyano Primary

One of our missions was bringing Katja's Linux Server back to her room. While doing that we saw her new decoration.

Björn, Simphiwe carried the PC to Katja's school


© benste CC NC SA

07 Nov 2011 2:00pm GMT

06 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Nelisa Haircut

Today I went with Björn to Needs Camp to Visit Katja's guest family for a special Party. First of all we visited some friends of Nelisa - yeah the one I'm working with in Quigney - Katja's guest fathers sister - who did her a haircut.

African Women usually get their hair done by arranging extensions and not like Europeans just cutting some hair.

In between she looked like this...

And then she was done - looks amazing considering the amount of hair she had last week - doesn't it ?

© benste CC NC SA

06 Nov 2011 7:45pm GMT

05 Nov 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Mein Samstag

Irgendwie viel mir heute auf das ich meine Blogposts mal ein bischen umstrukturieren muss - wenn ich immer nur von neuen Plätzen berichte, dann müsste ich ja eine Rundreise machen. Hier also mal ein paar Sachen aus meinem heutigen Alltag.

Erst einmal vorweg, Samstag zählt zumindest für uns Voluntäre zu den freien Tagen.

Dieses Wochenende sind nur Rommel und ich auf der Farm - Katja und Björn sind ja mittlerweile in ihren Einsatzstellen, und meine Mitbewohner Kyle und Jonathan sind zu Hause in Grahamstown - sowie auch Sipho der in Dimbaza wohnt.
Robin, die Frau von Rommel ist in Woodie Cape - schon seit Donnerstag um da ein paar Sachen zur erledigen.
Naja wie dem auch sei heute morgen haben wir uns erstmal ein gemeinsames Weetbix/Müsli Frühstück gegönnt und haben uns dann auf den Weg nach East London gemacht. 2 Sachen waren auf der Checkliste Vodacom, Ethienne (Imobilienmakler) außerdem auf dem Rückweg die fehlenden Dinge nach NeedsCamp bringen.

Nachdem wir gerade auf der Dirtroad losgefahren sind mussten wir feststellen das wir die Sachen für Needscamp und Ethienne nicht eingepackt hatten aber die Pumpe für die Wasserversorgung im Auto hatten.

Also sind wir in EastLondon ersteinmal nach Farmerama - nein nicht das onlinespiel farmville - sondern einen Laden mit ganz vielen Sachen für eine Farm - in Berea einem nördlichen Stadteil gefahren.

In Farmerama haben wir uns dann beraten lassen für einen Schnellverschluss der uns das leben mit der Pumpe leichter machen soll und außerdem eine leichtere Pumpe zur Reperatur gebracht, damit es nicht immer so ein großer Aufwand ist, wenn mal wieder das Wasser ausgegangen ist.

Fego Caffé ist in der Hemmingways Mall, dort mussten wir und PIN und PUK einer unserer Datensimcards geben lassen, da bei der PIN Abfrage leider ein zahlendreher unterlaufen ist. Naja auf jeden Fall speichern die Shops in Südafrika so sensible Daten wie eine PUK - die im Prinzip zugang zu einem gesperrten Phone verschafft.

Im Cafe hat Rommel dann ein paar online Transaktionen mit dem 3G Modem durchgeführt, welches ja jetzt wieder funktionierte - und übrigens mittlerweile in Ubuntu meinem Linuxsystem perfekt klappt.

Nebenbei bin ich nach 8ta gegangen um dort etwas über deren neue Deals zu erfahren, da wir in einigen von Hilltops Centern Internet anbieten wollen. Das Bild zeigt die Abdeckung UMTS in NeedsCamp Katjas Ort. 8ta ist ein neuer Telefonanbieter von Telkom, nachdem Vodafone sich Telkoms anteile an Vodacom gekauft hat müssen die komplett neu aufbauen.
Wir haben uns dazu entschieden mal eine kostenlose Prepaidkarte zu testen zu organisieren, denn wer weis wie genau die Karte oben ist ... Bevor man einen noch so billigen Deal für 24 Monate signed sollte man wissen obs geht.

Danach gings nach Checkers in Vincent, gesucht wurden zwei Hotplates für WoodyCape - R 129.00 eine - also ca. 12€ für eine zweigeteilte Kochplatte.
Wie man sieht im Hintergrund gibts schon Weihnachtsdeko - Anfang November und das in Südafrika bei sonnig warmen min- 25°C

Mittagessen haben wir uns bei einem Pakistanischen Curry Imbiss gegönnt - sehr empfehlenswert !
Naja und nachdem wir dann vor ner Stunde oder so zurück gekommen sind habe ich noch den Kühlschrank geputzt den ich heute morgen zum defrosten einfach nach draußen gestellt hatte. Jetzt ist der auch mal wieder sauber und ohne 3m dicke Eisschicht...

Morgen ... ja darüber werde ich gesondert berichten ... aber vermutlich erst am Montag, denn dann bin ich nochmal wieder in Quigney(East London) und habe kostenloses Internet.

© benste CC NC SA

05 Nov 2011 4:33pm GMT

31 Oct 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Sterkspruit Computer Center

Sterkspruit is one of Hilltops Computer Centres in the far north of Eastern Cape. On the trip to J'burg we've used the opportunity to take a look at the centre.

Pupils in the big classroom


The Trainer


School in Countryside


Adult Class in the Afternoon


"Town"


© benste CC NC SA

31 Oct 2011 4:58pm GMT

Benedict Stein: Technical Issues

What are you doing in an internet cafe if your ADSL and Faxline has been discontinued before months end. Well my idea was sitting outside and eating some ice cream.
At least it's sunny and not as rainy as on the weekend.


© benste CC NC SA

31 Oct 2011 3:11pm GMT

30 Oct 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Nellis Restaurant

For those who are traveling through Zastron - there is a very nice Restaurant which is serving delicious food at reasanable prices.
In addition they're selling home made juices jams and honey.




interior


home made specialities - the shop in the shop


the Bar


© benste CC NC SA

30 Oct 2011 4:47pm GMT

29 Oct 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: The way back from J'burg

Having the 10 - 12h trip from J'burg back to ELS I was able to take a lot of pcitures including these different roadsides

Plain Street


Orange River in its beginngings (near Lesotho)


Zastron Anglican Church


The Bridge in Between "Free State" and Eastern Cape next to Zastron


my new Background ;)


If you listen to GoogleMaps you'll end up traveling 50km of gravel road - as it was just renewed we didn't have that many problems and saved 1h compared to going the official way with all it's constructions sites




Freeway


getting dark


© benste CC NC SA

29 Oct 2011 4:23pm GMT

28 Oct 2011

feedPython Software Foundation | GSoC'11 Students

Benedict Stein: Wie funktioniert eigentlich eine Baustelle ?

Klar einiges mag anders sein, vieles aber gleich - aber ein in Deutschland täglich übliches Bild einer Straßenbaustelle - wie läuft das eigentlich in Südafrika ?

Ersteinmal vorweg - NEIN keine Ureinwohner die mit den Händen graben - auch wenn hier mehr Manpower genutzt wird - sind sie fleißig mit Technologie am arbeiten.

Eine ganz normale "Bundesstraße"


und wie sie erweitert wird


gaaaanz viele LKWs


denn hier wird eine Seite über einen langen Abschnitt komplett gesperrt, so das eine Ampelschaltung mit hier 45 Minuten Wartezeit entsteht


Aber wenigstens scheinen die ihren Spaß zu haben ;) - Wie auch wir denn gücklicher Weise mussten wir nie länger als 10 min. warten.

© benste CC NC SA

28 Oct 2011 4:20pm GMT