Уведомления

Группа в Telegram: @pythonsu

#1 Фев. 17, 2009 02:29:34

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

md5

Пространства для Вашего Я, коллеги!

В свободное время развлекаю себя тем, что потихонечку добиваю набор своих пылесосов. Вот, некоторое время назад, написалась такая функция…

# coding: utf-8

import os as _os
import hashlib as _hashlib
from subprocess import Popen as _Popen

def file_md5(file, use_system = False):
"""file_md5(file, use_system = False) -> md5sum of "file" as hexdigest string.
"file" may be a file name or file object, opened for read.
If "use_system" is True, if possible use system specific program. This ignore, if file object given."""

if isinstance(file, basestring):
if use_system:
sysname = _os.uname()[0]
if sysname in ('Darwin', 'FreeBSD'):
po = _Popen('md5 -q "%s"' % file, shell=True, stdout=-1, stderr=-1)
po.wait()
m = po.stdout.readline().strip()
if len(m) == 32:
return m
# elif sysname == ...

file = open(file, 'rb')

h = _hashlib.md5()
block = file.read(h.block_size)
while block:
h.update(block)
block = file.read(h.block_size)
return h.hexdigest()

"""
fn = "/Users/Shared/Фильмы/Аниме/Evangelion/Neon Genesis Evangelion - 13.avi"
print file_md5(fn)
print file_md5(open(fn, 'rb'))
print file_md5(fn, True)
import os
os.system('md5 -q "%s"' % fn)
"""
Код простой, работает и это хорошо.
Обратите внимание на use_system и закомментированный elif sysname == …. Собственно хотелось бы довавить ещё систем и в первую очередь Linux. Я помню, что там есть md5sum, но под рукой сейчас нет, чтобы написать и проверить. Зачем это надо? А вы сравните скорость вычисления на какой-нить большом файле и всё поймёте.

P.S. За мой француский сильно не пинайте, и если есть мысли как правильей оформить докстрин, то буду рад подсказке.



Офлайн

#2 Фев. 17, 2009 09:37:35

SvartalF
От:
Зарегистрирован: 2008-06-29
Сообщения: 73
Репутация: +  0  -
Профиль   Отправить e-mail  

md5

Держите под линукс.

Выдержка из man:

Печатает или проверяет контрольные суммы MD5 (128-битные).
-b, --binary читать в двоичном режиме
-t, --text читать в текстовом режиме (по умолчанию)

По умолчанию печатает строку с контрольной суммой, знак, показывающий
тип файла (`*' для двоичных, ` ' для текстовых), и имя каждого ФАЙЛА.
Процесс работы
svartalf@svartalf:~$ md5sum -b ~/amarok2.jpeg
c08cce704ad1877a6d62b47e1bf593f2 *~/amarok2.jpeg

svartalf@svartalf:~$ md5sum -t rfc3920.txt
f102c3f9f6e2e45601a083a03d0c4c4c rfc3920.txt

svartalf@svartalf:~$ uname -a
Linux svartalf 2.6.27-11-generic #1 SMP Thu Jan 29 19:24:39 UTC 2009 i686 GNU/Linux



Офлайн

#3 Фев. 17, 2009 09:58:32

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

md5

Что-то я идеей не проникся. Попробовал читать весь файл целиком - результаты почти такие же, как при запуске md5sum.
Вот код:

#!/usr/bin/python

import sys
import os

from md5 import md5
from subprocess import Popen
from timeit import Timer

def test1():
po = Popen('md5sum -b "%s"' % sys.argv[1], shell=True, stdout=-1, stderr=-1)
po.wait()
return po.stdout.readline().strip()

def test2():
#h = md5()
#file = open(sys.argv[1], 'rb')
# block = file.read(h.block_size)
# while block:
# h.update(block)
# block = file.read(h.block_size)
# return h.hexdigest()
return md5(open(sys.argv[1], 'rb').read()).hexdigest()

print os.uname()[0]
print sys.argv[1], os.path.getsize(sys.argv[1])
print test1()
print test2()
print Timer("test1()", "from __main__ import test1").timeit(3)
print Timer("test2()", "from __main__ import test2").timeit(3)
$ python md5sum.py /tmp/House.s05e13.rus.LostFilm.TV.avi
Linux
/tmp/House.s05e13.rus.LostFilm.TV.avi 364904448
505371f15678408238d0ee209bb48bb9 */tmp/House.s05e13.rus.LostFilm.TV.avi
505371f15678408238d0ee209bb48bb9
5.47134208679
5.59653997421

PS: Ваш код не будет работать в 2.4. Там в объекте md5 нет block_size.



Офлайн

#4 Фев. 17, 2009 11:57:45

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

md5

SvartalF
Держите под линукс.
Сенкс… Ещё бы os.uname() узнать… :-)

