2
\$\begingroup\$

I am a beginner to Python (and programming overall) and I decided that it would be funny if I wrote a program that could convert Letters/Numbers to these "Letter Emojis" in Discord. It works but I am quite unsure if this is the best way to do it. There are also some bugs caused by adding spaces before actually typing Letters/Numbers. I commented it as precisely as I could to make the Code as understandable as possible

from tkinter import *


root = Tk()


# defines the funtion click which gets called when the submitButton1 is pressed
def click():
    # makes sure the list is empty in case the user presses the button again to convert something new
    result.clear()
    # saves the text which is entered in the textentry box, this later gets converted to "Discord Letters"
    entered_text=textentry.get()
    # deletes the output box in case the user presses the button again to convert something new
    output.delete(1.0, END)
    # the function to convert a String to "Discord Letters"
    discord(entered_text)
    # loop which gets the length of the string typed in the textentry box and then outputs it to the output textbox
    for i in range(len(entered_text)):
        output.insert(END, result[i])


# A List in which the converted Letters are stored it's later used to output the converted Letters in a Textbox
result = []


# the function to convert a String to "Discord Letters"
def discord(word):
    # List of numbers from 0-9 , is used to check the string for numbers and then convert them
    # to "Discord Digits" since they have a different syntax in discord than Letters
    chars = set('0123456789')
    s = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
         'w', 'x', 'y', 'z'}
    # In case the User enters spaces they get removed since they just cause trouble
    word = word.replace(" ", "")
    word = word.lower()
    w = word
    for i in range(len(w)):
        # checks if the string has a number and outputs accordingly
        if any((c in chars) for c in w[i]):
            list_one = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", ]
            result.append(':' + list_one[int(w[i])] + ":" + " ")
        # checks if the string has letter and outputs accordingly (might be unnecessary)
        elif any((c in s) for c in w[i]):
            result.append(':regional_indicator_' + w[i] + ":" + " ")
        else:
            # In case the user inputs something wrong
            print("False Input")


