# 파이썬 심화

가상 환경, 코드 컨벤션, 변수 유효 범위, 모듈 및 패턴, 조건문, 함수의 인자와 리턴 타입, 패킹과 언패킹

# 파이썬 심화

## 가상환경 (virtual environment)

파이썬에서는 pip를 통해 패키지를 설치하고 관리하는데, 기본적으로 pip로 설치한 패키지들은 파이썬 실행 환경 전체에 영향을 미친다.

그러나 여러 프로젝트를 다룰 때는 버전 충돌 문제가 발생할 수 있는데, 이를 해결하기 위해 파이썬 가상환경을 활용한다.

가상환경은 각 프로젝트마다 독립된 환경을 제공하여 패키지 간의 충돌을 방자히고, 프로젝트 간의 의존성을 관리한다.

이를 통해 각 프로젝트는 자신만의 패키지 버전을 가질 수 있으며, 서로 영향을 주지 않는다.

### 가상환경 구축

터미널에 `python -m venv venv` 명령어를 통해 가상환경 구축

명령어를 실행하면 venv라는 이름의 폴더가 생성되고 후에 가상 환경에서 설치하는 패키지들은 이 폴더에 설치된다.

### 가상환경 활성화

가상환경 활성화를 위해서는 vanv 폴더 내에 있는 activate 파일을 실행시켜야 한다.

```plaintext
source venv/bin/activate
```

위의 명령어를 통해 가상 환경 활성화가 가능하고, 활성화되면 터미널 좌측에 (venv)가 추가된다.

### 가상환경 비활성화

```plaintext
deactivate
```

---

## 코드 컨벤션

코드 컨벤션이란 일관성 있고 가독성이 좋은 코드를 작성하기 위한 규칙과 규약을 의미한다.

이는 코드를 작성하는 개발자들이 서로 협업할 때 코드의 일관성을 유지하고 가독성을 높이기 위해 필요하다.

주로 들여쓰기, 변수 및 함수명 작성 규칙, 주석 사용 방법 등이 포함된다.

코드 컨벤션은 언어에 따라 요구하는 내용이 다르기 때문에 내가 개발하는 언어에 맞는 컨벤션 스타일에 맞춰 코드를 작성해야 한다.

### 파이썬 컨벤션

네이밍 컨벤션으로 **변수/함수를 네이밍 할때는 snake표기법, class를 네이밍할 때는 pascal 표기법** 사용

\- pascal : PythonIsVeryGood - 각 단어를 대문자로 구별

\- camel : pythonIsVeryGood - 첫 문자가 소문자로 시작

\- snake : python\_is\_very\_good : 각 단어를 \_로 구분

클래스, 함수, 변수 등을 네이밍할 때는 이름만 보고 해당 코드가 어떤걸 의미하는지 추측할 수 있어야 한다.

\- PI = 3.14 → 상수를 표현할 때는 예외적으로 모든 문자 대문자로 표현

\- numbers = \[1, 2, 3, 4\] → list를 표기할 때는 복수로 표현한다. 혹은 number\_list로 표현 for number in numbers

\- def add(a, b) : return a + b → 함수를 네이밍할 때는 해당 함수가 어떤 역할을 하는지 표현

---

## 변수 유효 범위 (variable scope)

파이썬에서 변수의 유효범위는 변수가 사용할 수 있는 범위를 타나내며, 지역 변수와 전역 변수 두가지가 있다.

지역 변수는 함수 내부에서 선언되며 다른 함수에 영향을 끼치지 않는다.

전역 변수는 함수 밖에서 선언되며 어디서든 접근할 수 있다.

### **지역 변수 (Local Variable)**

```plaintext
def func1():
    number = 10 # 함수 내에서 number라는 지역 변수를 선언
    
def func2():
    print(number) # func1에서 생성된 지역 변수는 funt2에서 접근할 수 없다.
    
func1()
func2()

"""
Traceback (most recent call last):
  File "sample.py", line 8, in <module>
    func2()
  File "sample.py", line 5, in function2
    print(number)
NameError: name 'number' is not defined
"""
```

