How can I optimize my simple code (python)? [closed] - python

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I'm taking an intro programming class and am working ahead on some assignments that aren't due for a few weeks. This one asks me to take three text files - names, titles, and descriptions - and use them to randomly generate "fantasy character" names. My code runs and does what I want it to, but I feel like it's cluttered and could be cleaned up quite a bit. Keep in mind that this is an INTRO course and in class have just covered basic boolean logic, loops, arrays, etc.; not classes, object-oriented, or any advanced stuff (I'm trying to learn some of this on my own).
import random
def main():
for i in range(10):
# Load names, titles, descriptions into arrays
names = loadFile('names.txt')
title = loadFile('titles.txt')
descriptor = loadFile('descriptors.txt')
# Generate random number based on list length
nameListLength = len(names)
titleListLength = len(title)
descListLength = len(descriptor)
firstNameIndex = random.randrange(nameListLength)
lastNameIndex = random.randrange(nameListLength)
randTitleIndex = random.randrange(titleListLength)
randDescriptor = random.randrange(descListLength)
# Choose random list entry
firstName = names[firstNameIndex]
lastName = names[lastNameIndex]
title2 = title[randTitleIndex]
description = descriptor[randDescriptor]
nameList = [title2, firstName, lastName, description]
dumpFile(nameList)
print(title2, firstName, lastName, 'the', description)
print()
print('These names have been written to \"CharacterNames.txt\".')
def dumpFile(nameList):
title = str(nameList[0])
firstName = str(nameList[1])
lastName = str(nameList[2])
descriptor = str(nameList[3])
outfile = open('CharacterNames.txt', 'a')
outfile.write(title + ' ' + firstName + ' ' + lastName + ' ' +
'the' + ' ' + descriptor + '\n')
outfile.write('\n')
outfile.close()
def loadFile(nameFile):
nameList = open(nameFile, 'r')
nameArray = []
for line in nameList:
name = line
name = name.rstrip('\n')
nameArray.append(name)
nameList.close()
return nameArray
main()

I see value in having others look at your code and rewrite it as they would do it. I kind of ignored your restriction on "any advanced stuff," although I don't think any of it will be too complicated for you to intuit. Here's my rewrite:
import random
def generatePerson(names=loadFile('names.txt'),
titles=loadFile('titles.txt'),
descriptons=loadFile('descriptors.txt')):
firstName = random.choice(names)
lastName = random.choice(names)
title = random.choice(titles)
description = random.choice(descriptons)
return '{} {} {} the {}'.format(title, firstName, lastName, description)
def main():
people = [generatePerson() for _ in range(10)]
dumpFile('\n\n'.join(people))
print('\n'.join(people))
print('\nThese names have been written to "CharacterNames.txt".')
def dumpFile(data, filename='CharacterNames.txt'):
with open(filename, 'a') as outfile:
outfile.write(data)
def loadFile(nameFile):
with open(nameFile, 'r') as nameList:
return nameList.read().splitlines()
if __name__ == '__main__':
main()
It still resembles your code in many ways, however, most of it has been rewritten. Now for details on some of the more drastic changes.
I moved the code related to picking random strings from the three lists into its own function generatePerson. This makes the easier to maintain in case the method of generating a random person or something else changes in the future.
I greatly simplified the logic of picking the random strings by using random.choice.
I used a trick that uses default arguments in generatePerson to avoid reading from the three files each time a random name is created. The only part of this trick that you should know for now is that the values of default arguments are created only once in Python.
Instead of passing a list into dumpFile, I opted to pass a string, which I can then immediately write to the file. This makes more sense because I had generatePerson return the formatted string for that person rather than [title, firstName, lastName, description].
This is mostly just my preference, but I don't really like for-loops, so in main I used a list comprehension to create a list of 10 random names. From there, I used str.join to create a single string with all ten names that is suitable to be passed to dumpFile and print.
I believe that is (nearly) all of the changes I made. If I missed something or if you need further clarification on anything I mentioned either comment on my answer or just ask a new question.

Move file operations before the 'for' loop - there is no need to re-read the files 10 times. That should make the application run much faster.

Related

