Уведомления

Группа в Telegram: @pythonsu

#1 Окт. 17, 2009 01:47:10

romanc
От:
Зарегистрирован: 2009-10-17
Сообщения: 4
Репутация: +  0  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

Доброго всем утра, вечера, дня и ночи. Начал изучать Python и сразу же столкнулся с задачей копирования директории с условием, чтоб пользователю предлагался выбор - заменить существующий файл или нет. Покопавшись в модулях os, os.path, shutil я такой функции не нашел, сразу скажу в shutil есть функция copytree(), но как я понял она генерирует исключение, а исключения область в которой я еще не твердо стою на ногах, а скорее вообще не стою. Подумав решил написать свою, взяв за основу код той же copytree(), заодно и в Python поупражняться. Собственно возникло пару вопросов:
1) Может я что-то пропустил в os и shutil, с английским у меня не очень, поэтому большая просьба опытным людям - ткните меня носом, если я что то пропустил в упомянутых модулях.
2) Можно ли добиться необходимого результата обработкой исключений, не создавая новую функцию копирования
3)Решил выложить написанную мной функцию здесь, хотелось бы услышать конструктивную критику, замечания, советы по поводу ее реализации.

Использую Python 3.0.1 (в PortablePython)

# -*- coding: cp866 -*-
import sys, subprocess, os, tempfile, code, time, shutil, stat
def dircpy(src, dst, ignore=None, symlinks=False, ForceReplace=False, logfile=None):
names = os.listdir(src)
AskStr = '\nФайл уже существует. Заменить?'
ChoiceNoReplace = ' н(n)= Не заменять (Don\'t replace)'
ChoiceReplace = ' д(y)= Да, заменить(Yes, replace)'
ChoiceAllReplace= ' в(a)= Заменить все файлы в данной директории(Replace all files in this dir)'
ChoiceForceAllReplace = ' ф(f)= Вообще все, и не спрашивайте меня больше (Force replace All, don\'t ask me never)'
ReplaceAll=False
ForceAll=ForceReplace
if ForceAll:
ReplaceAll=True
if ignore is not None:
ignored_names = ignore(src, names)
else:
ignored_names = set()
if not os.path.exists(dst):
os.makedirs(dst)
errors = []
for name in names:
if name in ignored_names:
continue
srcname = os.path.join(src, name)
dstname = os.path.join(dst, name)
try:
if symlinks and os.path.islink(srcname):
linkto = os.readlink(srcname)
os.symlink(linkto, dstname)
elif os.path.isdir(srcname):
ForceAll = dircpy(srcname, dstname, ignore, symlinks, ForceAll, logfile)
if ForceAll:
ReplaceAll=True
else:
print(dstname)
if logfile:
logfile.write('\n')
logfile.writelines(dstname)
if os.path.exists(dstname):
if ReplaceAll == True:
os.remove(dstname)
shutil.copy2(srcname, dstname)
else:
correctChoice = False
while not correctChoice:
print (AskStr)
print (ChoiceNoReplace)
print (ChoiceReplace)
print (ChoiceAllReplace)
print (ChoiceForceAllReplace)
if logfile:
logfile.writelines(AskStr)
Choice = code.InteractiveConsole.raw_input(': ')
if ('y' in Choice) or ('д' in Choice):
correctChoice = True
print ('Вы выбрали: ',ChoiceReplace)
if logfile:
logfile.writelines('\nВы выбрали: \n')
logfile.writelines(ChoiceReplace)
logfile.writelines('\n')
os.remove(dstname)
shutil.copy2(srcname, dstname)
elif ('в' in Choice) or ('a' in Choice):
correctChoice = True
print ('Вы выбрали: ',ChoiceAllReplace)
if logfile:
logfile.writelines('\nВы выбрали: \n')
logfile.writelines(ChoiceAllReplace)
logfile.writelines('\n')
os.remove(dstname)
shutil.copy2(srcname, dstname)
ReplaceAll=True
elif ('ф' in Choice) or ('f' in Choice):
correctChoice = True
print ('Вы выбрали: ',ChoiceForceAllReplace)
if logfile:
logfile.writelines('\nВы выбрали: \n')
logfile.writelines(ChoiceForceAllReplace)
logfile.writelines('\n')
os.remove(dstname)
shutil.copy2(srcname, dstname)
ReplaceAll=True
ForceAll=True
elif ('н' in Choice) or ('n' in Choice):
correctChoice = True
print ('Вы выбрали: ',ChoiceNoReplace)
if logfile:
logfile.writelines('\nВы выбрали: \n')
logfile.writelines(ChoiceNoReplace)
logfile.writelines('\n')
else:
print('Некоректный выбор\n')
if logfile:
logfile.writelines('Некоректный выбор-> ')
logfile.writelines(Choice)
logfile.writelines('\n')
else:
shutil.copy2(srcname, dstname)
# XXX What about devices, sockets etc.?