* 함수 내에서 선언되는 변수로, 해당 함수 내에서만 유효하다.
    
* 함수가 실행될 때 생성되고, 함수의 실행이 끝나면 사라진다.
    
* 함수 내부에서만 접근 가능하며, 함수 외부에서는 사용할 수 없다.
    

### **전역 변수 (Global Variable)**:

```plaintext
number = 10 # 함수 밖에서 number라는 전역 변수 생성

def func():
    print(number) # 전역 변수는 자유롭게 접근할 수 있다.
    
func() # 함수를 실행하면 10이 정상적으로 출력된다.
```

* 함수 외부에서 선언되는 변수로, 스크립트 전체에서 사용할 수 있다.
    
* 어디서든 접근 가능하지만, 함수 내부에서도 사용 가능하다.
    
* 전역 변수의 값을 함수 내에서 변경할 때는 global 키워드를 사용하여 명시적으로 선언해야 한다.
    

변수의 유효범위를 이해하면 함수와 스크립트 전체에서 변수를 사용할 수 있는지 파악할 수 있다.

### 전역변수 사용시 주의할점

**함수 내에서 전역 변수의 값을 바꾸려할 경우**

```plaintext
number = 10

def func():
    number = 5 # 전역 변수의 값이 바뀌는 것이 아닌, 지역 변수로 다시 선언됩니다.
    
func()

# 함수 내에서 재할당 된 지역 변수는 전역 변수에 영향을 끼치지 않습니다.
print(number) # 10
```

**전역 변수 사용과 지역 변수 할당을 같이 하는 경우**

```plaintext
number = 10

# 전역 변수인 10을 출력하고 지역 변수로 다시 선언하는 코드 작성
def func():
    print(number)
    number = 5

# 실행시켜 보면 에러가 발생한다.
func()
"""
Traceback (most recent call last):
  File "sample.py", line 7, in <module>
    func()
  File "sample.py", line 4, in func
    print(number)
UnboundLocalError: local variable 'number' referenced before assignment
"""
```

**함수 내에서 전역 변수의 값을 바꾸려면**

```plaintext
number = 10

def func():
    global number # 함수에서 number 변수를 다시 할당할 수 있도록 해줍니다.
    number = 5 # global 키워드를 사용했기 때문에 전역 변수의 값이 변경됩니다.
    
func()

print(number) # 5
```

---

## 모듈 및 패턴

### type() / 값의 자료형 확인해보기

특정 모듈을 사용했을 때 그 결과값으로 받아오는 값이 어떤 타입인지 구분하기 어려울때 사용

특정 변수가 어떤 타입의 데이터인지 알수있다.

```plaintext
integer = 10
float_ = 1.23
string = "hello world!!"
list_ = [1, 2, 3]
tuple_ = (1, 2, 3)
set_ = {1, 2, 3}
dictionary = {"key": "value"}
boolean = True

print(type(integer)) # <class 'int'>
print(type(float_)) # <class 'float'>
print(type(string)) # <class 'str'>
print(type(list_)) # <class 'list'>
print(type(tuple_)) # <class 'tuple'>
print(type(set_)) # <class 'set'>
print(type(dictionary)) # <class 'dict'>
print(type(boolean)) # <class 'bool'>
```

### split() / string을 list로 변환하기

```plaintext
# split은 string.split("구분자")로 구성되어 있습니다.

string = "hello/python/world!!"
string_list = string.split("/") # split() 안에 들어간 값을 기준으로 문자를 나눈다.

print(string_list) # ['hello', 'python', 'world!!']
```

### join() / list를 string으로 변환하기

```plaintext
# join은 "사이에 들어갈 문자".join(리스트) 로 구성되어 있습니다.

string_list = ["hello", "python", "world"]
string = "!! ".join(string_list)

print(string) # hello!! python!! world
```

### replace() / 문자열 바꾸기

```plaintext
# replace는 "변경할 문자".replace("변경 전 문자", "변경 후 문자")로 구성되어 있습니다.

before_string = "hello world!!!"
after_string = before_string.replace("!", "~") # !를 ~로 변경

print(after_string) # hello world~~~
```

