Run same loop twice, but getting different results - python

I cannot figure this out, but say I have a depth-3 array of strings called "text".
How can it be that in the following code:
print "FIRST"
for gate in text[1:]:
print "GATE"
for text in gate:
print "TEXT"
for entry in text:
print "ENTRY"
print what
print "SECOND"
for gate in text[1:]:
print "GATE"
for text in gate:
print "TEXT"
for entry in text:
print "ENTRY"
print what
I get different output for each loop.
"First"
FIRST
GATE
TEXT
ENTRY
א
ENTRY
מחברת אל"ף
ENTRY
אחל לבאר לשון יהודית, להעמיד כל מלה כפי שאת, יש מלה רבת פנים ולא יתבונן המשכיל יסודותיה, כי אם במהות ענינה אשר סביבותיה למרבית פניה, כי המלה מושכת והולכת עד אשר מתחלקת ממראה אחד עד חמשה עשר פנים, על כן יש מלה אשר הענין ימשכנה ויורה עליה וילמד על גזרתה. ויש מלה אשר היא מושכת הענין ומבארת הפתרון ושכל סודו, וכה הוא פתרון הלשון ופשר המלים לפי מחלקותיהם ותוצאותיהם.
TEXT
ENTRY
אב.
"Second"
SECOND
GATE
TEXT
ENTRY
מ
TEXT
ENTRY
ת
TEXT
ENTRY
ח
TEXT
ENTRY
ל
TEXT
ENTRY
Each Loop is coded exactly the same and yet I get different output. How is this possible?

for loops "leak" variables. You might expect gate, text, and entry to be scoped to their respective loops, but they're actually global. So, at the end of this loop
for text in gate:
The value of text has been altered, which affects the next loop.
Here's a more minimal example:
x = 'abc'
for x in x:
print x,
# output: "a b c"
for x in x:
print x,
# output: "c"
(If being able to run the same code twice and get the same result is the kind of thing that you find valuable, Python might not be the right choice of language for you. There are plenty of lovely languages that do have this property.)

text has been modified. Before the SECOND loop, text has its value taken from the last iteration of for text in gate: ...
You should consider renaming the inner loop variable to something different.

Related

How can I insert text and variables into a text box in Python tkinter without elements being contained by {}

I am trying to use a text widget in tkinter to display the output of my code.
sequence = ("This peptide contains ", len(Peptide), "total residues", "\n")
text.insert("current", sequence)
this seems to work, except for that when it is printed, the
elements are separated by {}.
I would prefer for the output to not have these parenthesis, and read like: "This peptide contains 17 total residues". Is there a way to do this?
Thank you
It is because sequence is a tuple. Try changing it to
sequence = "This peptide contains {} total residues\n".format(len(Peptide))

Unexpected String Translation from Dictionary

I'd like to write a program that reads in a file and translates a short string of text 4 characters long to a new string of 4 characters. Currently, I read in a tab-delimited text file containing two columns: an "old tag" and a "new tag". I'm able to successfully build a dictionary that maps the "old tag" as the key and the "new tag" as the value.
My problem comes in when I attempt to use maketrans() and str.translate(). Somehow my "old_tag" is getting converted to a "new_tag" that I don't even have in my dictionary! I've attached screenshots of what I mean.
"P020" should get converted to "AGAC" as outline in my dictionary.
The error is that variable "old_tag" should get converted to "AGAC" as outlined in my dictionary, but it's instead getting converted to "ACAC" (look at variable "new_tag"). I don't even have ACAC in my translation table!
Here's my function that does the string translate:
def translate_tag(f_in, old_tag, trn_dict):
"""Function to convert any old tags to their new format based on the translation dictionary (variable "trn_dict")."""
try:
# tag_lookup = trn_dict[old_tag]
# trans = maketrans(old_tag, tag_lookup)
trans = maketrans(old_tag, trn_dict[old_tag]) # Just did the above two lines on one line
except KeyError:
print("Error in file {}! The tag {} wasn't found in the translation table. "
"Make sure the translation table is up to date. "
"The program will continue with the rest of the file, but this tag will be skipped!".format(f_in,
old_tag))
return None
new_tag = old_tag.translate(trans)
return new_tag
Here's my translation table. It's a tab-delimited text file, and the old tag is column 1, and the new tag is column 2. I translate from old tag to new tag.
The strange this is that it converts just fine for some tags. For example, "P010" gets translated correctly. What could be causing the problem?
You should not use maketrans, as it works on individual characters (per the official documentation). Make it a dictionary, with your original text (1st column) as the key and the new text (2nd column) as its value.
Then you can look up any tag x with trn_dict[x], wrapped by a try or with a test beforehand if x in trn_dict.
database = """P001 AAAA
P002 AAAT
P003 AAAG
P004 AAAC
P005 AATA
P006 AATT
P007 AATG
P008 AATC
P009 ATAA
P010 ATAT
P011 ATAG
P012 ATAC
P013 ATTA
P014 ATTT
P015 ATTG
P016 ATTC
P017 AGAA
P018 AGAT
P019 AGAG
P020 AGAC
P021 AGTA
P022 AGTT
P023 AGTG
P024 AGTC
""".splitlines()
trn_dict = {str.split()[0]:str.split()[1] for str in database}
def translate_tag(old_tag, trn_dict):
"""Function to convert any old tags to their new format based on the translation dictionary (variable "trn_dict")."""
try:
return trn_dict[old_tag]
except KeyError:
print("Error in file {}! The tag {} wasn't found in the translation table. "
"Make sure the translation table is up to date. "
"The program will continue with the rest of the file, but this tag will be skipped!")
return None
print (translate_tag('P020', trn_dict))
shows the expected value AGAC.
(That string-to-list-to-dict code is a quick hack to get the data in the program and is not really part of this how-to.)

