Bug report
Bug description:
Hi! Using the csv module - when determining the delimiter in the _guess_delimiter method, it checks the "consistency" (comparison of counts of a character between rows) of a character. It does reading chunks of 10 lines.
So for the first chunk, when a ';' appears exactly e.g. 30 times on every row - the consistency is 100% - it passes straight away and ';' is recognised as the delimiter. When it appears 30 times on 9 rows and 29 times on 1 row, the consistency is 90%.
The bug is as follows: the required consistency starts at 100% and is lowered by 1% for some iterations until it reaches the minimum threshold of 90%. Only due to floating point rounding errors the consistency becomes 0.8999999, therefore missing the last iteration.
consistency=1.0# minimum consistency thresholdthreshold=0.9whilelen(delims) ==0andconsistency>=threshold:
fork, vinmodeList:
ifv[0] >0andv[1] >0:
if ((v[1]/total) >=consistencyand
(delimitersisNoneorkindelimiters)):
delims[k] =vconsistency-=0.01
Image with "proof":

Whole method:
def_guess_delimiter(self, data, delimiters):
""" The delimiter /should/ occur the same number of times on each row. However, due to malformed data, it may not. We don't want an all or nothing approach, so we allow for small variations in this number. 1) build a table of the frequency of each character on every line. 2) build a table of frequencies of this frequency (meta-frequency?), e.g. 'x occurred 5 times in 10 rows, 6 times in 1000 rows, 7 times in 2 rows' 3) use the mode of the meta-frequency to determine the /expected/ frequency for that character 4) find out how often the character actually meets that goal 5) the character that best meets its goal is the delimiter For performance reasons, the data is evaluated in chunks, so it can try and evaluate the smallest portion of the data possible, evaluating additional chunks as necessary. """data=list(filter(None, data.split('\n')))
ascii= [chr(c) forcinrange(127)] # 7-bit ASCII# build frequency tableschunkLength=min(10, len(data))
iteration=0charFrequency= {}
modes= {}
delims= {}
start, end=0, chunkLengthwhilestart<len(data):
iteration+=1forlineindata[start:end]:
forcharinascii:
metaFrequency=charFrequency.get(char, {})
# must count even if frequency is 0freq=line.count(char)
# value is the modemetaFrequency[freq] =metaFrequency.get(freq, 0) +1charFrequency[char] =metaFrequencyforcharincharFrequency.keys():
items=list(charFrequency[char].items())
iflen(items) ==1anditems[0][0] ==0:
continue# get the mode of the frequenciesiflen(items) >1:
modes[char] =max(items, key=lambdax: x[1])
# adjust the mode - subtract the sum of all# other frequenciesitems.remove(modes[char])
modes[char] = (modes[char][0], modes[char][1]
-sum(item[1] foriteminitems))
else:
modes[char] =items[0]
# build a list of possible delimitersmodeList=modes.items()
total=float(min(chunkLength*iteration, len(data)))
# (rows of consistent data) / (number of rows) = 100%consistency=1.0# minimum consistency thresholdthreshold=0.9whilelen(delims) ==0andconsistency>=threshold:
fork, vinmodeList:
ifv[0] >0andv[1] >0:
if ((v[1]/total) >=consistencyand
(delimitersisNoneorkindelimiters)):
delims[k] =vconsistency-=0.01iflen(delims) ==1:
delim=list(delims.keys())[0]
skipinitialspace= (data[0].count(delim) ==data[0].count("%c "%delim))
return (delim, skipinitialspace)
# analyze another chunkLength linesstart=endend+=chunkLengthifnotdelims:
return ('', 0)
# if there's more than one, fall back to a 'preferred' listiflen(delims) >1:
fordinself.preferred:
ifdindelims.keys():
skipinitialspace= (data[0].count(d) ==data[0].count("%c "%d))
return (d, skipinitialspace)
# nothing else indicates a preference, pick the character that# dominates(?)items= [(v,k) for (k,v) indelims.items()]
items.sort()
delim=items[-1][1]
skipinitialspace= (data[0].count(delim) ==data[0].count("%c "%delim))
return (delim, skipinitialspace)CPython versions tested on:
3.10
Operating systems tested on:
macOS
Linked PRs
Bug report
Bug description:
Hi! Using the csv module - when determining the delimiter in the
_guess_delimitermethod, it checks the "consistency" (comparison of counts of a character between rows) of a character. It does reading chunks of 10 lines.So for the first chunk, when a ';' appears exactly e.g. 30 times on every row - the consistency is 100% - it passes straight away and ';' is recognised as the delimiter. When it appears 30 times on 9 rows and 29 times on 1 row, the consistency is 90%.
The bug is as follows: the required consistency starts at 100% and is lowered by 1% for some iterations until it reaches the minimum threshold of 90%. Only due to floating point rounding errors the consistency becomes 0.8999999, therefore missing the last iteration.
Image with "proof":
Whole method:
CPython versions tested on:
3.10
Operating systems tested on:
macOS
Linked PRs