### pprint() / 코드 예쁘게 출력하기

```plaintext
# pprint는 pretty print의 약자이며, 데이터를 더 예쁘게 출력해 줍니다.
from pprint import pprint

sample_data = {
    "id": "0001",
    "type": "donut",
    "name": "Cake",
    "ppu": 0.55,
    "batters":
        {
            "batter":
                [
                    {"id": "1001", "type": "Regular"},
                    {"id": "1002", "type": "Chocolate"},
                    {"id": "1003", "type": "Blueberry"},
                    {"id": "1004", "type": "Devil's Food"}
                ]
        },
    "topping":
        [
            {"id": "5001", "type": "None"},
            {"id": "5002", "type": "Glazed"},
            {"id": "5005", "type": "Sugar"},
            {"id": "5007", "type": "Powdered Sugar"},
            {"id": "5006", "type": "Chocolate with Sprinkles"},
            {"id": "5003", "type": "Chocolate"},
            {"id": "5004", "type": "Maple"}
        ]
}

pprint(sample_data) # print로 출력했을 때와 결과 비교해 보기!!
```

### random / 랜덤한 로직이 필요할 때

```plaintext
# 난수 생성, 임의의 번호 생성 등 랜덤한 동작이 필요할 때 사용
import random

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
random.shuffle(numbers) # numbers를 무작위하게 섞기
print(numbers) # [2, 8, 6, 4, 3, 7, 1, 5]

random_number = random.randint(1, 10) # 1 ~ 10 사이의 무작위 번호 생성
print(random_number) # 4
```

### time / 시간 다루기

```plaintext
import time

start_time = time.time() # 현재 시간 저장

time.sleep(1) # 1초간 대기

end_time = time.time()

# 코드가 종료된 시간 - 코드가 시작된 시간으로 실행 시간 구하기 (단위 : 초)
print(f"코드 실행 시간 : {end_time-start_time:.5f}") # 코드 실행 시간 : 1.00100
```

### datetime / 날짜 다루기

```plaintext
from datetime import datetime, timedelta

# 현재 날짜 및 시간 출력
print(datetime.now()) # 2023-02-22 15:55:32.277095

# datetime의 format code 더 제세한건 여기서 확인 가능합니다.
'''
%y : 두 자리 연도 / 20, 21, 22
%Y : 네 자리 연도 / 2020, 2021, 2022
%m : 두 자리 월 / 01, 02 ... 11 ,12
%d : 두 자리 일 / 01, 02 ...  30, 31
%I : 12시간제 시간 / 01, 02 ... 12
%H : 24시간제의 시간 / 00, 01 ... 23
%M : 두 자리 분 / 00, 01 ... 58, 59
%S : 두 자리 초 / 00, 01 ... 58, 59
'''

# string을 datetime 날짜로 변경하기
string_datetime = "23/12/25 13:20"
datetime_ = datetime.strptime(string_datetime, "%y/%m/%d %H:%M")
print(datetime_) # 2023-12-25 13:20:00

# datetime 날짜를 string으로 변환하기
now = datetime.now()
string_datetime = datetime.strftime(now, "%y/%m/%d %H:%M:%S")
print(string_datetime) # 22/09/04 04:04

# 3일 전 날짜 구하기
three_days_ago = datetime.now() - timedelta(days=3)
print(three_days_ago) # 2023-02-19 16:27:52.526502
```

---

## 조건문

### 비교문법

비교 연산자들을 사용해 값을 비교하고 결과가 True인지 False인지 판단할 수 있다.

```plaintext
'''== : 값이 일치하는지 비교'''
"a" == "a" # True
"a" == "b" # False
1 == "1" # False, 값은 동일하지만 자료형이 다르기 때문

'''!= : 값이 일치하지 않는지 비교'''
0 != 1 # True
0 != 0 # False

'''>, < : 값이 큰지 작은지 비교'''
5 > 2 # True
1 < 0 # False
1 > 1 # False

'''>=, <= : 값이 크거나 같은지, 작거나 같은지 비교'''
1 >= 1 # True

'''in : 특정 값이 list / tuple / set에 포함되어 있는지 확인'''
4 in [1, 2, 3] # False
1 in (1, 2, 3) # True

# 모든 비교 연산자의 결과는 print()로 확인할 수 있습니다.
print(1 == 1) # True
```

