Pyparsing 'no such attribute _ParseResults__tokdict' on multi-line inputs - python

The following code gives me the error 'no such attribute _ParseResuls__tokdict' when run on an input with more than one line.
With single-line files, there is no error. If I comment out either the second or third line shown here, then I don't get that error either, no matter how long the file is.
for line in input:
final = delimitedList(expr).parseString(line)
notid = delimitedList(notid).parseString(line)
dash_tags = ', '.join(format_tree(notid))
print final.lineId + ": " + dash_tags
Does anyone know what's going on here?
EDIT: As suggested, I'm adding the complete code to allow others to reproduce the error.
from pyparsing import *
#first are the basic elements of the expression
#number at the beginning of the line, unique for each line
#top-level category for a sentiment
#semicolon should eventually become a line break
lineId = Word(nums)
topicString = Word(alphanums+'-'+' '+"'")
semicolon = Literal(';')
#call variable early to allow for recursion
#recursive function allowing for a line id at first, then the topic,
#then any subtopics, and so on. Finally, optional semicolon and repeat.
#set results name lineId.lineId here
expr = Forward()
expr << Optional(lineId.setResultsName("lineId")) + topicString.setResultsName("topicString") + \
Optional(nestedExpr(content=delimitedList(expr))).setResultsName("parenthetical") + \
Optional(Suppress(semicolon).setResultsName("semicolon") + expr.setResultsName("subsequentlines"))
notid = Suppress(lineId) + topicString + \
Optional(nestedExpr(content=delimitedList(expr))) + \
Optional(Suppress(semicolon) + expr)
#naming the parenthetical portion for independent reference later
parenthetical = nestedExpr(content=delimitedList(expr))
#open files for read and write
input = open('parserinput.txt')
output = open('parseroutput.txt', 'w')
#defining functions
#takes nested list output of parser grammer and translates it into
#strings suited for the final output
def format_tree(tree):
prefix = ''
for node in tree:
if isinstance(node, basestring):
prefix = node
yield node
else:
for elt in format_tree(node):
yield prefix + '_' + elt
#function for passing tokens from setResultsName
def id_number(tokens):
#print tokens.dump()
lineId = tokens
lineId["lineId"] = lineId.lineId
def topic_string(tokens):
topicString = tokens
topicString["topicString"] = topicString.topicString
def parenthetical_fun(tokens):
parenthetical = tokens
parenthetical["parenthetical"] = parenthetical.parenthetical
#function for splitting line at semicolon and appending numberId
#not currently in use
def split_and_prepend(tokens):
return '\n' + final.lineId
#setting parse actions
lineId.setParseAction(id_number)
topicString.setParseAction(topic_string)
parenthetical.setParseAction(parenthetical)
#reads each line in the input file
#calls the grammar expressed in 'expr' and uses it to read the line and assign names to the tokens for later use
#calls the 'notid' varient to easily return the other elements in the line aside from the lineId
#applies the format tree function and joins the tokens in a comma-separated string
#prints the lineId + the tokens from that line
for line in input:
final = delimitedList(expr).parseString(line)
notid = delimitedList(notid).parseString(line)
dash_tags = ', '.join(format_tree(notid))
print final.lineId + ": " + dash_tags
The input file is a txt document with the following two lines:
1768 dummy; data
1768 dummy data; price

Reassigning of notid breaks the second iteration when used in delimitedList. Your third line destroys the notid expression defined earlier in the code, so it will only work the first iteration. Use a different name for the notid assignment.

Related

Using function returns with None

