# Creating classes and tkinter GUI

**URL:** <https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621>\
**Category:** Python Help\
**Created:** [November 26, 2023, 12:22pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621 "2023-11-26T12:22:46Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![Thisisausername](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/thisisausername/32/11219_2.png) [@Thisisausername](https://discuss.python.org/u/Thisisausername)\
**Post date:** [November 26, 2023, 12:22pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/1 "2023-11-26T12:22:46Z")

</div>

I made a

```auto
class Persona:
      def __init__ (self,
                   cognome,
                   nome,
                   nascita):
          self.cognome = cognome
          self.nome = nome
          self.nascita = nascita
   
          a_persona = Persona.get()

```

and through a dedicated form window made with some Entry widgets, I want that infos that users write on it will be saved in a .csv file.  
In a first (well working) attempt, I used to write a " .get() " method for each element in the class (cognome.get(), and so on…) without creating any class for “Persona”.  
Now, I’m trying this way just because I guess that modifying single attributes if needed would be simple with a class (and a little more well written code for readability), but IDE returns this:

`NameError: name 'a_persona' is not defined`

So, I tried a different indentation and another error raises:

`NameError: cannot access free variable 'Persona' where it is not associated with a value in enclosing scope`

Is there a particular method for record data with classes using tkinter?

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [November 30, 2023, 6:06pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/2 "2023-11-30T18:06:04Z")

</div>

Hi,

as the error states, ‘Persona’ class has no attributes ‘.get()’. If you want to get the information for the class attributes, you need to get familiar with ‘getters’ and ‘setters’. This is the proper way of obtaining class attribute information outside the class.

Otherwise, you can also try the following if you want an in-class variable with the class attribute information:

```auto
class Persona:

      a_persona = tuple()

      def __init__ (self,
                   cognome,
                   nome,
                   nascita):
          self.cognome = cognome
          self.nome = nome
          self.nascita = nascita
   
          self.a_persona = self.cognome, self.nome, self.nascita
          print(self.a_persona) # print results for test verification purposes

test = Persona('john', 'larry', 'rick') # Instantiate class
print(test.a_persona[1])
print(test.a_persona[2])
print(test.a_persona[3])

```

Here, I have just created a tuple of the required information.

Or, you can also test it with a list, as in:

```auto
class Persona:

      a_persona = []
    
      def __init__ (self,
                   cognome,
                   nome,
                   nascita):
          self.cognome = cognome
          self.nome = nome
          self.nascita = nascita
   
          self.a_persona.append(self.cognome)
          self.a_persona.append(self.nome)
          self.a_persona.append(self.nascita)
          
          print(self.a_persona)

test = Persona('john', 'larry', 'rick')
print(test.a_persona[0])
print(test.a_persona[1])
print(test.a_persona[2])

```

Here is a good video on composition that touches on  
getters and setters. [https://www.youtube.com/watch?v=b4vxi3AXw54](https://www.youtube.com/watch?v=b4vxi3AXw54)

Although not directly related, I am posting it here because the video touches on the subject of ‘getters’ and ‘setters’ of which you should familiarize yourself with. As I stated above, this is one of the preferred ways of getting attribute information from objects (class instances).

There is also a way of using **decorators** by which you can get object attribute information without having to use the class methods with parentheses.

Here is the video: [https://www.youtube.com/watch?v=jCzT9XFZ5bw](https://www.youtube.com/watch?v=jCzT9XFZ5bw)

---

<div class="post-metadata">

**Author:** ![Thisisausername](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/thisisausername/32/11219_2.png) [@Thisisausername](https://discuss.python.org/u/Thisisausername)\
**Post date:** [December 6, 2023, 10:44am UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/3 "2023-12-06T10:44:20Z")

</div>

Thanks a lot Paul! I will check as soon as possible.

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 6, 2023, 5:44pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/4 "2023-12-06T17:44:42Z")

</div>

Hi,

what you are asking: “ **I want that infos that users write on it will be saved in a .csv file**.”  
… is not trivial. I am a rather newbie to the Python language and Tkinter myself. One of the books that I am using to learn Python and Tkinter in particular is: **Tkinter GUI Application Development Cookbook**. There is a dedicated GitHub for this book that includes code examples. [GitHub - PacktPublishing/Tkinter-GUI-Application-Development-Cookbook: Tkinter GUI Application Development Cookbook, published by Packt](https://github.com/PacktPublishing/Tkinter-GUI-Application-Development-Cookbook).

Once you go to that location (open location ‘ **Chapter05** ’), download files **chapter5\_01** , **chapter5\_02** , **chapter5\_03**. Make sure that you have saved all three files to the same folder. Open module **chapter5\_03** and run it. As you’ll see, it has similar properties to your desired application.

1. First Name
2. Last Name
3. Entry widgets
4. Saving info to .csv file

This is not for copy and pasting for your application, however. Use it as a reference upon which you can build your own unique application. If you’re new to Python and Tkinter, I would recommend reading this book to follow along. Like I stated before, it is not trivial and there are a lot of subtle concepts and nuances that you have to understand to fully appreciate the example.

One last point. Above, you wanted to execute the following line of code:

```auto
a_persona = Persona.get() # I am assuming you inadvertently had the wrong indentation
                           # and that you had instantiated it somewhere else not shown

```

Since ‘ **Persona** ’ is a class you defined, there is no auto ‘**get()**’ method associated with it or generated for it. All methods have to be explicitly defined by you. In your code there was no

```auto
def get(self):
    # other code here if need be
    return yourVariableHere

```

This is why you were getting an error (there was no **get()** method defined in your class).

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* UPDATE \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*

I overlooked one mistake in the code above and shown explicitly here for reference.

```auto
a_Persona = Persona.get() # Here, you would not use the name of the class 'Persona' as the name of the object created

```

So, assuming you instantiated the object like this in the IDLE prompt (interpreter/shell)  
(in actual code you would use the class attributes by way of the entry widgets):

```auto
user1 = Persona('Domenico', 'Morretti', '08/23/1998')
print(user1.get()) # You may now get access to the 'get()' method via the object created

```

The point that I am making here is that you have to get the class methods with the object name and not the name of the class itself as proposed in the original code.

---

<div class="post-metadata">

**Author:** ![CAM-Gerlach](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cam-gerlach/32/3688_2.png) [@CAM-Gerlach](https://discuss.python.org/u/CAM-Gerlach)\
**Post date:** [December 6, 2023, 7:46pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/5 "2023-12-06T19:46:56Z")

</div>

> [@onePythonUser](#):
>
> There is also a way of using **decorators** by which you can get object attribute information without having to use the class methods with parentheses.

Indeed: there is the [`property` builtin class](https://docs.python.org/3/library/functions.html#property) that is most commonly [used as a decorator](https://docs.python.org/3/library/functions.html#property.getter), and is in fact the greatly preferred, Pythonic way to control getting and setting data on classes instead of Java-style getters and setters, because they allow users of your class to access (and set, if you choose) properties just like normal attributes, and you to make attributes computed/specially handled, or read-only, without users having to change any of their code.

Of course, you only need this if your attribute has to be computed or handled in some way (e.g., computing an attribute `nome_e_cognome` from the given and surname, or vice versa). If you’re just setting the given name, surname and birth date without doing anything to them, you don’t need a getter at all—just access the attribute directly, `self.nome`, etc inside the class, or `persona_instance.nome` outside it, as @onePythonUser demonstrates.

However, there’s an even better way to handle this: if the `Persona` class exists primary to store data about a person, as opposed to take specific actions, then you’re likely much better off using a [Python `dataclass`](https://docs.python.org/3/library/dataclasses.html) instead, as it simplifies your code while doing a bunch of useful things for you and your users. So instead of needing all of, e.g.:

```python
class Persona:
      def __init__ (self,
                   cognome,
                   nome,
                   nascita):
          self.cognome = cognome
          self.nome = nome
          self.nascita = nascita

    def __eq__ (self, other):
        return (self.cognome == other.cognome
                and self.nome == other.nome
                and self.nascita == other.nascita)

    def __repr__ (self):
        return (f"Persona(cognome={self.cognome!r}, "
                f"nome={self.nome!r}, "
                f"nascita={self.nascita!r})")

```

to have a basic `Persona` class that you can construct, compare for equality and print, you can instead just do

```python
import dataclasses

@dataclasses.dataclass
class Persona:
    cognome: str
    nome: str
    nascita: datetime

```

and everything above and more will be automatically generated for you.

Furthermore, since your stated goal here is to allow converting your class to CSV, a dataclass makes that easy without having to list all your attributes manually (which is easy to make mistakes on or forget to include something), since you can get them as a Python dictionary or a tuple using [`dataclasses.asdict()`](https://docs.python.org/3/library/dataclasses.html#dataclasses.asdict) and [`dataclasses.astuple()`](https://docs.python.org/3/library/dataclasses.html#dataclasses.astuple) that you can easily loop over or feed to the Python standard library [`csv` module](https://docs.python.org/3.12/library/csv.html). I’d recommend the latter as the more correct solution, but you could probably get away with something as simple as:

```python
@dataclasses.dataclass
class Persona:
    ...
    def as_csv(self):
        return ",".join(str(a) for a in dataclasses.astuple(self))

```

Best of luck!

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 7, 2023, 3:51am UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/6 "2023-12-07T03:51:50Z")

</div>

Hello,

I created this code with a labelframe widget where the user can enter the required information.  
I am not familiar with manipulating files (I am just delving into the subject tbh) so hopefully you can  
get help with this subject. Currently, the way that I have it set up, the information entered rewrites  
the previously entered information.

```auto
import tkinter as tk
import csv

class App(tk.Tk):
        
    def __init__ (self):
        
        super(). __init__ ()

        # Create main window
        self.title("Terminale Utente")
        tk.Tk.geometry(self, '260x160')
        
        # Create labelFrame and title
        group_1 = tk.LabelFrame(self, padx = 15, pady = 10,
                                text = "Informazione Personale")
        group_1.pack(padx = 10, pady = 5)

        # Create entry labels
        tk.Label(group_1, text = "Nome").grid(row = 0)
        tk.Label(group_1, text = "Cognome").grid(row = 1)
        tk.Label(group_1, text = "Nascita").grid(row = 2)

        # Create entries and class attributes
        self.nome = tk.Entry(group_1)
        self.nome.grid(row = 0, column = 1, sticky = tk.W)
        
        self.cognome = tk.Entry(group_1)
        self.cognome.grid(row = 1, column = 1, sticky = tk.W)

        self.nascita = tk.Entry(group_1)
        self.nascita.grid(row = 2, column = 1, sticky = tk.W)

        # Create button to save information to csv file
        self.btn_submit = tk.Button(self, text = "Salva Informazioni", command = self.saveToCSV)
        self.btn_submit.pack(padx = 10, pady = 10)

    def saveToCSV(self):

        infoFields = ['Nome', 'Cognome', 'Nascita']
        userInfo = [self.nome.get(), self.cognome.get(), self.nascita.get()]
        
        print(self.nome.get(), self.cognome.get(), self.nascita.get()) # Print for test purposes only
        
        with open('user_information.csv', 'w') as csvfile:
            
            csvwriter = csv.writer(csvfile)  
                
            # writing the fields  
            csvwriter.writerow(infoFields)  
                
            # writing the data rows  
            csvwriter.writerow(userInfo) 

if __name__ == " __main__":
    
    app = App()
    app.mainloop()

```

---

<div class="post-metadata">

**Author:** ![CAM-Gerlach](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cam-gerlach/32/3688_2.png) [@CAM-Gerlach](https://discuss.python.org/u/CAM-Gerlach)\
**Post date:** [December 7, 2023, 5:07am UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/7 "2023-12-07T05:07:53Z")

</div>

Not a bad start overall (though I can’t really comment much on the Tkinter aspects, as I’m a Qt user and have never toyed around with Tk myself). I do have a few comments.

Most prominently, you’ll notice that there’s a lot of duplication listing the field names, no fewer than six different places—this is a so-called “code smell”, and usually a sign that you should refactor your code. This makes it harder to read and to add a new field, and a lot easier to make a mistake and introduce a bug while doing so.

To avoid this, I suggest storing the field names in a list as a module or class-level constant, say `FIELD_NAMES`, and then iterating that as needed to create your entry boxes and extract the data from them. So, assuming we have

```python
FIELD_NAMES = ['Nome', 'Cognome', 'Nascita']

```

we can replace

```python
        # Create entry labels
        tk.Label(group_1, text = "Nome").grid(row = 0)
        tk.Label(group_1, text = "Cognome").grid(row = 1)
        tk.Label(group_1, text = "Nascita").grid(row = 2)

        # Create entries and class attributes
        self.nome = tk.Entry(group_1)
        self.nome.grid(row = 0, column = 1, sticky = tk.W)
        
        self.cognome = tk.Entry(group_1)
        self.cognome.grid(row = 1, column = 1, sticky = tk.W)

        self.nascita = tk.Entry(group_1)
        self.nascita.grid(row = 2, column = 1, sticky = tk.W)

```

with

```python
        self.fields = {}
        for row, field_name in enumerate(FIELD_NAMES):
            tk.Label(group_1, text=field_name).grid(row=row)
            entry = tk.Entry(group_1)
            entry.grid(row=row, column=1, sticky=tk.W)
            self.fields[field_name] = entry

```

Now, saving to CSV becomes simpler too, and instead of

```python
    def saveToCSV(self):

        infoFields = ['Nome', 'Cognome', 'Nascita']
        userInfo = [self.nome.get(), self.cognome.get(), self.nascita.get()]
        
        print(self.nome.get(), self.cognome.get(), self.nascita.get()) # Print for test purposes only
        
        with open('user_information.csv', 'w') as csvfile:
            
            csvwriter = csv.writer(csvfile)  
                
            # writing the fields  
            csvwriter.writerow(infoFields)  
                
            # writing the data rows  
            csvwriter.writerow(userInfo) 

```

we can do

```python
    def saveToCSV(self):
        user_info = [entry.get() for entry in self.fields.values()]
        print(*user_info) # Print for test purposes only
        with open('user_information.csv', 'w') as csvfile:
            csvwriter = csv.writer(csvfile)  
            csvwriter.writerow(FIELD_NAMES)  
            csvwriter.writerow(user_info) 

```

or, using [a `csv.DictWriter`](https://docs.python.org/3/library/csv.html#csv.DictWriter), a bit more elegantly:

```python
    def saveToCSV(self):
        user_info = {field: entry.get() for field, entry in self.fields.items()}
        print(user_info) # Print for test purposes only
        with open('user_information.csv', 'w') as csvfile:
            csvwriter = csv.DictWriter(csvfile, FIELD_NAMES)
            csvwriter.writeheader()
            csvwriter.writerow(user_info) 

```

Now, our code is much more concise, and adding a new field is as simple as adding its name to the `FIELD_NAMES` list.

> [@onePythonUser](#):
>
> I am not familiar with manipulating files (I am just delving into the subject tbh) so hopefully you can  
> get help with this subject.

Your file handling generally looks fine, with the exception that you should always specify the encoding (almost always `UTF-8` unless you have a good reason not to) when reading and writing text files, i.e. `open(..., encoding="UTF-8")`. Otherwise, it will fall back to the system default encoding for legacy reasons, which is almost never what you want over explicitly specifying an encoding.

> [@onePythonUser](#):
>
> Currently, the way that I have it set up, the information entered rewrites  
> the previously entered information.

Its actually not too difficult to adapt what you have to add a new row to the CSV each time the user clicks the Save button. From an IO perspective, the only change needed is changing the `w` mode to `a` to append to the file if it already exists instead of overwriting it, plus moving the step of writing the headers outside `saveToCSV` (to e.g. a separate helper function called by ` __init__ `) so it only gets called once, and either having that helper open the file as `w` to overwrite the version from the last run, or check that it exists and skip writing the headers if it already does, to combine data from multiple runs.

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 7, 2023, 2:52pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/8 "2023-12-07T14:52:17Z")

</div>

> [@CAM-Gerlach](#):
>
> So, assuming we have
> 
> ```auto
> FIELD_NAMES = ['Nome', 'Cognome', 'Nascita']
> 
> ```

Hello,

thank you for the insight. Yes, I implemented this way, and it is more concise. By the way, in order for the code to work, we have to include a ‘ **self.** ’ in front of the list name wherever it is being used.

As in the two lines of code:

```auto
for _row, field_name in enumerate(self.FIELD_NAMES):

csvwriter.writerow(self.FIELD_NAMES) 

```

I will add code such that the subject headers are written once during the **init** method so that only subsequent additions of names are appended thereafter.

Thank you for the insight.  
Greatly appreciated!

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 7, 2023, 6:15pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/9 "2023-12-07T18:15:32Z")

</div>

> [@Thisisausername](#):
>
> and through a dedicated form window made with some Entry widgets, I want that infos that users write on it will be saved in a .csv file.

Hi,

with the help from @CAM-Gerlach, we came up with the following solution. You will have to define  
your own file directory for the variable **csvFilePath** , however.

```auto
import tkinter as tk
import csv
import os.path

class App(tk.Tk):

    FIELD_NAMES = ['Nome', 'Cognome', 'Nascita']
    csvFilePath = '/folder1/folder2/folder3/folder_n/user_information.csv'
        
    def __init__ (self):
        
        super(). __init__ ()

        # Create main window
        self.title("Terminale Utente")
        tk.Tk.geometry(self, '260x160')

        # Create labelframe and title
        group_1 = tk.LabelFrame(self, padx = 15, pady = 10,
                                text = "Informazione Personale")
        group_1.pack(padx = 10, pady = 5)

        self.fields = {}
        
        for _row, field_name in enumerate(self.FIELD_NAMES):
            
            tk.Label(group_1, text=field_name).grid(row = _row)
            entry = tk.Entry(group_1)
            entry.grid(row =_row, column = 1, sticky = tk.W)
            self.fields[field_name] = entry            

        # Create button 
        self.btn_submit = tk.Button(self, text = "Salva Informazioni", command = self.saveToCSV)
        self.btn_submit.bind("<Return>", self.saveToCSV) # Add carriage Return functionality to button
        self.btn_submit.pack(padx = 10, pady = 10)

        self.fields[self.FIELD_NAMES[0]].focus_set() # Put prompt inside widget entry by default
        self.createFile() # Create file if one not already created

    def createFile(self):

        # If file does not exist, create file and write subject headers
        if not os.path.exists(self.csvFilePath):
            
            with open('user_information.csv', 'w', encoding = 'utf-8') as csvfile:
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow(self.FIELD_NAMES)   
                csvfile.close()     

    def saveToCSV(self, *args):
        
        user_info = [entry.get() for entry in self.fields.values()]

        # Write user info to file
        with open('user_information.csv', 'a', encoding = 'utf-8') as csvfile:
            csvwriter = csv.writer(csvfile)   
            csvwriter.writerow(user_info)

        # Clear entries 
        for index in enumerate(self.fields):            
            self.fields[self.FIELD_NAMES[index[0]]].delete(0, 'end')

        self.fields[self.FIELD_NAMES[0]].focus_set() # Reset prompt focus to first entry
        

if __name__ == " __main__":
    
    app = App()
    app.mainloop()

```

From here, you should have enough to add additional bells and whistles to your liking, i.e., button to remove outdated users, more entry cells, etc. For example, from the Cookbook files from the previous post, you can force the user to enter their ‘nascita’ according to a format by way of the **re** module for regular expressions.

I hope this helps.

---

<div class="post-metadata">

**Author:** ![CAM-Gerlach](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cam-gerlach/32/3688_2.png) [@CAM-Gerlach](https://discuss.python.org/u/CAM-Gerlach)\
**Post date:** [December 7, 2023, 9:45pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/10 "2023-12-07T21:45:58Z")

</div>

> [@onePythonUser](#):
>
> By the way, in order for the code to work, we have to include a ‘ **self.** ’ in front of the list name wherever it is being used.

To note, that’s only necessary if you make it a class attribute rather than a module-level global constant, as I suggested in the sentence before the one quoted (and my code assumed). But that’s okay too, it just adds slightly more complexity.

> [@onePythonUser](#):
>
> You will have to define  
> your own file directory for the variable **csvFilePath** , however.

No need; instead of hardcoding an arbitrary absolute path, you can simply output it to the current working directory, just like the original code did, store that (default) path as a constant, and actually use it when saving the file—currently, the code doesn’t actually work anyway, since as `self.csvFilePath` is checked but not actually used to save the file.

> [@onePythonUser](#):
>
> ```python
> csvFilePath = '/folder1/folder2/folder3/folder_n/user_information.csv'
> 
> ```

As mentioned, this should just be the relative path to the file, and should be actually used when writing the file. Additionally, as a constant it should be named in `UPPER_CASE`, not camelCase (which, BTW, shouldn’t be used in standard Python style; `snake_case` should be used instead as it is more idiomatic and easier to read). Finally, I suggest making this a `pathlib.Path`, as it makes the existence check and other things simpler and more elegant. So replace `import os.path` with the recommended `from pathlib import Path` up top, and then this should be just

```python
    CSV_FILE_PATH = Path('user_information.csv')

```

> [@onePythonUser](#):
>
> ```python
> if not os.path.exists(self.csvFilePath):
> 
> ```

With the change above, instead of using the legacy `os.path` for this check, you can just use the `.exists()` method of `Path` objects. Additionally, since you’re in a function, its much cleaner and more idiomatic to check if the path _does_ exists and return immediately, rather than indent everything else under this check. So you’d have:

```python
        if self.CSV_FILE_PATH.exists():
            return

        with open(...):
            ...

```

> [@onePythonUser](#):
>
> ```python
> with open('user_information.csv', 'w', encoding = 'utf-8') as csvfile:
> 
> ```

Here you should use the actual path you’ve specified above rather than hardcoding another, so this should be:

```python
            with open(self.CSV_FILE_PATH, 'w', encoding='UTF-8') as csvfile:

```

> [@onePythonUser](#):
>
> ```python
> csvfile.close()    
> 
> ```

No need to manually close the file as that’s what using `open()` as a context manager (i.e. inside a `with` block) does for you, so this line can simply be removed

> [@onePythonUser](#):
>
> ```python
> def saveToCSV(self, *args):
> 
> ```

You take arbitrary variable-length positional arguments here (`*args`) but don’t actually need or use them, so that should simply be removed (also, don’t forget `snake_case` not `camelCase` for names, unless you have a very strong reason otherwise).

> [@onePythonUser](#):
>
> ```python
> for index in enumerate(self.fields):            
> self.fields[self.FIELD_NAMES[index[0]]].delete(0, 'end')
> 
> ```

Yup, its a great idea to clear the fields after saving—I was going to suggest that myself, but didn’t want to overcomplicate my post further. However, you can greatly simplify what you’re doing here—instead of looping over the `fields` dict, getting the current index, indexing into that (appears to be a bug), using that to index into field names, using the field names to index into the `fields`, and finally performing an action on each one, simply loop over the field entries directly (exactly like I do above when creating them):

```python
        for entry in self.fields.values():            
            entry.delete(0, 'end')

```

> [@onePythonUser](#):
>
> ```python
> def createFile(self):
> 
> ```

Good idea putting this into a helper function, though I’d suggest a bit of refactoring to avoid the mostly duplicated three lines of code opening the CSV, creating the CSV writer and then write a row of headers/data. I’d make this function just do those three things (passing in the data to write), pulling the file existence check outside the function, and then you can use it both when creating the file and when saving data to it.

So, this function should look something like:

```python
    def write_csv_row(self, row_data):
        with open(self.CSV_FILE_PATH, 'a', encoding='UTF-8') as csv_file:
            csv_writer = csv.writer(csv_file)
            csv_writer.writerow(row_data)   

```

Then, you use it in ` __init__ ()` like this:

```python
        if not self.CSV_FILE_PATH.exists():
            self.write_csv_row(self.FIELD_NAMES)

```

And in `save_to_csv()` like this:

```python
        self.write_csv_row(user_info)

```

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 8, 2023, 5:28am UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/11 "2023-12-08T05:28:23Z")

</div>

Hello,

thank you for the feedback.

As you know, in **IDLE** , when you run a **.py** module, the **IDLE** working directory will become the directory wherever that .py module resides. This just so happens to be where I wanted my module to reside. So, by chance, my code was ‘working’ (creating the module in the ‘correct’ folder), however incorrectly - as I have heard it said: The ends don’t justify the means. I made the changes (corrections) to the code as per your suggestion. Now the code is correct and the directory explicit.

By the way, the reason why I provided the user the option to enter their preferred directory versus the current working directory was just in case if the user had a particular folder where they wanted the .csv file to reside. Maybe if they wanted that list to reside in the same location as other user data where it could be easily accessed (all user information files in the same directory, for example).

Regarding the (\*args) addition, there was another test code that I was working on. As soon as I added the carriage Return functionality, it would not work. It wasn’t until I added the ‘\*args’ argument that it did. So, this was a precaution from an earlier code that I was working on.

After carefully reviewing your suggestion regarding rewriting the code for the method calls, it makes sense since we shouldn’t have to have two methods with essentially the same code. Thus, you only check if there is an existing .csv file when running the application at startup, and not every time when adding user info to the list.

Overall, your recommendations have been implemented. Yes, this is much better code.

Again, thank you and I appreciate your insights!. 🤝

```auto
import tkinter as tk
import csv
from pathlib import Path

class App(tk.Tk):

    FIELD_NAMES = ['Nome', 'Cognome', 'Nascita']
    CSV_FILE_PATH = Path('/folder_tree/user_information.csv')

    def __init__ (self):
        
        super(). __init__ ()

        # Create main window
        self.title("Terminale Utente")
        tk.Tk.geometry(self, '260x160')

        # Create labelframe and title
        group_1 = tk.LabelFrame(self, padx = 15, pady = 10,
                                text = "Informazione Personale")
        group_1.pack(padx = 10, pady = 5)

        self.fields = {}
        
        for _row, field_name in enumerate(self.FIELD_NAMES):
            
            tk.Label(group_1, text=field_name).grid(row = _row)
            entry = tk.Entry(group_1)
            entry.grid(row =_row, column = 1, sticky = tk.W)
            self.fields[field_name] = entry            

        # Create button 
        self.btn_submit = tk.Button(self, text = "Salva Informazioni", command = self.update_list)
        self.btn_submit.bind("<Return>", self.update_list) # Add carriage Return functionality to button
        self.btn_submit.pack(padx = 10, pady = 10)

        self.fields[self.FIELD_NAMES[0]].focus_set() # Put prompt inside widget entry by default                                               

        if not self.CSV_FILE_PATH.exists():            
            self.write_csv_row(self.FIELD_NAMES)

    def update_list(self, *args):

        user_info = [entry.get() for entry in self.fields.values()]            
        self.write_csv_row(user_info)  

    def write_csv_row(self, row_data):
        
        with open(self.CSV_FILE_PATH, 'a', encoding='UTF-8') as csv_file:
            
            csv_writer = csv.writer(csv_file)
            csv_writer.writerow(row_data)             

        # Clear entries 
        for entry in self.fields.values():            
            entry.delete(0, 'end')            

        self.fields[self.FIELD_NAMES[0]].focus_set() # Reset prompt focus
        
if __name__ == " __main__":
    
    app = App()
    app.mainloop()

```

---

<div class="post-metadata">

**Author:** ![CAM-Gerlach](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cam-gerlach/32/3688_2.png) [@CAM-Gerlach](https://discuss.python.org/u/CAM-Gerlach)\
**Post date:** [December 8, 2023, 10:52pm UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/12 "2023-12-08T22:52:03Z")

</div>

Looks pretty good now, thanks. One last followup comment—right now, you have the following code in `write_csv_row` to clear the current field entries and reset the prompt focus:

> [@onePythonUser](#):
>
> ```auto
> # Clear entries 
> for entry in self.fields.values():            
> entry.delete(0, 'end')            
> 
> self.fields[self.FIELD_NAMES[0]].focus_set() # Reset prompt focus
> 
> ```

However, per the principle of separation of concerns, those should really “belong” to the `update_list` method, since they involve actions in the GUI that should happen after submitting the data to be written, rather than anything to do with writing the CSV itself. And indeed, they aren’t necessary when creating the file during program startup, but are executed anyway.

Therefore, you should move these lines to `update_list` instead. Other than that, looks pretty good!

---

<div class="post-metadata">

**Author:** ![onePythonUser](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/onepythonuser/32/19158_2.png) [@onePythonUser](https://discuss.python.org/u/onePythonUser)\
**Post date:** [December 9, 2023, 2:14am UTC](https://discuss.python.org/t/creating-classes-and-tkinter-gui/39621/13 "2023-12-09T02:14:50Z")

</div>

Hi,

I follow you logic here. I have updated the code at my end. We’ll keep the **write\_csv\_row** method as concise as possible such that it only writes to the file and any other statements in a separate method.

This little piece of code sure has been learning experience.

Much obliged.
