Seite 1 von 1

Größte und neuste Datei aus Verzeichnis?

Verfasst: Sonntag 20. Juli 2008, 22:51
von RedSharky
Hi,
ich habe mir den Kopf zermartert, stehe aber irgendwie auf dem Schlauch.

Ich möchte mir lediglich zwei Funktionen schreiben, die mir sagen, welches die größte oder welches die neuste Datei in einem Verzeichnis ist.

Das muss doch irgendwie über os gehen. Und dann irgendwie sortiert werden.
Das Problem ist, dass man zwar leicht nach z. B. der Dateigröße sortieren kann, aber dann nicht weiß, wie der Dateiname dazu ist.
Irgendwas mit Dictionary?

Habe mal etwas sinnfreien Code erzeugt:

Code: Alles auswählen

from os import *

dirpath = "C:/temp"

class CacheFile: 
    def __init__(self,n,s,t):    #constructor method
        self.name = n
        self.size = s
        self.time = t

chdir(dirpath) #changes working directory
print getcwd() #print current dir
print 

cache_files = listdir(dirpath) #list all files in current directory
print cache_files
print

cf = []
for i in xrange(len(cache_files)):
    cf.append(i)
print cf
print

#make class objects
for i in xrange(len(cf)):
    cf[i] = CacheFile(cache_files[i],stat(cache_files[i])[6],stat(cache_files[i])[8])

#output
for i in xrange(len(cf)):
    print cf[i].name, cf[i].size, cf[i].time
print

#sort a list
sizelist = []
for i in xrange(len(cf)):
     sizelist.append(cf[i].size)
print sizelist
print    

sizelist.sort()
print sizelist

Verfasst: Sonntag 20. Juli 2008, 23:21
von Leonidas
Wozu Klassen?

Code: Alles auswählen

import os, operator
files = os.listdir('.')
stats = [os.stat(f) for f in files]
ctimes = [stat.st_ctime for stat in stats]
sizes = [stat.st_size for stat in stats]
combined = zip(files, ctimes, sizes)
# newest file
print reversed(sorted(combined, key=operator.itemgetter(1))).next()
# largest file
print reversed(sorted(combined, key=operator.itemgetter(2))).next()

Verfasst: Sonntag 20. Juli 2008, 23:36
von BlackJack
Ein bisschen umständlich. Sowohl `list.sort()`, als auch `sorted()` kennen das Argument `reverse` und ab Python 2.5 (inklusive) kann man sich das sortieren sparen, weil `min()` und `max()` dann auch das `key`-Argument kennen und einfach linear das Gewünschte suchen.

Verfasst: Sonntag 20. Juli 2008, 23:54
von veers
Leonidas hat geschrieben:Wozu Klassen?
Weil named tuples erst mit 2.6 kommen?

Verfasst: Montag 21. Juli 2008, 00:06
von snafu
veers hat geschrieben:
Leonidas hat geschrieben:Wozu Klassen?
Weil named tuples erst mit 2.6 kommen?
Ich glaube, Leonidas wollte damit eher seinen alternativen Lösungsvorschlag kommentieren, der eben ohne Klassen auskommt.

Verfasst: Montag 21. Juli 2008, 00:29
von Leonidas
Vereinfacht. Tatsächlich habe ich ``min()`` und ``max()`` vergessen gehabt. Aber gut zu wissen, dass die auch ein ``key``-Argument haben.

Code: Alles auswählen

import os, operator
files = os.listdir('.')
stats = [os.stat(f) for f in files]
ctimes = [stat.st_ctime for stat in stats]
sizes = [stat.st_size for stat in stats]
combined = zip(files, ctimes, sizes)
# newest file
print max(combined, key=operator.itemgetter(1))
# largest file
print max(combined, key=operator.itemgetter(2))

Verfasst: Montag 21. Juli 2008, 07:23
von veers
snafu hat geschrieben:
veers hat geschrieben:
Leonidas hat geschrieben:Wozu Klassen?
Weil named tuples erst mit 2.6 kommen?
Ich glaube, Leonidas wollte damit eher seinen alternativen Lösungsvorschlag kommentieren, der eben ohne Klassen auskommt.
Ja, an Stelle derer Verwendet er Tuples und Magic Numbers:

