0

I just finished a Python course and I'm working on my very first project, a tool for practising Spanish verbs and their conjugations. The tool is working, but now I want to improve it. For example, it's currently difficult to handle the data (e.g. add past tense to a verb + complicated random quiz function).

I however stumbled upon a couple of problems, one of them being: how can I add a new dictionary and give that dictionary the name from raw input? I thought that shouldn't be hard, but haven't found a solution after a lot of searching.

I have two files: one with the code and a .txt file that contains all verbs as dictionaries on seperate lines. Below you see my function for adding a new verb and appending it to the .txt file.

Example of a line in my .txt file:

to talk = {'present': ['hablo', 'hablas', ... ]}

Current code:

def add_verb():
    verb = {}
    name = (raw_input("Verb in Dutch: "))
    tense = (raw_input("Verb tense: ")) 

    conjugations = []
    conjugations.append(raw_input("Yo: "))
    conjugations.append(raw_input("Tú: "))

    verb_dict[tense] = conjugations

    with open("verbs.txt", "a") as abc:
        abc.write("%s = {'%s': %s}\n" % (name, tense, conjugations))

Basically, what I want is this:

abc.write(dictionary)

Thus, I want the dictionary to be written into the file as it is, but with the name of the dictionary given by raw input.

I'm currently also thinking of using a Verb class, because I think that would make the tool even better, but then I stumbled upon the exact same problem (how do I give a new class instance a name that's given by raw input?).

P.S. If you see other things that I should improve, please don't hesitate to mention this.

Jantien
  • 5
  • 3
  • 1
    Do not name variables `dict`, `list` or `type` they hide the python's builtin types and methods. It sounds like you just need a nested dictionary. – AChampion Aug 23 '17 at 12:33
  • Possible duplicate of [generating variable names on fly in python](https://stackoverflow.com/questions/4010840/generating-variable-names-on-fly-in-python) – Lafexlos Aug 23 '17 at 12:35
  • Yes, I changed the names for this post (in my original code, they are in Dutch) – Jantien Aug 23 '17 at 12:35
  • Even if they are in Dutch, "list" isn't very indicative, and it would probably be a good idea to name it something corresponding to what it actually represents, which will make your code a lot clearer. Unless I've misunderstood and in your translation to English you've also changed the semantics of the names. Also, hoi hoi :) – Izaak van Dongen Aug 23 '17 at 12:54
  • Hoi @IzaakvanDongen! Thanks, I was afraid it would complicate it instead of making it easier, but I see why it didn't. I edited the post! – Jantien Aug 23 '17 at 13:09

2 Answers2

2

From the other context in your question, it sounds like you might actually benefit from a nested dictionary structure more similar to this:

import pprint

spanish_dictionary = {"hablar": {"present": ["hablo", "hablas", "hablamos", ...],
                                 "past": ["i'm", "out", "of", "my", "depth"]},
                      "comer": {"present": ["como", ...] #you get the idea
                                }
                     }

def add_verb():
    spanish_dictionary["tener"] = {"present": ["tengo", ...]}

add_verb()

pprint.pprint(spanish_dictionary)

Note that it will run in Python2 if you replace the ellipses with something appropriate, and it actually already already runs in Python3, giving the output:

{'comer': {'present': ['como', Ellipsis]},
 'hablar': {'past': ["i'm", 'out', 'of', 'my', 'depth'],
            'present': ['hablo', 'hablas', 'hablamos', Ellipsis]},
 'tener': {'present': ['tengo', Ellipsis]}}

Please forgive my very limited Spanish, hopefully this is enough to demonstrate the structure. As you can see, it's generally a good idea not to have dictionaries with names you want to change, but instead to just put these in a bigger dictionary, so rather than having to do anything difficult and dangerous with exec, you can add dictionaries by adding values to the larger dictionary. If you have a name given by raw_input, say, like so:

new_verb_name = raw_input()
new_verb_data = some_processing(raw_input())
spanish_dictionary[new_verb_name] = new_verb_data

Regarding your idea to have a Verb class - I think this is probably a very good idea, but I have a tip. A Verb will probably be immutable, as the conjugations of a verb don't change, so collections.namedtuple seems suited to this task. namedtuple allows you to basically define a class without the hassle of defining it if all it needs to do is store values. Note that namedtuple is actually a factory function that returns a class. You might use it like so:

from collections import namedtuple

Verb = namedtuple("Verb", "past present")
Conjugation = namedtuple("Conjugation", "yo tu")

spanish_dict = {"hablar": Verb(present=Conjugation(yo="hablo", tu="hablas"),
                               past=Conjugation(yo="yo hablo in past :p", tu=...))
               }

print(spanish_dict)
print(spanish_dict["hablar"].present.tu)

This has the output:

{'hablar': Verb(past=Conjugation(yo='yo hablo in past :p', tu=Ellipsis), present=Conjugation(yo='hablo', tu='hablas'))}
hablas

As you can see, it's pretty expressive to be able to use .present.tu to access the attributes of the verb. This is just an example though - you'll get to do the actual job of all the Spanish : )