Delete range from Text

I am trying to create a keypad using python tk library. I am running Python version 3.6.3.
1) I have a Text widget in my UI. When I press the backspace button, I want to delete the last character in the Text widget. I am keeping the count of the total number of characters in the Text widget as well.
So far I have tried:
def back():
global char_count # contains total char count
text.delete(char_count)
I also tried to adjust the last line to text.delete(char_count-1) thinking that may be the index count was off by 1 (I wasn't sure if my count matched index in the Text widget). The above code doesn't delete anything.
2) I was also trying to see how a range of text can be deleted. I have checked online and I find to delete the entire Text content, people use:
text.delete("1.0", tk.END)
This works, but if I try another approach to delete everything from the second index as follows, nothing happens:
text.delete("2.0", tk.END)
I wanted to ask what is the right way to delete the last character or a range from the text, assuming the indices to be used are in variables and not hard coded like "2.0" above.
1) The Text widget always insures that the last character in the widget is a newline character, so you could delete the one you want, the second-to-last character, like this:
def back():
text.delete('%s - 2c' % 'end')
No need to keep track of the character count in the Text widget.
A full working sample is here:
import sys
if sys.version_info.major == 3:
import tkinter as tk
else:
import Tkinter as tk
def back():
text.delete('%s - 2c' % tk.END)
root = tk.Tk()
text = tk.Text(root)
text.pack()
tk.Button(root, text='Delete', command = back).pack()
root.mainloop()
2) Watch out for text.delete("2.0", tk.END). "2.0" is the start of the second line. The index of a Text widget has different formats, but the simplest is text string 'X.Y', where X is the line number (starting at 1) and Y is the column number (starting at 0) of that line. And the columns don't always line up, since a tab character will take a single column but look much wider in the Text widget.
You can call the delete() method with variables, like text.delete(startIndex, stopIndex). The trick is making sure that the indexes are valid. '1.0' represents the very first position in the Text widget, and 'end' represents the very last. Have a look at http://effbot.org/tkinterbook/text.htm for a pretty reasonable and concise look at Text widgets and how their indexes work.
There are different ways to manipulate a Text index, but some of the most common are with the text.index(arg) method, which returns an 'X.Y' representation of arg, and the nextpos = text.index('%s + 1 chars' % thispos) format, which allows you to do basic math on an index. In this case, it would set nextpos to the next column. But the '+ 1' can be plus or minus any_number, and the 'chars' can be 'lines' or 'words'. There's a lot to it. But have a look at that effbot.org page.

Is it possible to use a custom object instead of String inside a Tkinter Text Widget (python)?

