Sous Linux, il existe une commande appelée «tail» qui vous permet d'obtenir la ligne «n» à la fin du fichier. C'est assez pratique donc je veux pouvoir faire la même chose avec Python.
Je voudrais créer une fonction qui récupère n lignes à la fin du fichier avec tail (file_name, n)
en utilisant plusieurs approches.
Pour la dernière approche, allez sur le site it-swarm.dev Trouvez efficacement la dernière ligne du fichier texte -Je me réfère à la page swarm.dev/ja/python/ Trouver efficacement la dernière ligne du fichier texte / 940298444 /).
Le fichier à lire pourrait être un fichier texte, mais cette fois j'utiliserai le fichier csv
.
Le nom du fichier est «test.csv». Le contenu est un résumé du prix du Bitcoin en 86400 lignes (une journée) par seconde.
test.csv
date,price,size
1588258800,933239.0,3.91528007
1588258801,933103.0,3.91169431
1588258802,932838.0,2.91
1588258803,933217.0,0.5089811
(Omission)
1588345195,955028.0,0.0
1588345196,954959.0,0.05553
1588345197,954984.0,1.85356
1588345198,955389.0,10.91445135
1588345199,955224.0,3.61106
Bien que cela n'ait rien à voir avec le sujet principal, si vous expliquez chaque élément pour le moment, les unités de date, de prix et de taille sont UnixTime, YEN, BTC. La première ligne signifie qu'au moment «1588258800», c'est-à-dire à 0:00:00 le 1er mai, il y avait des pièces de «3.915280007» achetées et vendues pour «933239.0» yens.
Premièrement, utilisez la fonction intégrée ʻopen () `pour obtenir l'objet fichier, lire toutes les lignes depuis le début et afficher uniquement les n dernières lignes. Si n est 0 ou un entier négatif, des résultats étranges seront obtenus, il est donc en fait nécessaire d'effectuer un traitement limité aux nombres naturels, mais c'est important pour la visibilité.
python
def tail(fn, n):
#Ouvrez le fichier et récupérez toutes les lignes dans une liste
with open(fn, 'r') as f:
#Lire une ligne.La première ligne est l'en-tête, alors supprimez le résultat
f.readline()
#Lire toutes les lignes
lines = f.readlines()
#Renvoie seulement n lignes de l'arrière
return lines[-n:]
#résultat
file_name = 'test.csv'
tail(file_name, 3)
# ['1588345197,954984.0,1.85356\n',
# '1588345198,955389.0,10.91445135\n',
# '1588345199,955224.0,3.61106\n']
S'il s'agit d'un fichier texte, vous pouvez le laisser tel quel, mais rendez-le un peu plus facile à utiliser pour les fichiers csv.
python
def tail(fn, n):
#Ouvrez le fichier et récupérez toutes les lignes dans une liste
with open(fn, 'r') as f:
f.readline()
lines = f.readlines()
#Renvoie une chaîne après en avoir fait un tableau.Au fait str->Conversion de type en float
return [list(map(float ,line.strip().split(','))) for line in lines[-n:]]
#résultat
tail(file_name, 3)
# [[1588345197.0, 954984.0, 1.85356],
# [1588345198.0, 955389.0, 10.91445135],
# [1588345199.0, 955224.0, 3.61106]]
La seule chose qui a changé est la ligne return
, mais comme les fonctions sont encombrées et difficiles à comprendre, je vais l'expliquer en un mot.
Le traitement suivant est effectué pour chaque ligne.
strip ()
'1588345197,954984.0,1.85356\n'
-> '1588345197,954984.0,1.85356'
split ()
'1588345197,954984.0,1.85356'
-> ['1588345197', '954984.0', '1.85356']
map ()
['1588345197', '954984.0', '1.85356']
-> [1588345197.0, 954984.0, 1.85356]
Puisque le module csv convertit automatiquement chaque ligne en un tableau, ce sera un peu plus lent, mais cela peut être décrit de manière plus concise.
python
import csv
def tail_csv(fn, n):
with open(fn) as f:
#Convertir l'objet fichier en lecteur csv
reader = csv.reader(f)
#Jeter l'en-tête
next(reader)
#Lire toutes les lignes
rows = [row for row in reader]
#Flotter uniquement les n dernières lignes et retourner
return [list(map(float, row)) for row in rows[-n:]]
Puisque les pandas ont une fonction de queue, il est étonnamment facile à écrire.
python
import pandas as pd
def tail_pd(fn, n):
df = pd.read_csv(fn)
return df.tail(n).values.tolist()
Puisque les pandas traitent des tableaux numpy, il est converti en une liste à la fin avec tolist ()
. Ce n'est pas nécessaire si le tableau numpy peut être utilisé.
ʻIpython a une commande pratique appelée
timeit`, alors comparons-la avec le nombre de boucles défini sur 100.
timeit -n100 tail('test.csv', 3)
18.8 ms ± 175 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
timeit -n100 tail_csv('test.csv', 3)
67 ms ± 822 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
timeit -n100 tail_pd('test.csv', 3)
30.4 ms ± 2.45 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
Il s'est avéré qu'il était rapide à lire tel quel sans utiliser aucun module. Cospa semble être le meilleur car pandas est la simplicité du code et la vitesse est raisonnable. Étant donné que le module csv convertit volontairement d'une chaîne de caractères en un tableau jusqu'aux lignes non utilisées, les résultats sont extrêmement médiocres.
Toutes les approches jusqu'à présent lisent toutes les lignes après tout, mais comme je ne veux que les dernières lignes, s'il y a un moyen de lire le fichier depuis le dos, la lecture devrait être terminée en un instant.
Reportez-vous à la page [Trouvez efficacement la dernière ligne du fichier texte](https://www.it-swarm.dev/ja/python/ Trouvez efficacement la dernière ligne du fichier texte / 940298444 /) fait.
Lisez environ 100 octets à partir du verso dans l'ordre, et si un code de saut de ligne est trouvé, la chaîne de caractères après cela est la dernière ligne. Seule la dernière ligne se trouve dans la page, mais pour exécuter la commande tail
Vous devez trouver la ligne n
à l'arrière, alors ne réglez que là-bas.
Tout d'abord, à titre de connaissance préliminaire, nous expliquerons comment faire fonctionner le pointeur de fichier.
Il y a trois fonctions à utiliser: f.tell ()
, f.read (size)
, et f.seek (offset, whence)
.
f.tell ()
retourne la position actuellement pointée par le pointeur.
f.read (size)
renvoie le contenu lu size
octets à partir de la position actuelle. Le pointeur se déplace vers la position de lecture. Il ne peut être avancé que dans le sens positif.
f.seek (offset, whence)
est une fonction qui déplace la position du pointeur.
L'argument «whence» représente la position. L'une des valeurs «0, 1, 2» est entrée. «0» est le début du fichier, «1» est la position actuelle du pointeur et «2» est la fin du fichier. Veux dire.
Entrez un entier pour ʻoffset. Contrairement à
read, vous pouvez passer une valeur négative, donc par exemple,
f.seek (-15, 1)` renvoie la position actuelle du pointeur de 15 au début.
Nous allons le mettre en œuvre sur cette base.
python
#Utiliser une division qui peut utiliser des expressions régulières
import re
def tail_b(fn, n=None):
#Si n n'est pas donné, seule la dernière ligne est renvoyée seule
if n is None:
n = 1
is_list = False
#n est un nombre naturel
elif type(n) != int or n < 1:
raise ValueError('n has to be a positive integer')
#Lorsque n est donné, n lignes sont renvoyées ensemble dans une liste.
else:
is_list = True
# 128 *Lire n octets à la fois
chunk_size = 64 * n
# seek()Se comporte de manière inattendue sauf en mode binaire'rb'Spécifier
with open(fn, 'rb') as f:
#Première ligne pour trouver la position la plus à gauche à l'exclusion de l'en-tête(Ligne d'en-tête)Je lis
f.readline()
#Le tout premier code de saut de ligne se trouve à l'extrémité gauche(Fin lors de la lecture à partir de la fin du fichier)À
# -1 est'\n'1 octet
left_end = f.tell() - 1
#Fin de fichier(2)1 octet en arrière de. read(1)À lire
f.seek(-1, 2)
#Il y a souvent des lignes vides et des blancs à la fin du fichier
#Position du dernier caractère du fichier les excluant(Extrémité droite)Trouver
while True:
if f.read(1).strip() != b'':
#Extrémité droite
right_end = f.tell()
break
#Fais un pas, alors fais deux pas vers le bas
f.seek(-2, 1)
#Nombre d'octets non lus à gauche
unread = right_end - left_end
#Nombre de lignes lues.Si cela devient n ou plus, cela signifie que n lignes ont été lues.
num_lines = 0
#Variables de connexion des chaînes d'octets lues
line = b''
while True:
#Le nombre d'octets non lus est un morceau_Quand il devient plus petit que la taille,Fraction de morceau_Taille
if unread < chunk_size:
chunk_size = f.tell() - left_end
#Morceau de votre emplacement actuel_Se déplacer vers le haut du fichier par taille
f.seek(-chunk_size, 1)
#Lisez seulement autant que vous bougez
chunk = f.read(chunk_size)
#Relier
line = chunk + line
#Depuis que j'ai recommencé avec read, chunk encore au début_déplacement de taille
f.seek(-chunk_size, 1)
#Mettre à jour le nombre d'octets non lus
unread -= chunk_size
#Si un code de saut de ligne est inclus
if b'\n' in chunk:
#Num autant que le nombre de codes de saut de ligne_Compter les lignes
num_lines += chunk.count(b'\n')
#Lire plus de n lignes,Ou lorsque le nombre d'octets non lus atteint 0, un signal de fin
if num_lines >= n or not unread:
#Dernier code de saut de ligne trouvé
leftmost_blank = re.search(rb'\r?\n', line)
#Pas besoin de la pièce avant le dernier code de saut de ligne trouvé
line = line[leftmost_blank.end():]
#Convertir une chaîne d'octets en chaîne
line = line.decode()
#Code de saut de ligne'\r\n'Ou\n'Convertir en un tableau séparé par
lines = re.split(r'\r?\n', line)
#Enfin sortez n morceaux de l'arrière,Convertir en type float et retourner
result = [list(map(float, line.split(','))) for line in lines[-n:]]
#Si n n'est pas spécifié, la dernière ligne est renvoyée seule.
if not is_list:
return result[-1]
else:
return result
L'explication est donnée dans les notes. Faisons la mesure principale du temps.
timeit -n100 tail_b(fn, 3)
87.8 µs ± 3.74 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Le meilleur temps à ce jour a été la première approche, qui était de "18,8 ms ± 175 µs". Cela signifie que le temps d'exécution est d'environ "0,5%", soit "200" fois, mais 86400 lignes depuis le début. Il est naturel qu'il y ait une grande différence car c'est la différence entre lire tout ou lire quelques lignes au verso.
J'ai introduit quatre modèles, mais il semble y avoir une autre façon d'exécuter la commande tail
du système en utilisant le module subprocess
. C'est une méthode dépendante de l'environnement, donc je l'ai omise cette fois.
La méthode la plus recommandée que j'ai introduite est celle qui peut être écrite sur deux lignes en utilisant pandas
. Python est un langage qui maîtrise comment vous pouvez vous amuser en utilisant le code de quelqu'un d'autre.
En ce qui concerne la méthode de lecture à partir de l'arrière du fichier, il est recommandé de l'utiliser lorsque vous avez besoin de vitesse ou lorsque le nombre de lignes et de caractères est ridiculement grand et que la lecture du fichier prend trop de temps depuis le début.
De plus, cela n'a aucun sens d'utiliser 64
pour déterminer taille_chunk
. Il est probablement plus rapide de le définir à environ la longueur d'une ligne dans un fichier, mais la longueur de certains fichiers varie considérablement en fonction de la ligne. Par conséquent, je ne peux rien dire.
Si vous avez affaire à un fichier qui contient quelques caractères sur une ligne courte, mais 10 000 caractères sur une longue ligne, vous devrez modifier dynamiquement chunk_size.
Par exemple, si le nombre de lignes trouvées dans une recherche est bien inférieur à n, le prochain chunk_size est doublé et doublé.
Il semble qu'il soit également efficace de déterminer le prochain chunk_size à partir du nombre de lignes qui ont été recherchées et de la longueur moyenne des lignes.
Recommended Posts