except (IOError, os.error) as why:
errors.append((srcname, dstname, str(why)))
# catch the Error from the recursive copytree so that we can
# continue with other files
except shutil.Error as err:
errors.extend(err.args[0])

try:
shutil.copystat(src, dst)

except OSError as why:
if WindowsError is not None and isinstance(why, WindowsError):
# Copying file access times may fail on Windows
pass
else:
errors.extend((src, dst, str(why)))
if errors:
raise shutil.Error(errors)
return ForceAll



Офлайн

#2 Окт. 17, 2009 03:07:12

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Копирование файлов с подтверждением замены

1. Нет, стандартные высокоуровневые функции такое не поддерживают - очень неопределен “ответ пользователя”.
2. Вместо рекурсии посмотрите на os.walk
3. Вынесите “функцию-вопрошалку” за пределы вашего копировщика. Передавайте ей исходный путь и назначение. В ответ получайте константы типа “да, делать”, “только в текущей директории”, “все запрещаю и не беспокойте меня” и т.д.
4. Исключения - они несколько не для того. А именно исключения бросаются в случае ошибки, который внешний код не может игнорировать.
Например - open(filename)
если open будет возвращать код ошибки - его обязательно программист обязательно проигнорирует как правило. Особенно если этот open будет вызываться из функции, которая была вызвана уровнем выше и т.д. Да и неудобно так писать. Зато если выбросите OSError типа “файл не существует” - о вас уже не забудут.

По поводу исключений и их правильного применения можно писать долго - так что спрашивайте.



Офлайн

#3 Окт. 17, 2009 16:55:36

romanc
От:
Зарегистрирован: 2009-10-17
Сообщения: 4
Репутация: +  0  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

Андрей, спасибо за ответ.

Сделал функцию-вопрошалку отдельно. Но вот по варианту с os.wolk столкнулся с трудностью. При рекурсивном обходе директорий вот таким кодом:

for root, dirs, files in os.walk(src):
for name in files:
fullname = os.path.join(root, name)
print (fullname)
# src - копируемая директория.
# dst - директория, куда копируются файлы
получается что root - это путь к папке в которой лежит копируемый файл, причем root в себе уже содержит src, т.е root == src + ‘путь с подпапками’. Для того чтобы скопировать файл мне в пути root необходимо src заменить на dst.
Можно ли это сделать каким-либо простым способом (встроенная функция) или же необходимо писать собственную функцию? В os и shutil ничего не нашел. Стал пробовать через модуль re, но пока что не смог составить правильного regexp. Плюс, как я понял, ситуация усугубляется наличием в пути символов ‘\’



Офлайн

#4 Окт. 18, 2009 16:55:22

pyuser
От:
Зарегистрирован: 2007-05-13
Сообщения: 658
Репутация: +  36  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

romanc
Можно ли это сделать каким-либо простым способом (встроенная функция)
destination = os.path.join(dst, os.path.relpath(fullname, src))
может не самый простой, но интуитивно понятный способ



Офлайн

#5 Окт. 19, 2009 11:41:02

romanc
От:
Зарегистрирован: 2009-10-17
Сообщения: 4
Репутация: +  0  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

pyuser, благодарю, я пропустил функцию os.path.relpath при просмотре справки по модулю.

Постарался воспользоваться советами Андрея Светлова в меру своих знаний и понимания,
вот результат:

# Функция взаимодействия с пользователем.

# -*- coding: cp1251 -*-

import sys, subprocess, os, tempfile, code, time, shutil, stat, re

# Функция взаимодействия с пользователем.
def AskForReplace (dst, logfile=None):
AskStr = 'Файл уже существует. Заменить?'
ChoiceNoReplace = ' 1 = Не заменять (Don\'t replace)'
ChoiceCancelAll = ' 2 = Не заменять файлы в данной директории (Don\'t replace All in this dir)'
ChoiceForceCancelAll = ' 3 = Не заменять файлы, и не спрашивать больше (Don\'t replace All, don\'t ask me never)'
ChoiceReplace = ' 4 = Да, заменить(Yes, replace)'
ChoiceAllReplace= ' 5 = Заменить все файлы в данной директории(Replace all files in this dir)'
ChoiceForceAllReplace = ' 6 = Заменить все, и не спрашивайте меня больше (Force replace All, don\'t ask me never)'