### if, elif, else

```plaintext
if condition: # 조건이 True일 경우
    # some code

# not 키워드를 사용할 경우 조건이 False일 때 실행됩니다.
elif not condition: # 조건이 False일 경우
    # some code

else: # 위 조건들 중 만족하는게 없을 경우
    # some code
```

**if문은 기본적으로 다음에 오는 구문이 true인지 false인지 boolean으로 판단 가능한데, if문을 사용하지 않고도 true인지 false인지 알 수 있다.**

```plaintext
user_list = ["철수", "영희"]

# if문은 기본적으로 다음에 오는 구문이 true인지 false인지 boolean으로 판단한다.
if "철수" in user_list:
    print("user list에 철수가 있습니다")
else:
    print("user list에 철수가 존재하지 않습니다.")
    
# if문을 사용하지 않고도 true인지 false인지 결괏값을 받을 수 있다.
print("철수" in user_list) # True
```

### and, or

2개 이상의 조건을 복합적으로 사용할 수 있다.

```plaintext
if condition1 and condition2: # 두 조건을 모두 만족할 경우
    # some code

elif condition or condition: # 두 조건 중 하나라도 만족할 경우
    # some code

else: 
    # some code
```

**비어있는 string, list 등은 분기문에서 false로 판단한다.**

**not을 붙이면 뒤에 조건을 만족하지 않을 때 if 문을 통과한다.**

```plaintext
# 스트링 안이나 리스트 안에 내용이 담겨 있을 경우에는 분기문을 통과하지 않는다.
# 내용물이 비어있지 않기 때문에 empty_string, empty_list는 true가 되고, not True는 false이기 때문에 분기문을 통과하지 않는다.
empty_string = ""
empty_list = []

# not은 뒤에 있는 조건이 false 일 때 true가 된다.
if not empty_string:
    print("string is empty!!")

if not empty_list:
    print("list is empty!!")
```

### 특정 값이 True인지 False인지는 bool() 함수를 사용해 확인할 수 있다.

```plaintext
print(bool("")) # False
print(bool(0))  # False
print(bool([])) # False

print(bool("sample"))   # True
print(bool([1, 2]))     # True
print(bool(1))  # True
print(bool(-1)) # 0이 아닌 숫자는 True로 판단
print(bool([""]))   # True 빈 리스트 안의 빈 문자열 / 리스트가 false인 조건은 안에 어떠한 요소도 없을 때이다. 현재는 빈 string이라는 요소가 있어서 결과가 true로 나온다.
```

### any() 혹은 all() 함수를 사용해 여러 값들에 대한 조건을 판단할 수 있다.

```plaintext
# all() : 요소들이 모두 True일 경우 True 리턴
if all([True, True, True, False, True]):
    print("통과!") # False가 존재하기 때문에 분기문을 통과하지 못함

# any() : 요소들 중 하나라도 True일 경우 True 리턴
if any([False, False, False, True, False]):
    print("통과!") # True가 1개 이상 존재하기 때문에 분기문을 통과함
```

---

## 함수의 인자와 리턴 타입

함수들은 비슷해보여도 사용 방법이나 리턴 타입이 다르다.

list를 정렬하기 위해 사용하는 sorted, list.sort()는 사용 방법이 다르다.

```plaintext
# sort
# 변수에 담겨있는 list 자체를 정렬
sample_list = [3, 2, 4, 1, 5]
sample_list.sort() # return data 없이 list 자체를 정렬

print(sample_list) # [1, 2, 3, 4, 5]

# sorted
# 정렬된 list를 return 해주기 때문에 변수에 따로 담아줘야 한다.
sample_list = [3, 2, 4, 1, 5]
sorted_list = sorted(sample_list) # 정렬 된 list를 return

print(sorted_list) # [1, 2, 3, 4, 5]

# 잘못된 방법
sample_list = [3, 2, 4, 1, 5]
sorted_list = sample_list.sort() # .sort()의 return data는 None 입니다.

print(sorted_list) # None
```