It can be fun to see what the line

Verb = namedtuple("Verb", "past present")

would be equivalent to in a full class definition. You can view it by running

Verb = namedtuple("Verb", "past present", verbose=True)

Bewarned, this results in 50 odd lines of code. To be honest, whoever implemented namedtuple is probably just showing off a bit ;). But this does illustrate how powerful the namedtuple factory is.

Izaak van Dongen
  • 2,450
  • 13
  • 23
  • It took me a little while to implement (since I'm still new to coding), but the nested dictionary made the entire tool/code so much better and easier! I'm going to try using the classes next, have to read into this more, but this seems even better (especially for the 'random quiz' function in the tool). Dankjewel/muchas gracias! ;) P.S. If you ever whish ito learn Spanish, feel free to use the tool when it's finished, haha! – Jantien Aug 23 '17 at 16:37
0

What you are trying to do is really not recommended, because it mixes data (your user input) and code, which is usually the sign of some design flaw.

But, for the record, you can achieve what you want using exec:

>>> name = input("What is your name?\n")
What is your name?
test

>>> exec(f"{name} = dict()")  # Python 3.6 and above
>>> exec("{0} = dict()".format(name))  # Python 2.7 & 3.5
>>> print(test)
{}

Warning

exec simply executes the code it gets as an argument. As reminded by @Błażej Michalik in the comments, this is quite dangerous, especially combined with user's input.

With my example, if the user decides to enter actual Python code instead of their name, it will get executed so it can cause great damages!!!


Why is it so bad to mix data and code and where to go from here?

I mentioned that doing what you're trying to do is a common sign of design flaw. The simple reason is that your code is not exposed to the outside and should not depend on actual data. Applied to your example: why should the name of a dictionary, which is an object living inside of your program only, depend on anything from "the outside world"?

So, here are some hints on how to proceed:

  • store all your data in the JSON format (or YAML, TOML, XML, etc.). You're not far from that already, given the way you store your data in the text file. It'll just make it more standard.
  • if you use user input, store that input in a variable, which has any name you want. For instance, let's say you want the user to create some collection of words, just create a class WordsCollection, which will have a name attribute, and create instances of that class with any variable name you want.
  • if you start learning Python, I would rather choose working with Python 3 :)
filaton
  • 2,257
  • 17
  • 27
  • It is not recommended, because that `exec()` is a surefire way to introduce a code injection vulnerability to your app. – Błażej Michalik Aug 23 '17 at 12:38
  • 1
    Well, that's why I mentioned that not being recommended :) I'll add more warnings to my answer! – filaton Aug 23 '17 at 12:39
  • Are you sure that's what they want to do? To me it looks like all they want is to write it to a file, not introduce it to the global namespace. Also, considering this is in 'Python 2.7', I wouldn't use an f-string. – Izaak van Dongen Aug 23 '17 at 12:50
  • @IzaakvanDongen You're right about Python 2.7, I'll edit my answer. And about what the OP wants to do, I'm also unsure about what they try to achieve but if they get a dictionary named after the user input, it's easy to dump it to a file using `str()`. – filaton Aug 23 '17 at 12:54
  • @filaton Thank you for your reply! I see why it's not recommended, then I'll search for a way around it, starting with looking into your suggestions on how to proceed! Thanks – Jantien Aug 23 '17 at 13:00
  • @Jantien You can mark my answer as accepted if you believe that it answers your question :) Welcome on Stack Overflow! – filaton Aug 23 '17 at 13:01