text = ''
correctChoice = False
while not correctChoice:
print (AskStr)
print (ChoiceNoReplace)
print(ChoiceCancelAll)
print(ChoiceForceCancelAll)
print (ChoiceReplace)
print (ChoiceAllReplace)
print (ChoiceForceAllReplace)

if logfile:
logfile.writelines(AskStr)
Choice = code.InteractiveConsole.raw_input(': ')
if ('1' == Choice):
correctChoice = True
text = ChoiceNoReplace
elif ('2' == Choice):
correctChoice = True
text = ChoiceCancelAll
elif ('3' == Choice):
correctChoice = True
text = ChoiceForceCancelAll
elif ('4' == Choice):
correctChoice = True
text = ChoiceReplace
elif ('5' == Choice):
correctChoice = True
text = ChoiceAllReplace
elif ('6' == Choice):
correctChoice = True
text = ChoiceForceAllReplace
else:
print('Некоректный выбор\n')
if logfile:
logfile.writelines('Некоректный выбор-> ')
logfile.writelines(Choice)
logfile.writelines('\n')
print ('Вы выбрали: ',text)
print('')
if logfile:
logfile.writelines('\nВы выбрали: \n')
logfile.writelines(text)
logfile.writelines('\n\n')
return Choice
Сама функция копирования
# Функция копирования.
# src - Папка, содержимое которой копируется
# dst - Папка, куда копируем
# symlink - Копировать ли файлы на которые указывает линк (ярлык?)
# logfile - лог файл

def dircpy(src, dst, ignore=None, symlinks=False, logfile=None):
names = os.listdir(src)
ReplaceAll=False
CancelAll = False
ForceAll=False
ForceCancelAll = False
if ignore is not None:
ignored_names = ignore(src, names)
else:
ignored_names = set()
errors = []

# Обход всех файлов с помощью функции os.walk
for root, dirs, files in os.walk(src):

# Проверяем флаг ForceAll замены всех файлов без запроса, если ForceAll == True, то
# заменяем все файлы во всех папках без запроса пользователю,
# если ForceAll != True, то задаем вопрос пользователю при каждом совпадении имен файлов
if ForceAll == True:
ReplaceAll=True
else:
ReplaceAll=False

# Проверяем флаг ForceCancelAll запрета замены файлов, если ForceCancelAll == True, то
# отменяем замену файлов во всех папках без запроса пользователю,
# если ForceCancelAll != True, то задаем вопрос пользователю при каждом совпадении имен файлов
if ForceCancelAll == True:
CancelAll = True
else:
CancelAll = False

# Обходим все файлы в поддиректориях
for name in files:
if name in ignored_names:
continue
srcname = os.path.join(root, name) # путь, имя копируемого файла
dstpath=os.path.join(dst, os.path.relpath(root, src)) # путь - место назначения, куда копируем

# проверяем существует ли папка назначения, если нет, то создаем ее
if not os.path.exists(dstpath):
os.makedirs(dstpath)

# Получаем полное имя файла, куда будем копировать исходный файл
dstname = os.path.join(dstpath, name)
try:
if symlinks and os.path.islink(srcname):
linkto = os.readlink(srcname)
os.symlink(linkto, dstname)
else:
print(dstname)
if logfile:
logfile.writelines(dstname)
logfile.write('\n')

# Проверяем существует ли файл
if os.path.exists(dstname):
# Проверяем установлен ли флаг ReplaceAll замены файлов только в данной директории, если да, то
# заменяем все файлы без запроса пользователю
if ReplaceAll == True:
os.remove(dstname)
shutil.copy2(srcname, dstname)
elif CancelAll == True: # иначе проверяем установлен ли флаг CancelAll запрета замены файлов
pass # только для данной директории, если да, то пропускаем
# копирование уже существующих файлов. Замена не происходит
else: # Иначе (если не установлены флаг замены или запрета замены, взаимоддействуем
# с пользователем)
Choice = AskForReplace(dstname, logfile) # Вызов функции интерактивного запроса

# Действуем согласно ответу пользователя
if '1' in Choice:
pass
elif '2' in Choice:
CancelAll = True
elif '3' in Choice:
CancelAll = True
ForceCancelAll = True
elif '4' in Choice:
os.remove(dstname)
shutil.copy2(srcname, dstname)
elif '5' in Choice:
os.remove(dstname)
shutil.copy2(srcname, dstname)
ReplaceAll=True
elif '6' in Choice:
os.remove(dstname)
shutil.copy2(srcname, dstname)
ReplaceAll=True
ForceAll=True
else:
shutil.copy2(srcname, dstname)
#XXX What about devices, sockets etc.?