Write a function file_in_english(filename, character_limit) that takes a filename (as a str) and a character_limit (as an int). The filename is the name of the file to convert from Cat Latin to English and the character limit is the maximum number of characters that can be converted. The limit is on the total number of characters in the output (including newline characters).
The function should return a string that contains all the converted lines in the same order as the file - remember the newline character at the end of each line (that is make sure you include a newline character at the end of each converted line so that it is included in the line's length).
If the limit is exceeded (ie, a converted sentence would take the output over the limit) then the sentence that takes the character count over the limit shouldn't be added to the output. A line with "<>" should be added at the end of the output instead. The processing of lines should then stop.
The lines in the file will each be a sentence in Weird Latin and your program should print out the English version of each sentence
The function should keep adding sentences until it runs out of input from the file or the total number of characters printed (including spaces) exceeds the limit.
The answer must include your definition of english_sentence and its helper(s) functions - that I should have called english_word or similar.
You MUST use while in your file_in_english function.
You can only use one return statement per function.
The test file used in the examples (test1.txt) has the following data:
impleseeoow estteeoow aseceeoow
impleseeoow estteeoow aseceeoow ineleeoow 2meeoow
impleseeoow estteeoow aseceeoow ineleeoow 3meeoow
impleseeoow estteeoow aseceeoow ineleeoow 4meeoow
I program works fine except that sometimes it returns None.
def english_sentence(sentence):
"""Reverse Translation"""
consonants = 'bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'
eng_sentence = []
for coded_word in sentence.split():
if coded_word.endswith("eeoow") and (coded_word[-6] in consonants):
english_word = coded_word[-6] + coded_word[:-6]
if (coded_word[-6] == 'm') and (coded_word[0] not in consonants):
english_word = '(' + english_word + ' or ' + coded_word[:-6] + ')'
eng_sentence.append(english_word)
return " ".join(eng_sentence)
def file_in_english(filename, character_limit):
"""English File"""
newone = open(filename)
nowline = newone.readline()
characters = 0
while characters < character_limit and nowline != "":
process = nowline[0:-1]
print(english_sentence(process))
characters += len(nowline)
nowline = newone.readline()
if characters > character_limit:
return("<<Output limit exceeded>>")
ans = file_in_english('test1.txt', 20)
print(ans)
Output is:
simple test case
simple test case line (m2 or 2)
simple test case line (m3 or 3)
simple test case line (m4 or 4)
None
But I must use only one return statement in each function. How can I do that for the second function and avoid the "None" in output?
You're doing the same thing as:
def f():
print('hello')
print(f())
So basically narrows down to:
print(print('hello world'))
Also btw:
>>> type(print('hello'))
hello
<class 'NoneType'>
>>>
To solve your code do:
def file_in_english(filename, character_limit):
s=""
"""English File"""
newone = open(filename)
nowline = newone.readline()
characters = 0
while characters < character_limit and nowline != "":
process = nowline[0:-1]
s+=english_sentence(process)+'\n'
characters += len(nowline)
nowline = newone.readline()
if characters > character_limit:
s+="<<Output limit exceeded>>"
return s
ans = file_in_english('test1.txt', 20)
print(ans)
You have to make sure, that any function that should return something, does this for ALL ways that your function can end.
Your function file_in_english only returns something for the case if characters > character_limit:
If charachter == or charachter < character_limit this is not the case, the function returns nothing explicitly.
Any function that does not return something from it on end, implicitly returns None when it returns to its caller.
def something(boolean):
"""Function that only returns something meaninfull if boolean is True."""
if boolean:
return "Wow"
print(something(True)) # returns Wow
print(something(False)) # implicitly returns/prints None
You can find this fact f.e. in the python tutorial:
Coming from other languages, you might object that fib is not a
function but a procedure since it doesn’t return a value. In fact,
even functions without a return statement do return a value, albeit a
rather boring one. This value is called None (it’s a built-in name).
Writing the value None is normally suppressed by the interpreter if it
would be the only value written. You can see it if you really want to
using print():
Source:https://docs.python.org/3.7/tutorial/controlflow.html#defining-functions - just short after the 2nd green example box

How to use pyparsing LineStart?

I'm trying to use pyparsing to parse key:value pairs from the comments in a document. A key starts at the beginning of a line, and a value follows. Values may be continued on multiple lines that begin with whitespace.
import pyparsing as pp
instring = """
-- This is (a) #%^& comment
/*
name1: val
name2: val2 with $*&##) junk
name3: val3: with #)(*% multi-
line: content
*/
"""
comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()
identifier = pp.Word(pp.alphanums + "_").setDebug()
meta1 = pp.LineStart() + identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd())
meta2 = pp.LineStart() + pp.White() + pp.SkipTo(pp.LineEnd())
metaval = meta1 + pp.ZeroOrMore(meta2)
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.OneOrMore(metaval) + pp.Literal("*/")
if __name__ == "__main__":
p = metalist.parseString(instring)
print(p)
Fails with:
Matched {Empty SkipTo:(LineEnd) Empty} -> ['This is (a) #%^& comment']
File "C:\Users\user\py3\lib\site-packages\pyparsing.py", line 2305, in parseImpl
raise ParseException(instring, loc, self.errmsg, self)
pyparsing.ParseException: Expected start of line (at char 32), (line:4, col:1)
The answer to pyparsing whitespace match issues says
LineStart has always been difficult to work with, but ...
If the parser is at line 4 column 1 (the first key:value pair), then why is it not finding a start of line? What is the correct pyparsing syntax to recognize lines beginning with no whitespace and lines beginning with whitespace?
I think the confusion I have with LineStart is that, for LineEnd, I can look for a '\n' character, but there is no separate character for LineStart. So in LineStart I look to see if the current parser location is positioned just after a '\n'; or if it is currently on a '\n', move past it and still continue. Unfortunately, I implemented this in a place that messes up the reporting location, so you get those weird errors that read like "failed to find a start of line on line X col 1," which really does sound like it should be a successfully matched start of a line. Also, I think I need to revisit this implicit newline-skipping, or for that matter, all whitespace-skipping in general for LineStart.
For now, I've gotten your code to work by expanding your line-starting expression slightly, as:
LS = pp.Optional(pp.LineEnd()) + pp.LineStart()
and replaced the LineStart references in meta1 and meta2 with LS:
comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()
identifier = pp.Word(pp.alphanums + "_").setDebug()
meta1 = LS + identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd())
meta2 = LS + pp.White() + pp.SkipTo(pp.LineEnd())
metaval = meta1 + pp.ZeroOrMore(meta2)
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.OneOrMore(metaval) + pp.Literal("*/")
If this situation with LineStart leaves you uncomfortable, here is another tactic you can try: using a parse-time condition to only accept identifiers that start in column 1:
comment1 = pp.Literal("--") + pp.originalTextFor(pp.SkipTo(pp.LineEnd())).setDebug()
identifier = pp.Word(pp.alphanums + "_").setName("identifier")
identifier.addCondition(lambda instring,loc,toks: pp.col(loc,instring) == 1)
meta1 = identifier + pp.Literal(":") + pp.SkipTo(pp.LineEnd()).setDebug()
meta2 = pp.White().setDebug() + pp.SkipTo(pp.LineEnd()).setDebug()
metaval = meta1 + pp.ZeroOrMore(meta2, stopOn=pp.Literal('*/'))
metalist = pp.ZeroOrMore(comment1) + pp.Literal("/*") + pp.LineEnd() + pp.OneOrMore(metaval) + pp.Literal("*/")
This code does away with LineStart completely, while I figure out just what I want this particular token to do. I also had to modify the ZeroOrMore repetition in metaval so that */ would not be accidentally processed as continued comment content.
Thanks for your patience with this - I am not keen to quickly put out a patched LineStart change and then find that I have overlooked other compatibility or other edge cases that just put me back in the current less-than-great state on this class. But I'll put some effort into clarifying this behavior before putting out 2.1.10.