### **내가 사용하는 코드의 리턴 타입 확인 방법**

검색 : 구글에 python 함수명과 같은 형태로 검색해서 해당 기능을 어떻게 사용해야 하는지 확인

docstring 확인 : 함수를 작성할 때 docstring을 활용하면 사용하려는 함수가 어떤 역할을 하는지, 어떤 값을 인자로 받는지, 어떤 값을 리턴해주는지 확인 가능

함수 구현 코드 확인 : 외부 라이브러리를 import해서 사용할 때 권장하는 방법으로 구현부의 코드를 보고 사용 방법을 익히는것이 좋다.

command + 클릭 하면 구현부 코드 확인이 가능하다.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708504564479/d36674b0-411f-410f-882a-9704378f0c61.png align="center")

---

## try / exception을 활용한 에러 처리

파이썬에서는 try exception을 사용해 에러가 발생했을 때 처리할 수 있다.

```plaintext
number = "num"

try: # try 구문 안에서 에러가 발생할 경우 except로 넘어감
    number = int(number) # "num"을 숫자로 바꾸는 과정에서 에러 발생
except: # 에러가 발생했을 때 처리
    print(f"{number}은(는) 숫자가 아닙니다.")
```

**에러 종류에 따른 로직 처리**

```plaintext
number = input()

try:
    int(number)
    10 / number

except ValueError: # int로 변환하는 과정에서 에러가 발생했을 떄
    print(f"{number}은(는) 숫자가 아닙니다.")
    
except ZeroDivisionError: # 0으로 나누면서 에러가 발생했을 때
    print("0으로는 나눌수 없습니다.")
    
except Exception as e: # 위에서 정의하지 않은 에러가 발생했을 때(권장하지 않음)
    print(f"예상하지 못한 에러가 발생했습니다. error : {e}")

# except 문법 또한 if / elif와 같이 연달아서 작성할 수 있습니다.
```

---

## 패킹과 언패킹

패킹(packing)과 언패킹(unpacking)은 요소들을 묶어주거나 풀어주는 것을 의미한다.

list, dictionary의 값을 함수에 입력할 때 주로 사용한다.

### list에서의 활용

```plaintext
def add(*args):
    result = 0
    for i in args:
        result += i
        
    return result

numbers = [1, 2, 3, 4]

print(add(*numbers))
""" 아래 코드와 동일
print(add(1, 2, 3, 4))
"""

# result output
"""
10
"""
```

### dictionary의 활용

```plaintext
def set_profile(**kwargs):
    profile = {}
    profile["name"] = kwargs.get("name", "-")
    profile["gender"] = kwargs.get("gender", "-")
    profile["birthday"] = kwargs.get("birthday", "-")
    profile["age"] = kwargs.get("age", "-")
    profile["phone"] = kwargs.get("phone", "-")
    profile["email"] = kwargs.get("email", "-")
    
    return profile

user_profile = {
    "name": "lee",
    "gender": "man",
    "age": 32,
    "birthday": "01/01",
    "email": "python@sparta.com",
}

print(set_profile(**user_profile))
""" 아래 코드와 동일
profile = set_profile(
    name="lee",
    gender="man",
    age=32,
    birthday="01/01",
    email="python@sparta.com",
)
"""

# result print
"""
{
    'name': 'lee',
    'gender': 'man',
    'birthday': '01/01',
    'age': 32,
    'phone': '-',
    'email': 'python@sparta.com'
}
"""
```

---

## **class**

파이썬의 클래스는 과자 틀을 만드는 것이고, 과자 틀을 사용해서 찍어낸 쿠키가 인스턴스다.

### **\_\_init**\_\_