Ed
Что-то я идеей не проникся. Попробовал читать весь файл целиком - результаты почти такие же, как при запуске md5sum.
Ага, а если надо узнать контрольную сумму, например, образа DVD9? У меня в макбуке всего три гига памяти… :-)
Быстродействие очень страдает от малого размера block_size. Надо сделать его больше и не париться с hashlib.md5.block_size. См. ниже.

Ed
PS: Ваш код не будет работать в 2.4. Там в объекте md5 нет block_size.
Тоже сенкс. Но уже не важно.

def file_md5(file, use_system=False, block_size=1024):
"""file_md5(file, use_system = False) -> md5sum of "file" as hexdigest string.
"file" may be a file name or file object, opened for read.
If "use_system" is True, if possible use system specific program. This ignore, if file object given."""

if isinstance(file, basestring):
if use_system:
sysname = _os.uname()[0]
if sysname in ('Darwin', 'FreeBSD'):
po = _Popen('md5 -q "%s"' % file, shell=True, stdout=-1, stderr=-1)
po.wait()
m = po.stdout.readline().strip()
if len(m) == 32:
return m
elif sysname == "Linux2":
po = _Popen('md5sum -b "%s"' % file, shell=True, stdout=-1, stderr=-1)
po.wait()
m = po.stdout.readline().strip()
if len(m) > 32:
return m[:32]
# elif sysname == ...

file = open(file, 'rb')
h = _hashlib.md5()
block = file.read(block_size)
while block:
h.update(block)
block = file.read(block_size)
return h.hexdigest()
Был бы очень благодарен, если бы кто-нить проверил…
Ну и, собственно, кто какие ещё системы знает и есть ли в них что-нить на эту тему?



Отредактировано (Фев. 17, 2009 12:24:38)

Офлайн

#5 Фев. 17, 2009 13:39:51

Ed
От:
Зарегистрирован: 2008-12-13
Сообщения: 1032
Репутация: +  13  -
Профиль   Отправить e-mail  

md5

os.uname() должен быть Linux, а не Linux2
Это видно из моего предыдущего поста.

В остальном все правильно. по крайней мере работает на моем линухе.

А почему вы используете shell=True, если не секрет? Вроде на *nix оно False по-умолчанию, что в общем-то и правильно и дешевле.



Офлайн

#6 Фев. 17, 2009 15:32:20

evgenyl
От:
Зарегистрирован: 2008-07-22
Сообщения: 148
Репутация: +  0  -
Профиль   Отправить e-mail  

md5

не понимаю, зачем popen ?
чем стандартный md5 плох ?
а ещё имхо слишком маленький block_size минимум 8 килобайт



Отредактировано (Фев. 17, 2009 15:33:44)

Офлайн

#7 Фев. 17, 2009 16:15:26

slav0nic
Команда
От: dp.ua
Зарегистрирован: 2006-05-07
Сообщения: 2260
Репутация: +  41  -
Профиль   Отправить e-mail  

Офлайн

#8 Фев. 18, 2009 03:23:10

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

md5

Ed
os.uname() должен быть Linux, а не Linux2
Это видно из моего предыдущего поста.
Сорри за невнимательность. Спать надо больше.

Ed
А почему вы используете shell=True, если не секрет? Вроде на *nix оно False по-умолчанию, что в общем-то и правильно и дешевле.
У меня в MacOS X оно с shell=False отказывается работать. А разбираться почему я полгода назад так написал… Спать надо… Больше… :-)