Writing multiple values in a text file using python

I want to write mulitiple values in a text file using python.
I wrote the following line in my code:
text_file.write("sA" + str(chart_count) + ".Name = " + str(State_name.groups())[2:-3] + "\n")
Note: State_name.groups() is a regex captured word. So it is captured as a tuple and to remove the ( ) brackets from the tuple I have used string slicing.
Now the output comes as:
sA0.Name = GLASS_OPEN
No problem here
But I want the output to be like this:
sA0.Name = 'GLASS_HATCH_OPENED_PROTECTION_FCT'
I want the variable value to be enclosed inside the single quotes.
Does this work for you?
text_file.write("sA" + str(chart_count) + ".Name = '" + str(State_name.groups())[2:-3] + "'\n")
# ^single quote here and here^

pyparsing conditional parser

I need to parse the following three lines:
Uptime is 1w2d
Last reset at 23:05:56
Reason: reload
But last two lines are not always there, output could look like this prior to 1st reboot:
Uptime is 1w2d
Last reset
My parser looks like this:
parser = SkipTo(Literal('is'), include=True)('uptime') +
delimitedList(Suppress(SkipTo(Literal('at'), include=True))'(reset)' +
SkipTo(Literal(':'), include=true) +
SkipTo(lineEnd)('reason'), combine=True)
)
It works in first case with 3 lines, but doesnt work with second case.
I will use for the file that you've reported this syntax (supposing that the order is relevant):
from pyparsing import Literal, Word, alphanums, nums, alphas, Optional, delimitedList
def createParser():
firstLine = Literal('Uptime is') + Word(alphanums)
secLine = Literal('Last reset at') + delimitedList(Word(nums) + Literal(':') + Word(nums) + Literal(':') + Word(nums))
thirdLine = Literal('Reason:') + Word(alphas)
return firstLine + secLine + Optional(thirdLine)
if __name__ == '__main__':
parser = createParser()
firstText = """Uptime is 1w2d\n
Last reset at 23:05:56\n
Reason: reload"""
print(parser.parseString(firstText))
Declaring a parsing element optional you are able to let the parser skip it when it is not present, without raising any errors.