class에 \_\_init\_\_ 메서드를 사용할 경우 인스턴스 생성 시 해당 메서드 실행된다.

```plaintext
class CookieFrame():
    def __init__(self, name):
        print(f"생성 된 과자의 이름은 {name} 입니다!")
        self.name = name

cookie1 = CookieFrame("cookie1") # 생성 된 과자의 이름은 cookie1 입니다!
cookie2 = CookieFrame("cookie2") # 생성 된 과자의 이름은 cookie2 입니다!
```

### 상속

class 상속이란 클래스를 생성할 때 다른 클래스에 선연된 변수, 메서드 드으이 기능을 가져와 사용할 수 있게 해주는 기능이다.

동일한 코드를 여러 클래스에서 조금씩 수정해 사용하거나 모듈에 내장되어 있는 클래스를 변경할 때 주로 사용된다.

**상속 받은 클래스를 기반으로 새로운 클래스 만들기**

```plaintext
class Monster():
    def __init__(self, hp):
        self.hp = hp
        
    def attack(self, damage):
        self.hp -= damage

    def status_check(self):
        print(f"monster's hp : {self.hp}")
        
class FireMonster(Monster):
    def __init__(self, hp):
        self.attribute = "fire"
        # super()를 사용하면 부모 클래스의 코드를 그대로 사용할 수 있습니다.
        # 해당 코드는 self.hp = hp 코드를 실행시키는 것과 동일합니다.
        super().__init__(hp)
    
    # 부모 클래스에 존재하는 status_check 메소드를 overriding 합니다.
    def status_check(self):
        print(f"fire monster's hp : {self.hp}")
        
class IceMonster(Monster):
    def __init__(self, hp):
        self.attribute = "ice"
        super().__init__(hp)
    
    def status_check(self):
        print(f"ice monster's hp : {self.hp}")
        
fire_monster = FireMonster(hp=100)
# FireMonster 클래스에는 attack 메소드가 없지만
# 부모 클래스에서 상속받았기 때문에 별도의 선언 없이 사용 가능합니다.
fire_monster.attack(20)
fire_monster.status_check()

ice_monster = IceMonster(hp=200)
ice_monster.attack(50)
ice_monster.status_check()
```

**상속 받은 클래스의 특정 코드를 변경해 사용하기**

```plaintext
class Calc:
    def _print_zero_division_error(self):
        print("can't be division by zero")
        
    def plus(self, num1, num2):
        ...

    def minus(self, num1, num2):
        ...

    def divide(self, num1, num2):
        if num2 == 0:
            self._print_zero_division_error()
            return False
        ...

    def multiply(self, num1, num2):
        ...

calc = Calc()
calc.divide(5, 0)
# result print
"""
can't be division by zero
"""
```

이 코드는 0으로 나눴을 때 영어로 에러를 출력하는데 영어가 아닌 한글로 출력하고 싶을때는, 상속을 활용하면 코드를 다시 만들거나 모듈 내 코드를 변경하지 않고 원하는 코드로 수정할 수 있다.

```plaintext
class CustomCalc(Calc):
    def _print_zero_division_error(self):
        print("0으로는 나눌 수 없습니다.")

calc = CustomCalc()
calc.divide(5, 0)
"""
0으로는 나눌 수 없습니다.
"""
```

---

## class 객체 (object) 다루기

### 객체

객체와 인스턴스는 동일한 용어로 CookieFrame 클래스를 사용해 생성된 cookie를 “객체” 혹은 “CookieFrame의 인스턴스”라고 표현한다.

### 객체사용법

list, dict와 같은 자료형 데이터를 생성하고 활용했던 것들이 모두 해당 클래스의 객체를 생성하고 메서드를 활용하는 과정이었다.

```plaintext
sample_list = [1, 2, 3, 4]
sample_dict = {"key": "value"}

print(type(sample_list)) # <class 'list'>
print(type(sample_dict)) # <class 'dict'>

sample_list.sort() # list 클래스의 sort 메소드 사용
```

### datetime 모듈에서 객체 활용