I'm trying to create a text editor that will manipulate a string like object. Ideally I would like to subclass Tkinter's Text Widget or some other Gui module to allow me to substitute this custom object instead of using strings.
I understand how to create the object itself (which essentially just tags every word with meta-data), but not how to manipulate the rendered object as text while retaining its other attributes.
Eg.
The text editor imports a file that contains "Hello World". Upon opening this text, both words are tag'd with relevant attributes. (Displayed below as a dictionary, but ideally an object Hello.strContent, Hello.index, etc.)
Hello={strContent: "Hello", index: 0, speaker: "Joe", paragraph: 3}
World={strContent: "World", index: 1, speaker: "Joe", paragraph: 3}
Now I'm completely stumped though how I could make these string like objects manipulable, and subject to cutting, copying, pasting, deleting, and rearranging, inside a Text gui. I would not need to have the program allow me to type any new words or create new objects, just manipulate the objects that have been initialized upon converting the file I opened.
Any help or direction would be greatly appreciated. Thanks.
Note: this attempts to answer the question that was asked, but I suspect this is an xy problem. At the end of this answer I'll give some other suggestions.
The text widget can't have some complex object as its underlying data structure. It can display text, and it can have tags associated with the text. You can also insert images and widgets, but I think that's irrelevant to what you are asking.
When reading the data in, you can construct tags for each piece of metadata, and associate those tags with a range of text. For example, the word "Hello" could have the tags "paragraph:3", "speaker:Joe", and "index:0". "World" would be similar, except it would have the tag "index:1".
This would be easy to do when initially displaying the data. For example:
data = [{"strContent": "Hello", "index": 0, "speaker": "Joe", "paragraph": 3},
{"strContent": "World", "index": 1, "speaker": "Joe", "paragraph": 3}
]
for item in data:
tags = (
"index:%d" % item['index'],
"speaker:%s" % item['speaker'],
"paragraph:%d" % item['paragraph']
)
self.text.insert("end", item['strContent'], tags)
If you then go in and insert "r" in the word "World", it will inherit the tags of the surrounding text.
You can get the data back out of the widget with the dump method, which returns a stream of data. For example, self.text.dump("1.0", "end-1c", tag=True, text=True, mark=False) yields this information:
[
('tagon', 'paragraph:3', '1.0'),
('tagon', 'speaker:Joe', '1.0'),
('tagon', 'index:0', '1.0'),
('text', 'Hello', '1.0'),
('tagoff', 'index:0', '1.5'),
('tagon', 'index:1', '1.5'),
('text', 'World', '1.5')
]
Reassembling that data back to your original format is tricky. Here's a rough cut of an attempt, though I don't know how it would stand up in the real world. It might be possible for users to edit the data in ways that totally mess up the structure.
def get_data(self):
result = []
meta = {}
for item in self.text.dump("1.0", "end-1c", tag=True, text=True, mark=False):
if item[0] == "tagon":
(name, value) = item[1].split(":")
meta[name] = value
if item[0] == "tagoff":
(name, value) = item[1].split(":")
del meta[name]
if item[0] == "text":
text = item[1]
# if this text has the same tags as the previous text,
# don't create a new item; instead, append the text to
# the previous item
if result and all(item in result[-1].items() for item in meta.items()):
result[-1]["strContent"] += text
else:
data = {"strContent": text}
data.update(meta)
result.append(data)
return result
Not knowing what you're actually trying to accomplish, a text widget might not be the best solution since it gives the user a bit too much freedom to alter the text. For example, what should happen if they change "Hello" to "HelloWorld", and then delete the original "World"? Do they end up with one "HelloWorld" item, or the original two "Hello" and "World" items?
You might want to consider using either a canvas, where each text item is a distinct object (which can also have tags), or perhaps you might want to use a series of entry widgets so that the data in once can't bleed over into the other.
i think that you could do something like
class myobj:
def __init__(self, text):
self.strContent=text
hello = [[myobj("Hello"), myObj("World")], [myobj("Paragraph"), myobj("2")]]
then whenever you need to display that text, you would have it loop through like so
printableStr = ""
for paragraph in hello:
for word in paragraph:
printableStr += word.strContent + " "
printableStr += "\n"
and then printableStr will be a nice string containing all info from hello

python-taking part of the text stored within a combo box

I have a combo box on a form that I created. This combo box displays an ID and a name together. I am writing the contents of the combo box to a database. So far it writes correctly when I use this piece of code
self.ui.residentComboBox.currentText()
Now what I want to be able to do is pull only the ID from the combo box instead of the ID and the name together. Can anyone help?
If self.ui.residentComboBox.currentText() returns a string, and the ID is just the first word, you can do this:
self.ui.residentComboBox.currentText().split()[0]
which splits the string into a list of words (separated by whitespace) and then the [0] gives the first item in that list.
For example:
t = "3426523 askew chan"
print t.split()
#['3426523','askew','chan']
print t.split()[0]
#'3426523'

Categories