root.title("Discord Letter Converter")
# TextInput
textentry = Entry(root, width=20, bg="white")
textentry.pack()
# submitButton
submitButton1 = Button(root, text="Submit", width=6, command=click)#
submitButton1.pack()
# TextOutput
output = Text(root, width=75, height=6, wrap=WORD, background="white")
output.pack()
root.mainloop()
```
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review. \$\endgroup\$ – greybeard Apr 18 at 20:04
3
\$\begingroup\$

Disclaimer: I know nothing about these "Discord letters"

Code organisation

You could reorganise your code to make units easier to use independently.

The easiest thing to do it to re-write the discord function so that it returns a value instead of updating a global variable.

Another thing to do is to put all the Tkinter logic into a function. Usually, that function actually doing things would be put behind an if __name__ == "__main__" guard.

We'd have

# the function to convert a String to "Discord Letters"
def convert_to_discord_letters(word):
    # A List in which the converted Letters are stored it's later used to output the converted Letters in a Textbox
    result = []
    # List of numbers from 0-9 , is used to check the string for numbers and then convert them
    # to "Discord Digits" since they have a different syntax in discord than Letters
    chars = set('0123456789')
    s = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
         'w', 'x', 'y', 'z'}
    # In case the User enters spaces they get removed since they just cause trouble
    word = word.replace(" ", "")
    word = word.lower()
    w = word
    for i in range(len(w)):
        # checks if the string has a number and outputs accordingly
        if any((c in chars) for c in w[i]):
            list_one = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", ]
            result.append(':' + list_one[int(w[i])] + ":" + " ")
        # checks if the string has letter and outputs accordingly (might be unnecessary)
        elif any((c in s) for c in w[i]):
            result.append(':regional_indicator_' + w[i] + ":" + " ")
        else:
            # In case the user inputs something wrong
            print("False Input")
    # print(result)
    return result


# defines the funtion click which gets called when the submitButton1 is pressed
def click():
    # saves the text which is entered in the textentry box, this later gets converted to "Discord Letters"
    entered_text=textentry.get()
    # deletes the output box in case the user presses the button again to convert something new
    output.delete(1.0, END)
    # the function to convert a String to "Discord Letters"
    result = convert_to_discord_letters(entered_text)
    # loop which gets the length of the string typed in the textentry box and then outputs it to the output textbox
    for i in range(len(entered_text)):
        output.insert(END, result[i])


def tkinter_discord_letter_converter():
    root = Tk()
    root.title("Discord Letter Converter")
    # TextInput
    textentry = Entry(root, width=20, bg="white")
    textentry.pack()
    # submitButton
    submitButton1 = Button(root, text="Submit", width=6, command=click)#
    submitButton1.pack()
    # TextOutput
    output = Text(root, width=75, height=6, wrap=WORD, background="white")
    output.pack()
    root.mainloop()



if __name__ == '__main__':
    tkinter_discord_letter_converter()

Note: I do not have tested this because I do not have Tkinter but I've tested parts of your code because I have...

Unit Tests

Now that the code is reorganised, we can easily write small tests for the discord function that I've renamed test_convert_to_discord_letters

def test_convert_to_discord_letters():
    """Test function convert_to_discord_letters."""
    # TODO: This could/should use a proper unit-test framework
    # Empty case
    assert convert_to_discord_letters("") == []
    # Whitespace
    assert convert_to_discord_letters(" ") == []
    # Special characters
    assert convert_to_discord_letters("#~&$£()[]") == []
    # Lowercase letters
    assert convert_to_discord_letters("abcz") == [':regional_indicator_a: ', ':regional_indicator_b: ', ':regional_indicator_c: ', ':regional_indicator_z: ']
    # Uppercase letters
    assert convert_to_discord_letters("ABCZ") == [':regional_indicator_a: ', ':regional_indicator_b: ', ':regional_indicator_c: ', ':regional_indicator_z: ']
    # Digits
    assert convert_to_discord_letters("42") == [':four: ', ':two: ']
    # Mix
    assert convert_to_discord_letters("Bar -_- 9") == [':regional_indicator_b: ', ':regional_indicator_a: ', ':regional_indicator_r: ', ':nine: ']

Improving test_convert_to_discord_letters

Now that we have unit-tests for the function, we can more safely try to improve it without breaking its behavior.

Loop like a native

I highly recommend Ned Batchelder's talk "Loop like a native" about iterators. One of the most simple take away is that whenever you're doing range(len(iterabme)), you can probably do things in a better way: more concise, clearer and more efficient.

In your case, this gives:

    for char in w:
        # checks if the string has a number and outputs accordingly
        if any((c in chars) for c in char):
            list_one = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", ]
            result.append(':' + list_one[int(char)] + ":" + " ")
        # checks if the string has letter and outputs accordingly (might be unnecessary)
        elif any((c in s) for c in char):
            result.append(':regional_indicator_' + char + ":" + " ")
        else:
            # In case the user inputs something wrong
            pass # print("False Input")

Then, a few things seem more obvious:

  • we don't really need the w variable. Also, various re-assignment of word are not required
  • the variations of any((c in XXX) for c in char) look weird once we realise that char is a single character. We could write this: if char in XXX
  • the variable chars would be better named digits while s would be better named letters

At this stage, we have

# the function to convert a String to "Discord Letters"
def convert_to_discord_letters(word):
    # A List in which the converted Letters are stored it's later used to output the converted Letters in a Textbox
    # List of numbers from 0-9 , is used to check the string for numbers and then convert them
    # to "Discord Digits" since they have a different syntax in discord than Letters
    digits = set('0123456789')
    letters = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
    result = []
    for char in word.replace(" ", "").lower():
        # checks if the string has a number and outputs accordingly
        if char in digits:
            list_one = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", ]
            result.append(':' + list_one[int(char)] + ":" + " ")
        # checks if the string has letter and outputs accordingly (might be unnecessary)
        elif char in letters:
            result.append(':regional_indicator_' + char + ":" + " ")
        else:
            # In case the user inputs something wrong
            pass # print("False Input")
    # print(result)
    return result

Definitions of data used

For letters, you could use ascii_lowercase from string module:

import string
...
    letters = set(string.ascii_lowercase)

For digits, you define a set and a list, convert the value to integer to get the string at the relevant index. It seems like the best data structure to use would be a single dictionnary mapping digits to strings.

    digits = {"0": "zero", "1": "one", "2": "two", "3": "three", "4": "four", "5": "five", "6": "six", "7": "seven", "8": "eight", "9": "nine"}

Useless concatenations of literal string

":" + " " can be written ": ".

At this stage, we have:

# the function to convert a String to "Discord Letters"
def convert_to_discord_letters(word):
    # A List in which the converted Letters are stored it's later used to output the converted Letters in a Textbox
    # List of numbers from 0-9 , is used to check the string for numbers and then convert them
    # to "Discord Digits" since they have a different syntax in discord than Letters
    digits = {"0": "zero", "1": "one", "2": "two", "3": "three", "4": "four", "5": "five", "6": "six", "7": "seven", "8": "eight", "9": "nine"}
    letters = set(string.ascii_lowercase)
    result = []
    for char in word.replace(" ", "").lower():
        # checks if the string has a number and outputs accordingly
        if char in digits:
            result.append(':' + digits[char] + ": ")
        # checks if the string has letter and outputs accordingly (might be unnecessary)
        elif char in letters:
            result.append(':regional_indicator_' + char + ": ")
        else:
            # In case the user inputs something wrong
            pass # print("False Input")
    print(result)
    return result

And we realise that most comments are not required anymore because the code is much clearer. On the other hand, we could add a proper docstring.

def convert_to_discord_letters(word):
    """Convert a string into a list of strings corresponding to "Discord Letters"."""
    digits = {"0": "zero", "1": "one", "2": "two", "3": "three", "4": "four", "5": "five", "6": "six", "7": "seven", "8": "eight", "9": "nine"}
    letters = set(string.ascii_lowercase)
    result = []
    for char in word.replace(" ", "").lower():
        if char in digits:
            result.append(':' + digits[char] + ": ")
        elif char in letters:
            result.append(':regional_indicator_' + char + ": ")
        else:
            pass # TODO: I'll let you decide how to handle other cases
    return result

Removing the useless logic

It is now clearer that the .replace(" ", "") logic removes whitespace which would have no effect anyway (as we'd end up in the non-handled case). You can remove this.

More

I suspect the click function could benefit from the same ideas but it can't test it at the moment.

Also, the code seems to assume that entered_text and result will have the same length which may not be the case for non-handled characters.

\$\endgroup\$
  • \$\begingroup\$ +1 I really like the step by step approach! \$\endgroup\$ – Bailey Parker Apr 19 at 0:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.