객체에서 사용 가능한 메서드들은 함수의 리턴 타입을 확인하는 방법과 동일하게 검색, docstring, 구현 코드등을 확인하여 찾을 수 있다.

---

## 정규표현식 (regex)

regular expression의 약자인 regex라고도 하며 문자열이 특정 패턴과 일치하는지 판단하는 형식 언어

메일이 유효한 이메일인지, 유효한 핸드폰 번호를 입력했는지, 대문자로 시작하고 숫자로 끝나는 패턴의 단어가 몇번 반복되는지 등 다양한 패턴을 지정하고 검증할 수 있다.

### 정규표현식 예제

유효한 이메일인지 판단하는 최소한의 패턴은 아래와 같습니다.

1. 숫자, 알파벳 대/소문자, 일부 특수문자( - \_ . )를 조합한 문자로 시작합니다.
    
2. 문자열 중간에는 @가 반드시 1개 포함되어 있어야 합니다.
    
3. @ 이후에는 숫자, 알파벳 대/소문자, 일부 특수문자( - \_ . )를 조합한 문자가 들어갑니다.
    
4. 3번 문자 이후에는 .이 한 개 이상 포함되어 있어야 합니다.
    
5. 마지막 . 이후에는 2 ~ 4글자의 숫자, 알파벳 대/소문자, 일부 특수문자( - \_ )를 조합한 문자 포함되어 있어야 합니다.
    

정규표현식 없이 위 조건들을 만족하기 위해서는 아래와 같이 코드를 작성해야한다.

```plaintext
from pprint import pprint

alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
number = "0123456789"
special_char = "-_."

def verify_email(email):
    # 이메일에 @가 한개 포함되어 있는지 확인
    if email.count("@") != 1:
        return False
    
    # @를 기준으로 사용자명과 도메인을 분리
    username, domain = email.split("@")
    
    # username이 1자 이상인지 확인
    if len(username) < 1:
        return False
    
    # 도메인에 한개 이상의 .이 포함되어 있는지 확인
    if domain.count(".") < 1:
        return False
    
    # username에 알파벳, 숫자, 허용된 특수문자 이외의 문자가 포함되어 있는지 확인
    if not all([x in alphabet + number + special_char for x in username]):
        return False
    
    # domain에 알파벳, 숫자, 허용된 특수문자 이외의 문자가 포함되어 있는지 확인
    if not all([x in alphabet + number + special_char for x in domain]):
        return False
    
    # 마지막 .을 기준으로 도메인을 분리
    _, last_level_domain = domain.rsplit(".", 1)
    
    # 마지막 레벨의 도메인의 길이가 2~4글자인지 확인
    if not 2 <= len(last_level_domain) <= 4:
        return False
    
    # 모든 검증이 완료되면 True를 리턴
    return True

test_case = [
    "apple",              # False
    "sparta@regex",       # False
    "$parta@regex.com",   # False
    "sparta@re&ex.com",   # False
    "sparta@regex.com",   # True
    "sparta@regex.co.kr", # True
    "sparta@regex.c",     # False
    "sparta@regex.cooom", # False
    "@regex.com",         # False
]

result = [{x: verify_email(x)} for x in test_case]

pprint(result)
```

**정규표현식을 사용할 경우 코드를 매우 간소화 시킬 수 있다.**  

```plaintext
from pprint import pprint
import re

# rstring : backslash(\)를 문자 그대로 표현
# ^[\w\.-]+@([\w-]+\.)+[\w-]{2,4}$ : 이메일 검증을 위한 정규표현식 코드
email_regex = re.compile(r"^[\w\.-]+@([\w-]+\.)+[\w-]{2,4}$")

def verify_email(email):
    return bool(email_regex.fullmatch(email))
    
test_case = [
    "apple",              # False
    "sparta@regex",       # False
    "$parta@regex.com",   # False
    "sparta@re&ex.com",   # False
    "spar_-ta@regex.com", # True
    "sparta@regex.co.kr", # True
    "sparta@regex.c",     # False
    "sparta@regex.cooom", # False
    "@regex.com",         # False
]

result = [{x: verify_email(x)} for x in test_case]

pprint(result)
```