Access a dictionary key inside a dictionary key [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I'm trying to make a program that gets a country name, goes to restcountries.com, gets the country's current population number, does the same thing another time, and tells if the population got bigger/smaller/same. I am a beginner at python and coding, so existing answers to similar questions were not so useful to me since they seem different than my problem. I have managed to make the code get the JSON from the site, keep it, and tell me the population number and it worked (I tested it with "Israel". But when I gave it to my pal, he wrote "Russia" and it failed. After a short investigation, I discovered restcountries.com calls Russia by the name "Russian Federation" and has a separate JSON dictionary key for common names called "common:". But I can't get to it and extract it by code. Here is the JSON from this URL: https://restcountries.com/v3/name/russia?fields=name,population
[{"name":{"common":"Russia","official":"Russian Federation","nativeName":{"rus":{"official":"Российская Федерация","common":"Россия"}}},"population":144104080}]
Here is my current code:
import bs4
import urllib.request
import json
country_name = input("Choose country: ")
# Getting updated list and saving it as "json_data"
link = 'https://restcountries.com/v3/name/' + country_name + '?fields=name,population'
webpage = str(urllib.request.urlopen(link).read())
soup = bs4.BeautifulSoup(webpage, "html.parser")
json_data = soup.get_text()
# Cleans json_data from all the bullshit
clean_json = json_data[:-1][2:].replace('\\', '.') \
.replace('.xc3.x85', 'A').replace('.xc3.xa7', 'c').replace('.xc3.xa9', 'e')
# Convert clean_json to a dictionary
loaded_json = json.loads(clean_json)
# Country search function
def search(name):
for p in loaded_json:
if p['name'] == name:
return p['population']
# Choose Country, save data.
country_population = search(country_name)
datafile = open('G:\HackerU\Python\Challenges\Population\dataname.txt','w')
def save(done):
datafile = open('G:\HackerU\Python\Challenges\Population\dataname.txt','w')
datafile.write(country_population)
datafile.close()
return True
saved_population_number = datafile.read()
if saved_population_number > country_population:
print("Population number has been decreased.")
elif saved_population_number < country_population:
print("Population number has risen.")
elif saved_population_number == country_population:
print("Population number stayed the same.")
else:
print("Unknown Error.")
How do I make my code recognize a country only by its "common" name?
It seems that there is a lot of unnecessary code here:
First of all, an API returns a JSON, therefore I don't see why you need to use bs4. You can load the JSON into a dict using json.loads.
Secondly, you don't want a string representation of the bytes object - but to decode the bytes. Therefore you can use urllib.request.urlopen(req).read().decode().
Therefore I would do something like this:
import json
import urllib.request
country_name = input("Choose country: ")
req = 'https://restcountries.com/v3/name/' + country_name + '?fields=name,population'
response = json.loads(urllib.request.urlopen(req).read().decode())[0]
Now you have the JSON response in a dictionary, and accessing response['population] gets you the country's population.
In addition, I would recommend learning about context managers and using them. For example, I would change the save function to (also, why does it return True?):
def save(country_population):
with open('G:\HackerU\Python\Challenges\Population\dataname.txt','w') as datafile:
datafile.write(country_population)
Good luck in the course, Python is a great language to start coding with.
Your issue has to do with how the json data is formatted. Replace if p['name'] == name: by if p['name']['common'] == name:

Filter search function in Tkinter python for lists

So this is what my GUI looks like:
GUI
,and where i need help with is if i type a subject code it must show all the students with the that particular subject. Here is my code for the search function and add function:
def add_student():
Sname = Student_name.get()
Ssurnname = Student_surname.get()
Sdetail = Student_detail.get()
Snumber = Student_number.get()
i = Students(Sname,Ssurnname,Sdetail,Snumber)
Sinfo.append(i)
iName = Student_subject.get()
iCode = Student_code.get()
iMark1 = Student_Mark1.get()
iMark2 = Student_Mark2.get()
iMark3 = Student_Mark3.get()
iProject = Student_project.get()
j = Subjects(iName,iCode,iMark1,iMark2,iMark3,iProject)
SSubject.append(j)
kCourse = Degree_course.get()
kCode = Degree_code.get()
kYear = Degree_year.get()
v = Degrees(kCourse,kCode,kYear)
SDegree.append(v)
popup_add()
student_list.append(Sinfo)
student_list.append(SSubject)
student_list.append(SDegree)
def filter_data():
top3 = Toplevel()
top3.geometry('300x300')
top3.title("Search")
Searchlabel = Label(top3, text = "Please enter the Subject code: ")
Searchlabel.grid(column=1, row=1, sticky = (W,E))
searchValue = StringVar()
top3.searchBox = ttk.Entry(top3, textvariable=searchValue).grid(column=1, row=2, sticky = (W,E))
def searchdata(*args):
print("*")
resultList.delete(0,END)
searchkey = searchValue.get()
for student in student_list:
if searchkey == student[0]:
resultList.insert(END,str(student))
elif searchkey == student[1]:
resultList.insert(END,str(student))
top3.button_1 = Button(top3, text = "Search", command = searchdata)
top3.button_1.grid(column=3, row=2, sticky = (W,E))
Observations
In the code you posted, it appears that student_list is a list with exactly three elements: a list of Students objects, a list of Subjects objects, and a list of Degrees objects. I make that observation based on these lines of code:
...
Sinfo.append(i)
...
SSubject.append(j)
...
SDegree.append(v)
...
student_list.append(Sinfo)
student_list.append(SSubject)
student_list.append(SDegree)
I have no way of knowing if that's your intent, or if that's the first bug. I also have no way of knowing if it will always have exactly three or more than three (but always a multiple of three).
Assuming that it's intentional that student_list will always have exactly three elements, this loop is incorrect:
for student in student_list:
if searchkey == student[0]:
resultList.insert(END,str(student))
The first time through the loop, student will itself be a list of Students. The second time through the loop, student will be a list of Subjects), and the third time through it will be a list of Degrees.
Suggestions
Assuming that your data structures are intentional and that your ultimate goal is to be able to generate a list of students based on a given subject code, you will need to iterate over the list of subjects, not students.
Given the current code structure, you would need to do something like this (using a temporary variable subjects for clarity):
subjects = student_list[1]
for subject in subjects:
...
However, given that the students are in one list but the subjects are in another, you need to keep track of the index in the list of the subjects so that you can use the same index to reference the related student.
Note: This would be much easier if the subjects were an attribute of the student, so you might want to consider making the subjects and degrees attributes of a student.
We can keep track of the index within the list of subjects by using python's enumerate function:
for index, subject in enumerate(subjects):
...
Within the loop, you need to compare what the user entered with the subject code, and if you find it, you need to insert the corresponding student in the window.
Without knowing for certain, I'm going to assume that the Subjects class provides the subject code as the attribute code. I have no way of knowing if that's a correct assumption.
students = student_list[0]
subjects = student_list[1]
for index, subject in enumerate(subjects):
if searchkey == subject.code:
this_student = students[index]
resultList.insert(END, str(thisstudent))
Solving the problem through better data structures
The real solution to your problem might be to rethink some of your design choices. Instead of keeping student info, student subjects, and student grades in three separate lists, you might want to consider creating a Student class that has the info, subjects and lists as attributes. With that, all of the data for a student is in one place.
For example:
class Student(object):
def __init__(self, info, subjects, grades):
self.info = info
self.subjects = subjects
self.grades = grades
You would then create student_list like this:
student_list.append(Student(Sinfo, SSubject, SDegree))
With that you can loop over these students in a slightly more easy and understandable way:
for student in student_list:
for subject in student.subjects:
if subject.code == searchkey:
resultList.insert(END, str(student.info))
You can make this even easier by creating a method that can do the test for you:
class Student(object):
def has_subject(code):
for subject in self.subjects:
if subject.code == code:
return True
return False
Then, your loop becomes even clearer:
for student in student_list:
if student.has_code(searchkey):
resultList.insert(END, str(student.info))
Final thoughts
My answer could be wrong, because your question lacks a considerable amount of information. This is why we ask for a Minimal, Complete, and Verifiable Example that actually runs. Without it, we have to make many guesses and assumptions about your code. If the assumptions are bad, I will have wasted half an hour addressing the wrong problem, and you will have wasted time reading an answer that isn't relevant.
Finally, it would be easier for you to get help if you were to write code that conforms to PEP8 naming standards. Your code is difficult to read because of your unconventional use of uppercase characters. People tend to not want to provide answers to code that is difficult to read.

Where to put this class in django?

I have a class for data entry that requires a lot of input from the user. I use it to semi-automate the process of putting stuff in the db where it is possible.
My instinct is to put it in my model classes, and write tests on it, but it would be an insane amount of work, and I have a lot of raw_input() functions and logic loops that I don't know how to test or what to do with.
Should I keep this module separate or try to include it in the model classes?
def define(self, word=False, word_pk=False):
'''Defining a word, there may be language specific elements to edit in here'''
try:
if word_pk:
word = Word.objects.get(id=word_pk)
else:
word = Word.objects.get(language__name=self.language_ISO, name=word)
except:
return "Word lookup failed for word=%s word_pk=%s\n" % (word, word_pk)
print "\n\tThe Word is: '%s'...\n" % (word)
wiktionary_list = word.wiktionary_lookup(self.wiktionary_prefix, self.driver)
wn_tuple = word.wn_lookup()
while choice("Would you like to add a/another definition for '%s'?: " % word):
#Ask the user if they want to use the wn output for definitions, make them select which ones
if choice("Would you like to choose a wordnet definition?: "):
chosen_defs = ask_input("Which ones? (choose all that apply with a space between numbers): ")
chosen_defs = [int(i) for i in (chosen_defs.split())]
#Wornet only gives part of speech and definition information so I need to split that here.
for i in chosen_defs:
#Print_n_save function will return False if it exits successfully, so there is an option to repeat this loop if the user makes a mistake somewhere
repeat = True
while repeat:
tup = wn_tuple[i]
print "\n(%s) - %s\n" % (tup[0], tup[1])
audio_tup = self.add_audio(word)
picture_tup = self.add_picture(word)
new_definition = Definition()
new_definition.word=word
new_definition.part_speech= tup[0]
new_definition.definition=tup[1]
new_definition.def_source="Wordnet"
new_definition.add_pronunciation()
new_definition.word_audio=audio_tup[0]
new_definition.audio_source=audio_tup[1]
new_definition.picture=picture_tup[0]
new_definition.pic_source=picture_tup[1]
repeat = self.print_n_save(new_definition)
elif choice("Would you like to choose a wiktionary definition?: "):
choose_defs = ask_input("Which ones would you like to choose? (Numbers separated by spaces): ")
chosen_defs = [int(i) for i in choose_defs.split()]
for i in chosen_defs:
#Print_n_save function will return False if it exits successfully, so there is an option to repeat this loop if the user makes a mistake somewhere
repeat = True
while repeat:
print "\n%s\n" % (wiktionary_list[i])
audio_tup = self.add_audio(word)
picture_tup = self.add_picture(word)
new_definition = Definition()
new_definition.word=word
new_definition.get_pos()
new_definition.definition=wiktionary_list[i]
new_definition.def_source="Wiktionary"
new_definition.add_pronunciation()
new_definition.word_audio=audio_tup[0]
new_definition.audio_source=audio_tup[1]
new_definition.picture=picture_tup[0]
new_definition.pic_source=picture_tup[1]
repeat = self.print_n_save(new_definition)
else:
#Print_n_save function will return False if it exits successfully, so there is an option to repeat this loop if the user makes a mistake somewhere
repeat = True
while repeat:
#Asking for definition, inputting raw from some internet source
definition = ask_input("What is the definition?: ")
definition_source = ask_input("What is the source of the definition?: ")
audio_tup = self.add_audio(word)
picture_tup = self.add_picture(word)
new_definition = Definition()
new_definition.word=word
new_definition.get_pos()
new_definition.definition=definition
new_definition.def_source=definition_source
new_definition.add_pronunciation()
new_definition.word_audio=audio_tup[0]
new_definition.audio_source=audio_tup[1]
new_definition.picture=picture_tup[0]
new_definition.pic_source=picture_tup[1]
repeat = self.print_n_save(new_definition)
Don't try to force a raw python function into a single box.
What you should have done (a long long time ago), is separate it out into separate functions so it would be easier to test and figure things out.
Since you're asking for user input, websites do that through forms, so you're going to need a form - or a form wizard/set/whatever.
That form is going to need at least one view to handle it, so you might need to write that too, or use a generic view.
Who knows, the model might even need to do something post processing (I didn't really read the code)
I would put this into management/commands. Just wrap your functions into a BaseCommand class and you are good to go. And here is how to make testing.

Python: list index out of range, but there are items in list? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I'm trying to add 18 list items to a 3 text file, 6 items for each them. paths[0] and paths[1] text files are correct but third one paths[2] only gets 3 items.
mainStudents = ["1 Name Surname D1", "2 Name Surname D2" ...16x]
def randomize(path):
count = 0
currentpathIs = getprojdir + "\\" + "\shuffled" + path
print(currentpathIs)
with open(currentpathIs, "a+") as file:
while True:
try:
file.write(mainStudents[count] + "\n")
del(mainStudents[count])
print(count)
count += 1
except Exception as e:
print(mainStudents)
break
if count == 6:
break
randomize(paths[0])
randomize(paths[1])
randomize(paths[2])
I'm getting this error:
Traceback (most recent call last):
File "C:\Users\user\Desktop\New folder\python.py", line 53, in randomize(paths[2])
File "C:\Users\user\Desktop\New folder\python.py", line 43, in randomize
file.write(mainStudents[count] + "\n")
IndexError: list index out of range
But there's 3 items left in mainStudents list?
The problem you have is that when you delete the item from your list, you are then decreasing its size. Therefore, your count is going to increment and then try to access an index that no longer exists. Take a very simple example of just having a list of size two:
mainStudents = ["1 Name Surname D1", "2 Name Surname D2"]
Now, when you call your method, what is going to happen, the first iteration will work, because you will access mainStudents[0].
But, on your second iteration, you have deleted that item from the list, so now your list looks like:
['2 Name Surname D2']
Which is now a list of size one, and the index access for it would be 0.
So, the next iteration of your while loop will have count at 1. Therefore, that is exactly where your IndexError is coming from.
The combination of deciding to use a while loop and del items from your list is what is causing issues. Instead, choose what it is exactly you want to iterate over, which from your logic seems like it is mainStudents. So why not just do that instead?
def randomize(path):
currentpathIs = getprojdir + "\\" + "\shuffled" + path
print(currentpathIs)
with open(currentpathIs, "a+") as file:
for student in mainStudents:
file.write(student + "\n")
And you can further simplify that by simply taking your list and converting it to a string separated by \n by using the available string method, join:
'\n'.join(mainStudents)
Furthermore, there are available methods to facilitate path creation. Take a look at the os module. More specifically os.path.join. So, your code can be further simplified to:
from os.path import join
def randomize(path):
currentpathIs = join(getprojdir, shuffled, path)
with open(currentpathIs, "a+") as file:
file.write('\n'.join(mainStudents))

name.replace xX with y if x exists [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
To be more specific I'd like to figure out how to:
name.replace xX with y if x exists, if not then just replace X
I've searched this forum for an hour now, make it two, and all I find is how to replace one thing with another, which by now is pretty easy.
/a
you can just run:
output = name.replace('xX','y').replace('X','y')
Example:
name = "123xX345X"
output = "123y345y"
Sounds like a job for regular expression x?X:
>>> import re
>>> text = " test xX blabla"
>>> re.sub('x?X', 'y', text)
' test y blabla'
>>> text = " test X blabla"
>>> re.sub('x?X', 'y', text)
' test y blabla'
Quote from docs about ? mark:
The question mark character, ?, matches either once or zero times; you
can think of it as marking something as being optional. For example,
home-?brew matches either homebrew or home-brew.
if 'x' in name:
name = name.replace('xX','y')
else:
name = name.replace('X','y')
From your example above this is a slightly more involved problem. You have to make sure to do your renames in the root namespace or things can get nasty. You also run the risk of renaming parents before children, which will make it hard to get at the children with one call to ls. So:
def replace_with_any_namespace(src, tgt):
cmds.namespace(set=":")
results = {}
xforms = cmds.ls(r=True, tr=True, l=True) # use long paths and recursive to get all namespaces
xforms = [i for i in xforms if src in i] # only work on items with your target pattern
xforms.sort()
xforms.reverse() # sort and reverse means children get renamed before parents
for item in xforms:
path, sep, shortname = item.rpartition("|") # gets only the last name
newname = shortname.replace(src, tgt) # this should be fine even if the namespace is there
results[item] = cmds.ls(cmds.rename ( item, newname), l=True)[0]
# the paths and returns are all long paths so there are no ambiguities
return results
Are you trying to move things out of their namespaces with this? Thats easier:
cmds.namespace(mv = ("R", ":"), force=True)
which moves everything in R:* to the base namespace. This will probably result in some renames, however. You might want to put important nodes into a set before you call this so you can find them.

Categories