evgenyl
не понимаю, зачем popen ?
чем стандартный md5 плох ?
В смысле такой, как при use_system=False?
Мысль первая: скорость. Но эта мысль явно устарела, так как при 14 (?!) килобайтном буфере оно работает быстрее, чем мой макосный md5. 1.3s против 1.9s.
Мысль вторая: параллельность. Что там у нас с GIL? Сколько ядер я загружу при одновременном вычислении нескольких хэшей? А с помощью использования отдельных процессов я загружу их все. Поправте уж, если я ошибаюсь. Дело в том, что сейчас это планируется использовать на сервере, где контрольные суммы больших и не очень файлов будут вычисляться с завидной регулярностью.

slav0nic
http://code.activestate.com/recipes/266486/
Ну это почти тоже такое, что и написал я, только без отсылке к системе.



Офлайн

#9 Фев. 18, 2009 08:23:04

evgenyl
От:
Зарегистрирован: 2008-07-22
Сообщения: 148
Репутация: +  0  -
Профиль   Отправить e-mail  

md5

ZZZ
В смысле такой, как при use_system=False?
Мысль первая: скорость. Но эта мысль явно устарела, так как при 14 (?!) килобайтном буфере оно работает быстрее, чем мой макосный md5. 1.3s против 1.9s.
Мысль вторая: параллельность. Что там у нас с GIL? Сколько ядер я загружу при одновременном вычислении нескольких хэшей? А с помощью использования отдельных процессов я загружу их все. Поправте уж, если я ошибаюсь. Дело в том, что сейчас это планируется использовать на сервере, где контрольные суммы больших и не очень файлов будут вычисляться с завидной регулярностью.
алгоритм расчета md5 очень прост и никоем образом не упирается в процесор, узкое место диск, так что лучше делать очередь и считать их не паралельно, так ты не убьеш сервер и добьешся оптимальной загрузки дисковой подсистемы



Офлайн

#10 Фев. 18, 2009 11:49:46

ZZZ
От: Москва
Зарегистрирован: 2008-04-03
Сообщения: 2161
Репутация: +  26  -
Профиль   Адрес электронной почты  

md5

Вот об этом я тоже думал. Поэтому позможно сделаю более динамичный код подсчёта. На питоне. Но заниматься такой оптимизацией раньше времени не стоит. Как, впрочем, и любой другой.
А сейчас просто хотелось сделать как-нить красиво и в общем удалось. Я доволен. А так… Это просто вылысыпэд и пылысос в одном флаконе.

Обратите внимание на алгоритм выделения контрольой суммы из md5sum. Мне кажется, что это самый простой подход, удовлетворяющий принципу KISS, но всё-таки, с высокой долей вероятности, отлавливающий ошибку самого md5sum.

# coding: utf-8

import os as _os
import hashlib as _hashlib
from subprocess import Popen as _Popen


def file_md5(file, use_system=False, block_size=1024*14):
"""file_md5(file, use_system = False) -> md5sum of "file" as hexdigest string.
"file" may be a file name or file object, opened for read.
If "use_system" is True, if possible use system specific program. This ignore, if file object given.
"block_size" -- size in bytes buffer for calc md5. Used with "use_system=False"."""

if isinstance(file, basestring):
if use_system:
sysname = _os.uname()[0]
if sysname in ('Darwin', 'FreeBSD'):
po = _Popen('md5 -q "%s"' % file, shell=True, stdout=-1, stderr=-1)
po.wait()
m = po.stdout.readline().strip()
if len(m) == 32:
return m
else:
print "Warning: md5 on %s is error" % sysname
elif sysname == "Linux":
po = _Popen('md5sum -b "%s"' % file, shell=True, stdout=-1, stderr=-1)
po.wait()
m = po.stdout.readline().strip()
# if len(m) > 32:
if m.find(' ') == 32:
return m[:32]
else:
print "Warning: md5sum on Linux is error"
# elif sysname == ...

file = open(file, 'rb')

h = _hashlib.md5()
block = file.read(block_size)
while block:
h.update(block)
block = file.read(block_size)
return h.hexdigest()



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version