---

## 파일과 디렉토리

### glob을 활용한 목록 확인

```plaintext
from pprint import pprint
import glob

# ./는 현재 python 파일이 위치한 경로를 의미합니다.
# *은 "모든 문자가 일치한다" 라는 의미를 가지고 있습니다.
# ./venv/*은 venv 폴더 내 모든 파일들을 의미합니다.
path = glob.glob("./venv/*")
pprint(path)

# result output
"""
['./venv\\Include', './venv\\Lib', './venv\\pyvenv.cfg', './venv\\Scripts']
"""

# **은 해당 경로 하위 모든 파일을 의미하며, recursive 플래그와 같이 사용합니다.
# recursive를 True로 설정하면 디렉토리 내부의 파일들을 재귀적으로 탐색합니다.
path = glob.glob("./venv/**", recursive=True)
pprint(path)

# result output
"""
['./venv\\',
 './venv\\Include',
 './venv\\Lib',
 './venv\\Lib\\site-packages',
 './venv\\Lib\\site-packages\\autopep8-2.0.1.dist-info',
 ...
]
"""

# *.py와 같이 작성 시 특정 확장자를 가진 파일들만 볼 수 있습니다.
# ./venv/**/*.py는 venv 하위 모든 폴더들을 재귀적으로 탐색하며 .py 확장자 파일을 탐색합니다.
path = glob.glob("./venv/**/*.py", recursive=True)
pprint(path)

# result output
"""
['./venv\\Lib\\site-packages\\autopep8.py',
 './venv\\Lib\\site-packages\\pycodestyle.py',
 './venv\\Lib\\site-packages\\pip\\__init__.py',
 './venv\\Lib\\site-packages\\pip\\__main__.py',
 './venv\\Lib\\site-packages\\pip\\__pip-runner__.py',
 ...
]
"""
```

open을 활용

```plaintext
# python에서는 파일을 편집하거나 생성할 때, with open 문법을 사용할 수 있습니다.

# "file.txt" : 파이썬에서 사용할 파일을 지정합니다.
# "w" : 파일을 쓰기 모드로 엽니다. 만약 파일이 없다면 새로 생성합니다.
# r, a, 등 다양한 mode가 존재하며, 더 많은 mode의 종류는 여기서 확인할 수 있습니다.
# encoding : 파일의 encoding 형식을 지정합니다.

# case 1. open 함수를 사용해 파일 열기
f = open("file.txt", "w", encoding="utf-8")
f.write("파이썬 파일 쓰기 테스트!\n")

# open 함수를 실행하면 python 스크립트가 끝날때 까지 파일이 열려있게 됩니다.
# 때문에 파일에 대한 작업이 끝나면 close()를 사용해 파일을 닫아줘야 합니다.
f.close()

# open을 with와 같이 사용하면 with 구문이 끝날 때 자동으로 파일이 close 됩니다.
# 때문에 with를 사용할 떄는 별도로 close 해주지 않아도 됩니다.
# a 모드는 기존 내용을 유지한 상태로 추가합니다.
with open("file.txt", "a", encoding="utf-8") as w:
    w.write("파이썬 내용 추가 테스트!")

# r 모드는 파일을 읽기 모드로 엽니다.
with open("file.txt", "r", encoding="utf-8") as r:
    # readlines는 파일의 모든 내용을 list 자료형으로 한번에 읽어들입니다.
    print(r.readlines())

# result output
"""
['파이썬 파일 쓰기 테스트!파이썬 내용 추가 테스트!']
"""

with open("file.txt", "r", encoding="utf-8") as r:
    while True:
        # readline은 파일을 한 줄 씩 읽어들입니다.
        line = r.readline()

        # 파일 끝까지 텍스트를 읽어들였다면 반복문을 중지합니다.
        if not line:
            break

        # 텍스트의 줄바꿈 문자 제거
        line = line.strip()
        print(line)

# result output
"""
파이썬 파일 쓰기 테스트!
파이썬 내용 추가 테스트!
"""
```