Code: Alles auswählen

key=operator.itemgetter(1)
Leonidas, warum
stats = [os.stat(f) for f in files] und nicht stats = map(os.stat, files)?

Verfasst: Montag 21. Juli 2008, 10:35
von BlackJack
Ich will auch mal:

Code: Alles auswählen

import os
from functools import partial
from itertools import imap

def main():
    filenames = os.listdir('.')
    tmp = zip(filenames, imap(os.stat, filenames))
    
    def key_func(attr_name, (filename, stats)):
        return getattr(stats, attr_name)
    
    for stat_attr in ('st_ctime', 'st_size'):
        print max(tmp, key=partial(key_func, stat_attr))
Und noch mal mit den `path` Modul:

Code: Alles auswählen

from path import path

def methodcaller(method_name, *args, **kwargs):
    return lambda o: getattr(o, method_name)(*args, **kwargs)

def main():
    files = path('.').listdir()
    for method_name in ('getctime', 'getsize'):
        print max(files, key=methodcaller(method_name))

Verfasst: Montag 21. Juli 2008, 12:00
von Leonidas
veers hat geschrieben:Ja, an Stelle derer Verwendet er Tuples und Magic Numbers:

Code: Alles auswählen

key=operator.itemgetter(1)
So magisch finde ich es nicht, ist ja schließlich nur ein Indexzugriff. Wenn ich 1 und 2 in Konstanten auslagere würde es auch gehen aber die Lesbarkeit kaum verbessern.
veers hat geschrieben:Leonidas, warum
stats = [os.stat(f) for f in files] und nicht stats = map(os.stat, files)?
Ist als LC warscheinlich schneller.

Verfasst: Montag 21. Juli 2008, 12:05
von audax

Code: Alles auswählen

% python -mtimeit "import os; files = os.listdir('.'); [os.stat(f) for f in files]"
1000 loops, best of 3: 602 usec per loop
% python -mtimeit "import os; files = os.listdir('.'); map(os.stat, files)"        
1000 loops, best of 3: 554 usec per loop
Natürlich ist das wieder der Beste von 10 Läufen, es ist also alles im Cache.

Code: Alles auswählen

 % python -mtimeit "map(int, range(10))"                                    
100000 loops, best of 3: 3.79 usec per loop
% python -mtimeit "[int(n) for n in range(10)]" 
100000 loops, best of 3: 5.11 usec per loop
Und hier nochmal, mit möglichst wenig overhead.

map > LC ;)

Verfasst: Montag 21. Juli 2008, 12:21
von Leonidas
audax hat geschrieben:map > LC ;)
Und jetzt die Erklärung?

Verfasst: Montag 21. Juli 2008, 12:32
von lunar
os.listdir liefert relative Pfade, man muss also os.path.join einschalten:

Code: Alles auswählen

def largest_file(directory):
    files = (os.path.join(directory, f) for f in os.listdir(directory))
    return max(((f, os.path.getsize(f)) for f in files if os.path.isfile(f)),
               key=operator.itemgetter(1))
Und für die neueste Datei:

Code: Alles auswählen

def newest_file(directory):
    files = (os.path.join(directory, f) for f in os.listdir(directory))
    return max(((f, os.path.getmtime(f)) for f in files if os.path.isfile(f)),
               key=operator.itemgetter(1))

Verfasst: Montag 21. Juli 2008, 12:58
von audax
Leonidas hat geschrieben:
audax hat geschrieben:map > LC ;)
Und jetzt die Erklärung?
hmm?
Man sollte map() nicht krampfhaft durch LC ersetzen, vor allem nicht aus performance-Gründen.

Verfasst: Montag 21. Juli 2008, 13:04
von Leonidas
audax hat geschrieben:
Leonidas hat geschrieben:
audax hat geschrieben:map > LC ;)
Und jetzt die Erklärung?
hmm?
Naja, schließlich haben wir bei ``map()`` genauso viele Aufrufe der inneren Funktion (``os.stat()``) und noch dazu den ``map()`` Aufruf. Der ist auch nicht so billig, da man die Funktion erst im Namensraum finden muss und dann ausführen muss. Intern muss genauso wie bei einer LC über die Werte iteriert werden von daher würde ich gerne wissen, warum ``map()`` dennoch schneller ist.