Replace single quotes with double quotes in python, for use with insert into database

Was wondering whether anyone has a clever solution for fixing bad
insert statements in Python, exported by a not so clever program. It didn't add
two single quotes for strings with a single quote in the string. To
make it a bit easier all the values being inserted are strings.
So it has:
INSERT INTO addresses VALUES ('1','1','CUCKOO'S NEST','CUCKOO'S NEST STREET');
instead of:
INSERT INTO addresses VALUES ('1','1','CUCKOO''S NEST','CUCKOO''S NEST STREET');
Obviously there are multiple lines of this and I don't want to replace
the enclosing single quotes as well.
Was thinking of using split and join, but I'm not sure how to easily update the split values while looping in a loop. Sorry I'm a noob. Something like the below, where I'm not sure how to do #update bit
import sys
fileIN = open('a.sql', "r")
line = fileIN.readline()
while line:
bits = line.split("','")
for bit in bits:
if bit.find("'") > -1:
#update bit
line_out = "','".join(bits)
sys.stdout.write(line_out)
line = fileIN.readline()
Thanks
Based on katrielalex's suggestion, how about this:
>>> import re
>>> s = "INSERT INTO addresses VALUES ('1','1','CUCKOO'S NEST','CUCKOO'S NEST STREET');"
>>> def repl(m):
if m.group(1) in ('(', ',') or m.group(2) in (',', ')'):
return m.group(0)
return m.group(1) + "''" + m.group(2)
>>> re.sub("(.)'(.)", repl, s)
"INSERT INTO addresses VALUES ('1','1','CUCKOO''S NEST','CUCKOO''S NEST STREET');"
and if you're into negative lookbehind assertions, this is the headache inducing pure regex version:
re.sub("((?<![(,])'(?![,)]))", "''", s)
while line:
# Restrain line2 to inside parentheses
line1, rest = line.split('(')
line2, line3 = rest.split(')')
# A bit more cleaner
new_bits = []
for bit in line2.split(','):
# Remove border ' characters
bit = bit[1:-1]
# Duplicate the ones inside
if "'" in bit:
bit = bit.replace("'", "''")
# Re-add border '
new_bits.append("'" + bit + "'")
sys.stdout.write(line1 + '(' + ','.join(new_bits + ')' + line3)
line = fileIN.readline()
Warning: This depends way too much on the formatting of the SQL statement. However, if your input is only ever going to have the format "statements (params) end" then this will work every time.
import sys
fileIN = open('a.sql', "r")
line = fileIN.readline()
while line:
#split out the parameters (between the ()'s)
start, temp = line.split("(")
params, end = temp.split(")")
#replace the "'"s in the parameters (without the start and end quote)
newParams = "','".join([x.replace("'", "''") for x in params[1:-1].split("','")])
#join the statement back together
line_out = start + "('" + newParams + "')" + end
#next line
sys.stdout.write(line_out)
line = fileIN.readline()
Explanation:
Split the string into 3 parts: The query start, the parameters, and the end.
The generator takes the parameters (without the starting/ending 's), splits it on ',', and, for every element in the list the split generates (the individual data entries), replaces the 's with ''s.
The last line then joins the query start, the new params (with the parenthesis and quotes that were removed previously), and the end of the statement.
Another answer:
a = "INSERT INTO addresses VALUES ('1','1','CUCKOO'S NEST','CUCKOO'S NEST STREET');"
open_par = a.find("(")
close_par = a.find(")")
b = a[open_par+1:close_par]
c = b.split(",")
d = map(lambda x: '"' + x.strip().strip("'") + '"',c)
result = a[:open_par+1] + ",".join(d) + a[close_par:]
Went with:
import sys
import re
def repl(m):
if m.group(1) in ('(', ',') or m.group(2) in (',', ')'):
return m.group(0)
return m.group(1) + "''" + m.group(2)
fileIN = open('a.sql', "r")
line = fileIN.readline()
while line:
line_out = re.sub("(.)'(.)", repl, line)
sys.stdout.write(line_out)
# Next line.
line = fileIN.readline()

Categories