有多个SO问题可以解决这个主题的某些形式,但是从csv文件中只删除一行(通常它们涉及复制整个文件),它们似乎都非常低效。如果我有一个像这样的csv格式:
fname,lname,age,sex
John,Doe,28,m
Sarah,Smith,27,f
Xavier,Moore,19,m
删除Sarah行的最有效方法是什么?如果可能的话,我想避免复制整个文件。
答案 0 :(得分:27)
这里有一个根本问题。没有当前的文件系统(我知道)提供了从文件中间删除一堆字节的工具。您可以覆盖现有字节,也可以写入新文件。所以,你的选择是:
\0
)。如果你想要完全通用,这不是CSV文件的选项,因为没有定义的注释字符。如果您尝试删除第一行,最后一个选项显然无济于事(但如果您想要删除末尾附近的行,则很方便)。它也非常容易在整个过程中崩溃。
答案 1 :(得分:3)
这是一种方式。你必须将文件的其余部分加载到缓冲区,但它是我能想到的最好的Python:
with open('afile','r+') as fd:
delLine = 4
for i in range(delLine):
pos = fd.tell()
fd.readline()
rest = fd.read()
fd.seek(pos)
fd.truncate()
fd.write(rest)
fd.close()
我解决了这个问题,就好像你知道行号一样。如果你想检查文本而不是上面的循环:
pos = fd.tell()
while fd.readline().startswith('Sarah'): pos = fd.tell()
如果Sarah'将会有例外情况。还没有找到。
如果您要删除的行更接近末尾,这可能更有效,但是我不确定读取所有内容,丢弃行并将其转储回来将比用户时间节省更多(考虑到这是一个Tk app)。这也只需要打开并刷新一次文件一次,所以除非文件非常长,并且Sarah真的很远,它可能不会引人注意。
答案 2 :(得分:3)
就地编辑文件是一个充满陷阱的任务(就像在迭代迭代时修改一个迭代一样)并且通常不值得麻烦。在大多数情况下,写入临时文件(或工作内存,取决于你拥有的更多 - 存储空间或RAM)然后删除源文件并用临时文件替换源文件将同样尝试执行同样的事情。
但是,如果你坚持,这是一个通用的解决方案:
import os
def remove_line(path, comp):
with open(path, "r+b") as f: # open the file in rw mode
mod_lines = 0 # hold the overwrite offset
while True:
last_pos = f.tell() # keep the last line position
line = f.readline() # read the next line
if not line: # EOF
break
if mod_lines: # we've already encountered what we search for
f.seek(last_pos - mod_lines) # move back to the beginning of the gap
f.write(line) # fill the gap with the current line
f.seek(mod_lines, os.SEEK_CUR) # move forward til the next line start
elif comp(line): # search for our data
mod_lines = len(line) # store the offset when found to create a gap
f.seek(last_pos - mod_lines) # seek back the extra removed characters
f.truncate() # truncate the rest
这将仅删除与提供的比较函数匹配的行,然后迭代文件的其余部分,将数据移到“已删除”行。您也不需要将文件的其余部分加载到工作内存中。要测试它,test.csv
包含:
fname,lname,age,sex John,Doe,28,m Sarah,Smith,27,f Xavier,Moore,19,m
您可以将其运行为:
remove_line("test.csv", lambda x: x.startswith(b"Sarah"))
您将获得test.csv
就地删除Sarah
行:
fname,lname,age,sex John,Doe,28,m Xavier,Moore,19,m
请记住,我们正在传递bytes
比较函数,因为文件以二进制模式打开,以便在截断/覆盖时保持一致的换行符。
更新:我对此处介绍的各种技术的实际性能感兴趣但我昨天没有时间对它们进行测试,所以有点延迟我创建了一个基准测试这可以解释一下。如果您只对结果感兴趣,请向下滚动。首先,我将解释我的基准测试以及如何设置测试。我还将提供所有脚本,以便您可以在系统中运行相同的基准测试。
至于什么,我已经在这个和其他答案中测试了所有提到的技术,即使用临时文件(temp_file_*
函数)替换行并使用就地编辑(in_place_*
) 功能。我将这两个设置为流式传输(逐行读取,*_stream
函数)和内存(读取工作内存中的其余文件,*_wm
函数)模式。我还使用mmap
模块(in_place_mmap
函数)添加了就地行删除技术。包含所有功能的基准测试脚本以及通过CLI控制的一小部分逻辑如下:
#!/usr/bin/env python
import mmap
import os
import shutil
import sys
import time
def get_temporary_path(path): # use tempfile facilities in production
folder, filename = os.path.split(path)
return os.path.join(folder, "~$" + filename)
def temp_file_wm(path, comp):
path_out = get_temporary_path(path)
with open(path, "rb") as f_in, open(path_out, "wb") as f_out:
while True:
line = f_in.readline()
if not line:
break
if comp(line):
f_out.write(f_in.read())
break
else:
f_out.write(line)
f_out.flush()
os.fsync(f_out.fileno())
shutil.move(path_out, path)
def temp_file_stream(path, comp):
path_out = get_temporary_path(path)
not_found = True # a flag to stop comparison after the first match, for fairness
with open(path, "rb") as f_in, open(path_out, "wb") as f_out:
while True:
line = f_in.readline()
if not line:
break
if not_found and comp(line):
continue
f_out.write(line)
f_out.flush()
os.fsync(f_out.fileno())
shutil.move(path_out, path)
def in_place_wm(path, comp):
with open(path, "r+b") as f:
while True:
last_pos = f.tell()
line = f.readline()
if not line:
break
if comp(line):
rest = f.read()
f.seek(last_pos)
f.write(rest)
break
f.truncate()
f.flush()
os.fsync(f.fileno())
def in_place_stream(path, comp):
with open(path, "r+b") as f:
mod_lines = 0
while True:
last_pos = f.tell()
line = f.readline()
if not line:
break
if mod_lines:
f.seek(last_pos - mod_lines)
f.write(line)
f.seek(mod_lines, os.SEEK_CUR)
elif comp(line):
mod_lines = len(line)
f.seek(last_pos - mod_lines)
f.truncate()
f.flush()
os.fsync(f.fileno())
def in_place_mmap(path, comp):
with open(path, "r+b") as f:
stream = mmap.mmap(f.fileno(), 0)
total_size = len(stream)
while True:
last_pos = stream.tell()
line = stream.readline()
if not line:
break
if comp(line):
current_pos = stream.tell()
stream.move(last_pos, current_pos, total_size - current_pos)
total_size -= len(line)
break
stream.flush()
stream.close()
f.truncate(total_size)
f.flush()
os.fsync(f.fileno())
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: {} target_file.ext <search_string> [function_name]".format(__file__))
exit(1)
target_file = sys.argv[1]
search_func = globals().get(sys.argv[3] if len(sys.argv) > 3 else None, in_place_wm)
start_time = time.time()
search_func(target_file, lambda x: x.startswith(sys.argv[2].encode("utf-8")))
# some info for the test runner...
print("python_version: " + sys.version.split()[0])
print("python_time: {:.2f}".format(time.time() - start_time))
下一步是构建一个测试人员,在尽可能隔离的环境中运行这些功能,试图为每个功能获得公平的基准。我的测试结构如下:
chrt -f 99
)到/usr/bin/time
进行测试以进行基准测试,因为在这样的场景中,Python无法真正被信任以准确测量其性能。不幸的是,我手边没有一个系统可以完全隔离测试,所以我的数字是通过在虚拟机管理程序中运行获得的。这意味着I / O性能可能非常偏差,但它应该同样会影响仍然提供可比数据的所有测试。无论哪种方式,欢迎您在自己的系统上运行此测试,以获得可以与之相关的结果。
我已经设置了一个执行上述场景的测试脚本:
#!/usr/bin/env python
import collections
import os
import random
import shutil
import subprocess
import sys
import time
try:
range = xrange # cover Python 2.x
except NameError:
pass
try:
DEV_NULL = subprocess.DEVNULL
except AttributeError:
DEV_NULL = open(os.devnull, "wb") # cover Python 2.x
SAMPLE_ROWS = 10**6 # 1M lines
TEST_LOOPS = 3
CALL_SCRIPT = os.path.join(os.getcwd(), "remove_line.py") # the above script
def get_temporary_path(path):
folder, filename = os.path.split(path)
return os.path.join(folder, "~$" + filename)
def generate_samples(path, data="LINE", rows=10**6, columns=10): # 1Mx10 default matrix
sample_beginning = os.path.join(path, "sample_beg.csv")
sample_middle = os.path.join(path, "sample_mid.csv")
sample_end = os.path.join(path, "sample_end.csv")
separator = os.linesep
middle_row = rows // 2
with open(sample_beginning, "w") as f_b, \
open(sample_middle, "w") as f_m, \
open(sample_end, "w") as f_e:
f_b.write(data)
f_b.write(separator)
for i in range(rows):
if not i % middle_row:
f_m.write(data)
f_m.write(separator)
for t in (f_b, f_m, f_e):
t.write(",".join((str(random.random()) for _ in range(columns))))
t.write(separator)
f_e.write(data)
f_e.write(separator)
return ("beginning", sample_beginning), ("middle", sample_middle), ("end", sample_end)
def normalize_field(field):
field = field.lower()
while True:
s_index = field.find('(')
e_index = field.find(')')
if s_index == -1 or e_index == -1:
break
field = field[:s_index] + field[e_index + 1:]
return "_".join(field.split())
def encode_csv_field(field):
if isinstance(field, (int, float)):
field = str(field)
escape = False
if '"' in field:
escape = True
field = field.replace('"', '""')
elif "," in field or "\n" in field:
escape = True
if escape:
return ('"' + field + '"').encode("utf-8")
return field.encode("utf-8")
if __name__ == "__main__":
print("Generating sample data...")
start_time = time.time()
samples = generate_samples(os.getcwd(), "REMOVE THIS LINE", SAMPLE_ROWS)
print("Done, generation took: {:2} seconds.".format(time.time() - start_time))
print("Beginning tests...")
search_string = "REMOVE"
header = None
results = []
for f in ("temp_file_stream", "temp_file_wm",
"in_place_stream", "in_place_wm", "in_place_mmap"):
for s, path in samples:
for test in range(TEST_LOOPS):
result = collections.OrderedDict((("function", f), ("sample", s),
("test", test)))
print("Running {function} test, {sample} #{test}...".format(**result))
temp_sample = get_temporary_path(path)
shutil.copy(path, temp_sample)
print(" Clearing caches...")
subprocess.call(["sudo", "/usr/bin/sync"], stdout=DEV_NULL)
with open("/proc/sys/vm/drop_caches", "w") as dc:
dc.write("3\n") # free pagecache, inodes, dentries...
# you can add more cache clearing/invalidating calls here...
print(" Removing a line starting with `{}`...".format(search_string))
out = subprocess.check_output(["sudo", "chrt", "-f", "99",
"/usr/bin/time", "--verbose",
sys.executable, CALL_SCRIPT, temp_sample,
search_string, f], stderr=subprocess.STDOUT)
print(" Cleaning up...")
os.remove(temp_sample)
for line in out.decode("utf-8").split("\n"):
pair = line.strip().rsplit(": ", 1)
if len(pair) >= 2:
result[normalize_field(pair[0].strip())] = pair[1].strip()
results.append(result)
if not header: # store the header for later reference
header = result.keys()
print("Cleaning up sample data...")
for s, path in samples:
os.remove(path)
output_file = sys.argv[1] if len(sys.argv) > 1 else "results.csv"
output_results = os.path.join(os.getcwd(), output_file)
print("All tests completed, writing results to: " + output_results)
with open(output_results, "wb") as f:
f.write(b",".join(encode_csv_field(k) for k in header) + b"\n")
for result in results:
f.write(b",".join(encode_csv_field(v) for v in result.values()) + b"\n")
print("All done.")
最后(和 TL; DR ):这是我的结果 - 我只从结果集中提取最佳时间和内存数据,但您可以在此处获取完整的结果集:{{ 3}}和Python 2.7 Raw Test Data。
根据我收集的数据,最后一些注意事项:
*_stream
功能提供的占用空间很小。在Python 3.x中,中间是mmap
技术。in_place_*
功能可行。in_place_stream
,但代价是处理时间和增加的I / O调用(与*_wm
函数相比)。in_place_*
函数很危险,因为如果它们在中途停止,可能会导致数据损坏。 temp_file_*
函数(没有完整性检查)仅在非事务性文件系统上存在危险。答案 3 :(得分:3)
使用sed:
sed -ie "/Sahra/d" your_file
编辑,抱歉,我没有完全阅读有关使用python的所有标签和评论。无论哪种方式,我都可能尝试使用一些shell-utility进行一些预处理来解决它,以避免在其他答案中提出所有额外的代码。但既然我不完全了解你的问题,那可能是不可能的?
祝你好运!答案 4 :(得分:2)
你可以用熊猫来做。如果您的数据保存在data.csv下,则以下内容应该有所帮助:
import pandas as pd
df = pd.read_csv('data.csv')
df = df[df.fname != 'Sarah' ]
df.to_csv('data.csv', index=False)
答案 5 :(得分:1)
删除Sarah行的最有效方法是什么?如果可能的话,我想避免复制整个文件。
最有效的方法是使用csv解析器忽略的内容覆盖该行。这样可以避免在删除行之后移动行。
如果您的csv解析器可以忽略空行,请用\n
符号覆盖该行。否则,如果解析器从值中删除空格,则使用(空格)符号覆盖该行。
答案 6 :(得分:0)
这可能会有所帮助:
with open("sample.csv",'r') as f:
for line in f:
if line.startswith('sarah'):continue
print(line)