except (IOError, os.error) as why:
errors.append((srcname, dstname, str(why)))
# catch the Error from the recursive copytree so that we can
# continue with other files
except shutil.Error as err:
errors.extend(err.args[0])

try:
shutil.copystat(src, dst)

except OSError as why:
if WindowsError is not None and isinstance(why, WindowsError):
# Copying file access times may fail on Windows
pass
else:
errors.extend((src, dst, str(why)))
if errors:
raise shutil.Error(errors)
Вообщем то работает, но нет предела совершенству, возможно будут еще предложения и советы.
…замечания?



Офлайн

#6 Окт. 20, 2009 03:01:50

pyuser
От:
Зарегистрирован: 2007-05-13
Сообщения: 658
Репутация: +  36  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

romanc
dstpath=os.path.join(dst, os.path.relpath(root, src)) # путь - место назначения, куда копируем
# проверяем существует ли папка назначения, если нет, то создаем ее
if not os.path.exists(dstpath): os.makedirs(dstpath)
этот код целесообразнее делать перед циклом
for name in files:
root - папка, files - список файлов в папке root. зачем для каждого файла из папки root проверять существование приемника, если это можно сделать один раз :)

ЗЫ. не то чтобы оптимизация, просто код:
if нечто == True:
выглядит, скажем, необычно :) почему не просто:
if нечто:



Отредактировано (Окт. 20, 2009 03:07:18)

Офлайн

#7 Окт. 20, 2009 08:03:16

romanc
От:
Зарегистрирован: 2009-10-17
Сообщения: 4
Репутация: +  0  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

Да, действительно, так будет лучше:

...
dstpath=os.path.join(dst, os.path.relpath(root, src)) # путь - место назначения, куда копируем
if not os.path.exists(dstpath):
os.makedirs(dstpath)
# Обходим все файлы в поддиректориях
for name in files:
if name in ignored_names:
continue
...
Благодарю =)

Это:
if нечто == True:
даже не знаю, как и объяснить =). Я не програмировал на динамически типизированных (или типизируемых) языках, привык все явно задавать наверное.
Если немного от темы отвлечься, я работаю со SCADA. Вот в одной из них встроен интерпритатор С, в одной из версий этой SCADA была ошибка обработки
True и False в условии if, как раз если пишешь if (BoolVar) … . После этого я стал побаиваться писать неявно =)



Офлайн

#8 Окт. 20, 2009 13:36:16

Rodegast
От: Пятигорск
Зарегистрирован: 2007-12-28
Сообщения: 2742
Репутация: +  183  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

Хм… не пойму чем вам рекурсия не нравится. Она хоть и ест память, но если вложенность не превышает приделы разумного, то в ней нет ничего страшного. Я для копирования дерева каталогов когда то написал такую функцию:

def cp(N, K):
if os.path.isdir(N):
if not os.path.exists(K): os.mkdir(K)
for x in os.listdir(N):
if os.path.isdir(os.path.join(N,x)):
if not os.path.exists(os.path.join(K,x)): os.mkdir(os.path.join(K,x))
cp(os.path.join(N,x),os.path.join(K,x))
else: cp(os.path.join(N,x),os.path.join(K,x))
else:
file = open(N,"rb"); data = file.read(); file.close()
file = open(K,"wb"); file.write(data); file.close()
Функция хотя ничего и не спрашивает, но всё прекрасно копирует : )



С дураками и сектантами не спорю, истину не ищу.
Ели кому-то правда не нравится, то заранее извиняюсь.

Отредактировано (Окт. 20, 2009 13:40:46)

Офлайн

#9 Окт. 20, 2009 16:15:55

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Копирование файлов с подтверждением замены

дело не в памяти. Генератор более уместен.



Офлайн

#10 Окт. 21, 2009 09:42:34

Rodegast
От: Пятигорск
Зарегистрирован: 2007-12-28
Сообщения: 2742
Репутация: +  183  -
Профиль   Отправить e-mail  

Копирование файлов с подтверждением замены

Почему?



С дураками и сектантами не спорю, истину не ищу.
Ели кому-то правда не нравится, то заранее извиняюсь.

Офлайн

Board footer

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

Powered by DjangoBB

Lo-Fi Version