1. Introduction
이 예제 프로그램은 파일명 관리 유틸리티, SR(Super Renamer)입니다. 코어 코드만 포함된 초기 시험판입니다.
이 프로그램은 중급/고급 대상 예제이므로 입문자들에게는 무리입니다. 지금은 전혀 Super스럽지(?) 않지만, 우선은 저에게도 필요한 프로그램입니다. ^.^;
파일명이 문자열로 되어 있어서 이 프로그램은 실용적인 문자열 처리 기법과 정규 표현식에 대한 종합 연습용 예제로 '가끔' 유용합니다. ^.^;
이 예제에 사용된 파일명 변경 규칙들은 계속 추가될 예정입니다. 아마도 프로그램 디자인(=설계) 방향이 규칙 함수들의 지속적인 추가-누적 방향으로 진행될 것으로 생각됩니다. 초기 코어 개발 기간 당분간은.
이후에는 아마도 GUI를 도입하고 규칙 함수들의 적용 및 제외 관리 쪽으로 주력하게 될 것으로 생각되고, Ai까지는 생각하고 있지 않지만 그렇게 될 수 있으면 좋겠습니다. Optional Rule-Based Renamer가 일단은 가시적인 개발 목표입니다.
GUI 부분은 처음에는 Tkinter로, 이후에는 wxPython이나 PyQt로 만들어 볼 생각입니다. 이 프로그램은 엄청난 기능보다는 프로그램의 사용성에 중점을 두고 개발됩니다.
[ SR. v.0.0.2 beta 001 ]
"""
아래는 주어진 규칙에 따라 'C:\Temp' 폴더의 모든 파일 이름을 변경하는 파이썬 프로그램의 샘플 코드입니다. 여기서 지정된 폴더는 이후 동적 실행 환경 및 GUI를 통한 폴더 선택 방법 등을 통해 임의의 다른 폴더로 변경될 수 있습니다.
"""
2. Code
"""
SR. v.0.0.2 beta 001
Oct. 08th, 2024.
ITM.DG.James.
"""
import os
import re
# 폴더 경로 설정, 보통, GUI를 통해 동적으로 결정됨.
folder_path = r'C:\Temp'
def add_prefix_if_needed(sname):
print("sname 1 : ", type(sname), " : ", sname) #check
if re.match(r'^S5_', sname): # 문자열이 'S5_'로 시작하는지 확인, sname 타입 오류 주의.
return sname
elif not re.match(r'^S5_', sname): # 시작하지 않으면 'S5_'를 추가
sname = 'S5_' + sname
print("sname 2 : ", type(sname), " : ", sname) #check
return sname
# 파일 이름 변경 함수 정의
def rename_files_in_folder(folder_path):
for filename in os.listdir(folder_path):
# 파일의 전체 경로
old_file_path = os.path.join(folder_path, filename)
# 파일명에서 확장자 분리
name, ext = os.path.splitext(filename)
# 여기는 규칙 서술 부분 시작.
# 이하 규칙 부분은 Regular Expression.
# 규칙 1 : 모든 공백 문자를 밑줄(_)로 대체
name = name.replace(' ', '_')
# 규칙 2 : 모든 특수 기호를 밑줄(_)로 대체. 현재로서 이 규칙은 문제점이 많다.
name = re.sub(r'[^A-Za-z0-9_]', '_', name)
# 규칙 3 : 파일명이 숫자나 밑줄 문자로 시작하는 경우 'S5_' 추가
if name[0].isdigit() or name.startswith('_'):
name = 'S5_' + name
# 새로운 규칙은 규칙 부분에 계속 추가될 수 있을 것.
# 규칙 4 : 연속 반복되는 문자열 하나로 통합, 밑줄 문자의 경우임.
name = re.sub(r'_{2,}', '_', name)
# Rule 5 : 접두어 추가 혹은 스킵.
name = add_prefix_if_needed(name)
# 여기는 규칙 서술 부분 끝.
# 새로운 파일명. 확장자 포함.
new_filename = name + ext
new_file_path = os.path.join(folder_path, new_filename)
# 실제로 파일 이름을 변경하는 함수.
os.rename(old_file_path, new_file_path)
# 확인용 :
print(f'Renamed: "{filename}" to "{new_filename}"')
# 여기서 rename_files_in_folder() 함수 종료!
# 함수 호출
rename_files_in_folder(folder_path)
3. 코드 해설 Notes
약간의 코드 설명.
■ os 모듈 : 파일과 디렉토리 작업을 위한 모듈입니다....
■ re 모듈 : 정규 표현식을 사용하여 문자열을 조작합니다.
■ os.listdir(folder_path) : 지정된 폴더의 모든 파일 목록을 가져옵니다.
◈ 각 파일 이름에 대해 규칙을 적용하고 새 파일 이름으로 변경합니다. 순서는 유의미합니다.
■ os.rename(old_file_path, new_file_path) : 파일 이름을 변경합니다.
◈ 위 코드를 실행하기 전에 해당 폴더 경로가 올바른지 확인 필요함. 실무 환경에서의 폴더 경로 설정은 주로 동적으로 결정될 듯. 보통은 GUI를 통해서 특정 폴더 선택하는 방식으로!
◈ GUI + 누적 Rule Based 방식으로 구성 가능할 것.
4. 추가 사항들 Additions
□ 규칙 4, 파이썬에서 정규 표현식을 사용하여 문자열 중 연속된 밑줄 문자를 하나의 밑줄 문자로 줄이는 방법은 다음과 같습니다.
1) re 모듈 임포트 :
import re
2) 정규 표현식 사용 :
정규 표현식 r'_{2,}'를 사용하여 두 개 이상의 연속된 밑줄을 찾습니다.
3) re.sub() 함수를 사용하여 찾은 부분을 하나의 밑줄로 대체합니다.
□ 규칙 4, 실제 구현 예제
import re
# 원본 문자열
text = "이___것은___예시___문자열입니다____."
# 연속된 밑줄을 하나로 줄이기
result = re.sub(r'_{2,}', '_', text)
print(result)
□ 규칙 4, 코드 설명
r'_{2,}' : 두 개 이상의 밑줄을 찾는 정규 표현식입니다.
re.sub() : 첫 번째 인자는 패턴, 두 번째 인자는 대체할 문자열, 세 번째 인자는 원본 문자열입니다.
□ 규칙 4, 실행 결과
위의 예제 코드를 실행하면, 출력 결과는 다음과 같습니다:
이_것은_예시_문자열입니다_.
이렇게 하면 연속된 여러 개의 밑줄이 하나의 밑줄로 줄어듭니다. 다른 문자열 중복의 경우에도 응용할 수 있는 유용한 코드입니다.
□ 규칙 5, 문자열이 'S5_' 로 시작하는지 확인하고, 그렇지 않으면 문자열 맨 앞에 'S5_' 를 추가하는 방법은 다음과 같습니다.
1) re 모듈 임포트:
import re
2) 정규 표현식 사용:
정규 표현식 ^S5_ 를 사용하여 문자열이 S5_ 로 시작하는지 확인합니다.
3) 예제 코드
import re
def add_prefix_if_needed(sname):
print("sname 1 : ", type(sname), " : ", sname) #check
if re.match(r'^S5_', sname):
# 문자열이 'S5_'로 시작하는지 확인
return sname
elif not re.match(r'^S5_', sname):
# 시작하지 않으면 'S5_'를 추가
sname = 'S5_' + sname
print("sname 2 : ", type(sname), " : ", sname)
#check
return sname
# 예시 문자열
example1 = "example_string"
example2 = "S5_example_string"
# 결과 확인
print(add_prefix_if_needed(example1)
# 출력: S5_example_string
print(add_prefix_if_needed(example2)
# 출력: S5_example_string
4) 코드 설명
^S5_ : 문자열의 시작 부분에 S5_ 가 있는지를 확인하는 정규 표현식입니다.
re.match() : 주어진 패턴이 문자열의 시작 부분과 일치하는지 검사합니다.
5) 실행 결과
위의 코드를 실행하면, 다음과 같은 결과를 얻을 수 있습니다:
S5_example_string
S5_example_string
이렇게 하면 주어진 문자열이 S5_ 로 시작하지 않을 경우, 문자열 앞에 S5_ 가 추가됩니다.
♨ 참고 : 어떤 경우에 다음과 같은 타입 오류가 일어나는지 주의해야 합니다.
Traceback (most recent call last):
File "SR_v0_0_1_.py", line 77, in <module>
rename_files_in_folder(folder_path)
File "SR_v0_0_1_.py", line 63, in rename_files_in_folder
new_filename = name + ext
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
5. Updates
5.1. Updates #0001
Updates #0001은 SR 기본 코드와 분리된 독립적 프로그램 형태로 만들어 작동 테스트를 겸하고 있습니다. 이 업데이트 프로그램에서 충분히 테스트된 후 SR 기본 코드에 통합될 예정입니다.
Updates #0001은 tkinter GUI 대화상자를 통해서 특정 폴더를 선택하고 이 폴더의 이름을 Python 코드로 특정 변수에 저장하려고 합니다. 그리고 폴더 선택에 이 변수를 활용하려고 합니다. 이 코드들은 SR 프로그램의 실행 초기에 결정되는 특정 대상 폴더를 GUI 옵션을 통해 선택적으로 변경하는 역할을 합니다. 구체적으로는 아래 코드 부분을 GUI 옵션화합니다.
# 폴더 경로 설정, 보통, GUI를 통해 동적으로 결정됨.
folder_path = r'C:\Temp'
위 코드 부분을 Python tkinter 모듈의 GUI 대화상자를 통한 폴더 선택 방식으로 변경합니다.
■ 필요한 라이브러리
- tkinter: GUI를 생성하는 라이브러리
- tkinter.filedialog: 파일 대화상자를 열기 위해 필요
■ 코드 예시
import tkinter as tk
from tkinter import filedialog
def select_folder():
# Tkinter 윈도우 생성 (숨김).
# 우선은, 윈도우 자체가 이 코드의 핵심이 아니기 때문임. 윈도우가 필요하기는 함. 필수 사항.
root = tk.Tk()
root.withdraw() # 메인 윈도우 숨김
# 폴더 선택 대화상자 열기
folder_path = filedialog.askdirectory(title="폴더 선택")
# 선택한 폴더가 있을 경우
if folder_path:
print("선택한 폴더:", folder_path)
return folder_path
else:
print("폴더를 선택하지 않았습니다.")
# 폴더가 선택되지 않을 경우, 기본 폴더 설정을 사용하도록 이 부분에서 변경할 수도 있음.
return None
# 함수 호출 및 선택한 폴더의 이름 저장
selected_folder = select_folder()
# 선택한 폴더의 이름을 출력, 그리고 if문 이외의 경우 처리.
if selected_folder:
folder_name = selected_folder.split('/')[-1] # 폴더 경로에서 폴더 이름 추출
print("폴더 이름:", folder_name)
else:
selected_folder = "D:\\" # 대화상자를 통한 폴더 선택이 취소된 경우, 기본값으로 제공. 이하 동일.
folder_name = "" # Default Value.
■ 코드 설명
Tkinter 초기화 : tk.Tk()를 통해 Tkinter 객체를 생성하고, withdraw() 메서드를 사용하여 메인 윈도우를 숨깁니다.
cf. root.deiconify()
# 윈도우 보이기
폴더 선택: filedialog.askdirectory() 함수를 사용하여 폴더 선택 대화상자를 띄웁니다.
폴더 경로 처리 : 선택한 폴더의 경로에서 폴더 이름을 추출하기 위해 split('/')[-1]을 사용합니다.
이 부분은 운영 체제에 따라 경로 구분자가 다를 수 있으므로, 필요에 따라 조정할 수 있습니다.
이 부분에서 필요에 따라 '드라이브명 + 폴더 경로 전체'를 포함한 이름으로 대신할 수도 있을 것.
결과 출력: 선택한 폴더 경로와 이름을 출력합니다.
이 코드를 실행하면 폴더 선택 대화상자가 나타나며, 선택한 폴더의 경로와 이름이 변수에 저장됩니다.
■ 보충 설명
□ 위 코드 중에서 [-1] 부분 :
[-1] 부분은 파이썬에서 리스트나 문자열의 인덱스를 나타내는 방법 중 하나로, 리스트나 문자열의 '마지막 요소'를 선택하는 데 사용됩니다. 좀 더 자세히.
□ 코드 설명
1. if selected_folder:
- 이 조건문은 'selected_folder'가 존재하는지 확인합니다. 만약 'selected_folder'가 비어 있지 않다면, 다음 코드를 실행합니다.
2. folder_name = selected_folder.split('/')[-1]
- selected_folder.split('/') :
- selected_folder 문자열을 '/' 기호를 기준으로 나누어 리스트 형태로 만듭니다.
- 예를 들어, selected_folder가 "D:/Documents/Projects"라면, split('/')의 결과는 ["D:", "Documents", "Projects"]입니다. 결과가 리스트 자료형입니다.
- [-1] : 리스트의 마지막 요소를 선택합니다. 위의 예에서는 "Projects"가 됩니다.
- 즉, folder_name에는 선택된 폴더의 '이름'이 할당됩니다.
3. print("폴더 이름:", folder_name)
- 선택된 폴더 이름을 출력합니다.
4. else:
- 만약 selected_folder가 비어 있다면(= 변수에 값이 할당되어 있지 않다면...의 뜻), 다음 코드를 실행합니다.
- 'selected_folder가 비어 있다면'이라는 표현은 selected_folder 변수에 값이 할당되어 있지 않거나 값이 빈 문자열(empty string, "", 이중 인용 부호 사이에 공백이 없음, 문자열 자료형이라는 것만은 유지함)인 경우를 의미합니다. '선택된 폴더에 실제 파일들이 하나도 없다'라는 뜻이 아닙니다. 해석 주의.
□ '비어 있다'는 조건:
- selected_folder가 'None'이거나
- selected_folder가 빈 문자열("")인 경우. 주의 : 이중 인용 부호("") 사이에 공백(Space)이 없음.
□ 예시
- 만약 selected_folder가 None이나 ""일 경우, if selected_folder:문이 False로 평가되므로 else: 블록이 실행됩니다.
- 따라서, selected_folder에 기본 경로인 "D:\\"가 할당되고, folder_name은 빈 문자열로 초기화됩니다.
이런 방식으로 selected_folder가 비어 있을 때의 처리를 하게 됩니다.
5. selected_folder = "D:\\"
- 기본 경로를 "D:\"로 설정합니다.
6. folder_name = ""
- 폴더 이름을 빈 문자열로 초기화합니다.
□ 요약
- [-1]은 리스트의 '마지막 요소'를 선택하는 데 사용됩니다. 위의 코드에서는 선택된 폴더 경로의 마지막 부분, 즉 폴더 이름을 추출하는 데 사용됩니다.
■ 리스트 기본기 필수 보충 : 리스트 카운트에 음수 인덱스 사용하기:
[ 리스트 기본기 보충 : 리스트 카운트에 음수 인덱스 사용하기 ]
리스트의 맨 뒤에 있는 최종 요소를 선택할 때 뒤에서부터 카운트하는 방법이 있으면 편리합니다. 리스트의 전체 크기를 몰라도 인덱스를 지정할 수 있으니 편리합니다.
다만 이때 주의할 점은 0부터 세지 않고 1부터 세며, 뒤에서부터 세기 때문에 앞에서부터 세는 경우와 차별점을 두어 양수가 아니라 음수를 사용합니다.
[ 앞에서부터 카운트 ]
리스트의 맨 앞 요소 : 0
앞에서 2번째 요소 : 1
앞에서 3번째 요소 : 2
......
......
[ 뒤에서부터 카운트 ]
리스트의 맨 뒤 요소 : -1
뒤에서 2번째 요소 : -2
뒤에서 3번째 요소 : -3
▶ 아마도, 추측이지만, 뒤에서부터 카운트를 할 때, 리스트의 맨 뒤 요소 인덱스 번호를 0으로 하면 0이 2개가 되는 사태(?)가 발생해서 이를 방지하기 위해서?? ^.^; +0과 -0이라는 개념은 없으니, 어쩌면. ^.^; 개인적인 추측입니다.
리스트 인덱싱에서 음수 인덱스를 사용하는 이유는 '리스트의 끝에서부터 요소를 쉽게 접근할 수 있도록 하기 위함'입니다. 음수 인덱스의 사용은 다음과 같은 장점이 있습니다.
▶ 음수 인덱스의 장점
1. 끝에서부터의 접근 용이성:
- 음수 인덱스('-1', '-2', ...)는 리스트의 마지막 요소부터 시작하여 거꾸로 셀 수 있게 해줍니다.
- 예를 들어, 리스트의 마지막 요소를 쉽게 가져오고 싶을 때 '-1'을 사용하면 간단하고 직관적입니다.
2. 가독성:
- 음수 인덱스를 사용하면 코드가 더 명확해질 수 있습니다. 예를 들어, 'some_list[-1]'는 "리스트의 마지막 요소"를 직접적으로 나타내므로, 코드의 의도가 분명해집니다.
3. 일관성:
- 리스트의 길이에 관계없이 항상 마지막 요소를 참조할 수 있는 방법이 있습니다. 리스트의 길이가 변해도 음수 인덱스를 사용하면 항상 올바른 결과를 얻을 수 있습니다.
▶ 통일성에 대한 의견
- 음수 인덱스를 도입하지 않았다면, 리스트의 길이를 알아야 마지막 요소에 접근할 수 있는 방법을 찾아야 했을 것입니다. 예를 들어, 'some_list[len(some_list) - 1]'와 같이 작성해야 했습니다.
- 이렇게 되면 코드가 길어지고 가독성이 떨어질 수 있습니다.
▶ 결론
음수 인덱스는 리스트와 같은 자료구조에서 끝에서부터 요소에 접근하는 효과적인 방법이며, 이를 통해 코드의 간결함과 가독성을 높이는 데 기여합니다. 이러한 설계는 많은 프로그래밍 언어에서 채택하고 있는 유용한 패턴입니다.
^.^;
.
.
5.2 Updates # 0002
[ 콜백 함수의 선택적 실행법 ]
Updates #0002는 SR 기본 코드와 분리된 독립적 프로그램 형태로 만들어 작동 테스트를 겸하고 있습니다. 이 업데이트 프로그램에서 충분히 테스트된 후 SR 기본 코드에 통합될 예정입니다.
Updates #0002는 Tkinter GUI를 통한 콜백 함수를 선택적으로 실행하는 방법을 보여줍니다. 복수의 선택이 가능합니다.
import tkinter as tk
def function_one():
print("Function One 실행!")
def function_two():
print("Function Two 실행!")
def function_three():
print("Function Three 실행!")
def run_selected_functions():
if var1.get():
function_one()
if var2.get():
function_two()
if var3.get():
function_three()
# 기본 윈도우 생성
root = tk.Tk()
root.title("함수 선택 실행기")
# 체크 버튼 변수
var1 = tk.BooleanVar()
var2 = tk.BooleanVar()
var3 = tk.BooleanVar()
# 체크 버튼 생성
check1 = tk.Checkbutton(root, text="Function One", variable=var1)
check2 = tk.Checkbutton(root, text="Function Two", variable=var2)
check3 = tk.Checkbutton(root, text="Function Three", variable=var3)
# 버튼 생성
run_button = tk.Button(root, text="선택된 함수 실행", command=run_selected_functions)
# 위젯 배치
check1.pack()
check2.pack()
check3.pack()
run_button.pack()
# 메인 루프 시작
root.mainloop()
■ 간단한 코드 설명 :
□ 함수 정의: function_one , function_two , function_three 는 각각 실행할 함수입니다.
□ 체크 버튼: 각 함수에 대해 체크 버튼을 생성하고, 선택 여부를 BooleanVar 로 관리합니다.
□ 실행 버튼: "선택된 함수 실행" 버튼을 클릭하면 선택된 체크 버튼에 따라 함수가 실행됩니다.
□ 메인 루프: root.mainloop() 를 통해 GUI를 실행합니다.
이 코드를 실행하면 체크 박스를 통해 선택된 함수만 실행할 수 있습니다.
■ var1.get() 의 반환값은 불리언(Boolean) 자료형입니다.
var1 = tk.BooleanVar()
□ BooleanVar는 Tkinter에서 체크 버튼의 상태를 관리하는 변수로, 체크 버튼이 선택되면 True, 선택되지 않으면 False를 반환합니다. 따라서, var1.get() 을 호출하면 해당 체크 버튼이 선택되었는지 여부를 나타내는 True 또는 False 값을 얻을 수 있습니다.
6. 예정 사항 Things To Do
1) 폴더 문자열과 파일 문자열의 확장자 구분 문제. 마침표(.)가 문제가 될 때의 해법.
2) 폴더명이나 파일명에 이미 같은 문자열로 된 경우가 있을 경우의 해법. 중복의 문제.
3) Rule 5 함수 if... elif 관련 타입 오류 발생 원인 찾기.
4) 유니코드의 문제를 어떻게 할 것인가?
: S5_Español (España).spa.doc ==> S5_Espa_ol_Espa_a_spa.doc
: 대체-제거되어 버린다.
.
.
7. 첨부 파일 Files
GUI를 포함한 좀 더 긴 코드가 되면 별도 파일로 올려두겠습니다. 현재 GUI 관련 작업 중... ^.^;
.
.
8. 참고 자료 References
정규표현식Regular Expression 관련 교재들 좋아요. 출간된 지 제법 오래되었다고는 해도 가치가 있습니다. 여기서는 가장 얇은 교재로 한 권만.
1) 강환수, 신용현. 《파이썬으로 배우는 누구나 코딩》홍릉과학출판사. 2019.
2) 천인국. 《파워 유저를 위한 파이썬 Express》생능. 2020.
3) 벤 포터 저. 김경수 외 2인 공역《손에 잡히는 10분 정규표현식》인사이트. 2019.
4) Aria Ai.
5) chat-GPT Ai.
Happy Programming!
^.^;
'Programming > Python' 카테고리의 다른 글
Tkinter 기본 연습 - 1 : 버튼과 pack() 사용법 (0) | 2024.10.26 |
---|---|
UniCode 연습 - 001 (0) | 2024.10.09 |
[ L2P ] Python 입문 - Turtle 모듈 - 001 (0) | 2024.09.15 |
Free IDE - Thonny (0) | 2024.09.08 |
turtle.pencolor() 함수 (0) | 2023.12.10 |