Verfasst: Montag 21. Juli 2008, 13:15
von audax

Code: Alles auswählen

  4           0 BUILD_LIST               0
              3 DUP_TOP             
              4 STORE_FAST               0 (_[1])
              7 LOAD_GLOBAL              0 (range)
             10 LOAD_CONST               1 (10)
             13 CALL_FUNCTION            1
             16 GET_ITER            
        >>   17 FOR_ITER                19 (to 39)
             20 STORE_FAST               1 (n)
             23 LOAD_FAST                0 (_[1])
             26 LOAD_GLOBAL              1 (int)
             29 LOAD_FAST                1 (n)
             32 CALL_FUNCTION            1
             35 LIST_APPEND         
             36 JUMP_ABSOLUTE           17
        >>   39 DELETE_FAST              0 (_[1])
             42 POP_TOP             
             43 LOAD_CONST               0 (None)
             46 RETURN_VALUE        
Das ist die LC.

Code: Alles auswählen

  7           0 LOAD_GLOBAL              0 (map)
              3 LOAD_GLOBAL              1 (int)
              6 LOAD_GLOBAL              2 (range)
              9 LOAD_CONST               1 (10)
             12 CALL_FUNCTION            1
             15 CALL_FUNCTION            2
             18 POP_TOP             
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE  
Und das mit map.

Bei einer LC findet wohl das Bilden der Liste komplett in Python statt, bei map() in C, das macht wohl den kleinen unterschied aus.

btw, der Code:

Code: Alles auswählen

import dis 

def f():
    [int(n) for n in range(10)]

def g():
    map(int, range(10))

dis.dis(f)

dis.dis(g)
Und auch bei einer LC muss die Liste nachgeschlagne werden:

Code: Alles auswählen

             23 LOAD_FAST                0 (_[1])

Verfasst: Montag 21. Juli 2008, 13:30
von BlackJack
Der Unterschied ist das Nachschlagen der Funktion; bei `map()` wird `os.stat` bzw. `int` nur *einmal* nachgeschlagen, bei der LC *für jedes Element*.

Verfasst: Montag 21. Juli 2008, 13:37
von mkesper
Noch ein unerwarteter Effekt:
Ich hatte den Code versucht auszuführen, mich aber gewundert, daß er scheinbar loopt und bin erst nach nach mehrmaligem Ausprobieren darauf gekommen, daß die erste Zeile

Code: Alles auswählen

#!/bin/bash
statt

Code: Alles auswählen

#!/usr/bin/python
lautete. D'oh!

Verfasst: Montag 21. Juli 2008, 13:49
von Leonidas
BlackJack hat geschrieben:Der Unterschied ist das Nachschlagen der Funktion; bei `map()` wird `os.stat` bzw. `int` nur *einmal* nachgeschlagen, bei der LC *für jedes Element*.
Ja, diese Vermutung hatte ich ab irgendeinem Zeitpunkt auch. Das finde ich dennoch recht erstaunlich, dass die LC die Referenzen auf die Funktionen nicht irgendwo für schnellere Verwendung zwischenspeichert.

Verfasst: Montag 21. Juli 2008, 14:32
von Zap
Leonidas hat geschrieben:Das finde ich dennoch recht erstaunlich, dass die LC die Referenzen auf die Funktionen nicht irgendwo für schnellere Verwendung zwischenspeichert.
Mich wunderts auch das es in Summe so lange dauert den Namen immer wieder neu zuzuordnen.
Aber bei einer LC ist dies nötig da man ja auch den Funktionsnamen wärend der Bearbeitung neu binden kann.
Z.B für so nen Blödsinn ;)

Code: Alles auswählen

In [27]: [f(i) for i in range(10) for f in [[str, int][i % 2]] ]
Out[27]: ['0', 1, '2', 3, '4', 5, '6', 7, '8', 9]

Verfasst: Montag 21. Juli 2008, 19:25
von RedSharky
Danke für die große Hilfsbereitschaft.
Kaum ist man mal einen Tag nicht da, geht's zu wie im Taubenschlag. :)
Da sind wirklich ein paar interessante Lösungen dabei, die mir gänzlich unbekannt sind. Danke!