[day-12] datetime, 클래스, 정적 메서드, 속성 접근자(Property), 덕타이핑, 오버라이딩, 추상클래스, 시큐어 코딩

Joohyung Park·2024년 1월 15일
0

[모두연] 오름캠프

목록 보기
12/95

날짜, 시간을 출력하는 방법

from datetime import datetime

datetime.now()
datetime.now().date()
datetime.now().year
datetime.now().month
datetime.now().day
datetime.now().hour
datetime.now().minute

d = datetime.now()
print(f'{d.year}/{d.month}/{d.day}') 
# 가볍게 사용하긴 좋지만, 날짜나 시간은 타입이 있다.

d.strftime('올해 연도는 %Y!!')
d.strftime('%y/%m/%d')
d.strftime('%Y/%m/%d') 
#가장 많이 사용하는 date format, 하나만 기억해야 한다면 이것을 기억!

datetime.now().strftime()을 통해 현재 날짜와 시간을 출력할 수 있다.


email 클래스 만들기

class User:
    # 모든 곳에서 공유될 변수들
    user_count = 0
    gender = ('남', '여')

    def __init__(self, name, joindate, gender, age, email, password):
        self.joindate = joindate
        self.accessdate = joindate
        self.name = name
        self.gender = gender
        self.age = age
        self.email = email
        self.password = password # 실무에서 이렇게 저장 절대 안함
        # (Django에서도 이렇게 저장해서 오류나는 경우 많음)

        # self.password = 39743FFC/FB179CEB/A590B68F/FF1A7C65/A9DB72B3/A5AD63E/E01A66C6/896A07311 + salt
        # Django에서도 sha256을 사용합니다. 은행권에서는 이 알고리즘이 깨졌다고 보고 있음.
        # sha512를 사용하려 노력을 함.
        # MD5라는 알고리즘을 암호화 알고리즘으로 많이 사용했었는데 이게 깨졌다. (레인보우 어택)

user1 = User('이호준', '2024/01/15', '남', 15, 'hojun@gmail.com', '1q2w3e4r!')
# 서비스가 몇년 정도 갈지 생각해서 연도의 자리수(24 or 2024)를 정하자. 보통 4자리를 넣는게 좋다.
user1.name
user1.accessdate

User.user_count = 1
user2 = User('홍길동', '2024/01/15', '남', 30, '햐ㅣ애ㅜㅎ@gmail.com', '1q2w3e4r!')
user2.name
User.user_count = 2


user1.user_count

# 무엇을 알 수 있는가? 실제로 class와 인스턴스의 메모리 영역은 교집합 상태
# 한 곳에서 수정이 되면 모두 수정이 된다!
# 그래서 처음에 클래스를 설계할 때 모든 인스턴스에 있을 변수(크래스 변수)와
# 인스턴스에만 있는 변수(인스턴스 변수)를 나누는 것이 매우 중요하다.

password는 해시 값을 넣어준다고 한다. 그냥 넣으면 모두가 다 볼 수 있으니까.. 문제가 될 것이다. 여기서 self가 붙은 부분은 인스턴스 변수라고 생각하면 된다.

짤막상식

def f():
    return

f() # 반환 값이 None이면 출력을 안함

실제 django 코드 예시

import hashlib
import re
from datetime import datetime

class User:
    user_count = 0
    gender = ('남', '여')

    def __init__(self, name, joindate, gender, age, email, password):
        self.joindate = joindate
        self.accessdate = joindate
        self.name = name
        self.gender = gender
        self.age = age
        self.email = email
        self.password = self._hash_password(password)

    def _hash_password(self, password):
        return hashlib.sha256(password.encode()).hexdigest()

    def change_password(self, password): # django의 user.set_password('비밀번호')와 비슷한 역할
        if len(password) < 8: # validate 또는 유효성 검증이라고 합니다.
            print('너무 짧습니다!')
            return
        self.password = self._hash_password(password)
        self.accessdate = datetime.now().strftime('%Y/%m/%d')

    def update_email(self, email):
        if self._validate_email(email):
            self.email = email
            self.accessdate = datetime.now().strftime('%Y/%m/%d')
        else:
            print("유효하지 않은 이메일 주소입니다.")

    def _validate_email(self, email):
        pattern = r"[\w.-]+@[\w.-]+\.\w+"
        return re.match(pattern, email) is not None

    def display_profile(self):
        print(f'name: {self.name}')
        print(f'joindate: {self.joindate}')
        print(f'accessdate: {self.accessdate}')
        print(f'email: {self.email}')

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name

user1 = User('이호준', '2024/01/15', '남', 15, 'hojun@gmail.com', '1q2w3e4r!')
user2 = User('홍길동', '2024/01/15', '남', 30, 'gildong@gmail.com', '1q2w3e4r!!')

print(user1)
user1.display_profile()
user1.password

django 코드는 위와 같이 짠다고 하는데 아직은 배우지 않았으니 이렇게 하는 거구나.. 하고 알아만 두자.


cart 클래스 만들기

# 온라인 쇼핑몰에서 장바구니에 넣기

class Cart:
    def __init__(self):
        self.items = []

    def add_item(self, item, count):
        self.items.append({
            '물품': item,
            '갯수': count,
        })

    def total_price(self):
        total_sum = 0
        for i in self.items:
            total_sum += i['물품'].price * i['갯수']
        return format(total_sum, ',')

class Product:
    def __init__(self, product_name, price):
        self.product_name = product_name
        self.price = price

    def __str__(self):
        return self.product_name

    def __repr__(self):
        return self.product_name

로지텍키보드 = Product('로지텍키보드', 50000)
LG모니터 = Product('LG모니터', 300000)
그래픽카드4090 = Product('GTX4090', 2000000)

hojun_cart = Cart()
hojun_cart.add_item(로지텍키보드, 10)
hojun_cart.add_item(LG모니터, 10)
hojun_cart.add_item(그래픽카드4090, 2)
hojun_cart.items
hojun_cart.total_price()

우선, 장바구니 클래스와 상품 클래스 2개를 선언을 하였다. 장바구니 클래스에는 품목들의 리스트가 인스턴스 변수로 선언이 되어있고, add_item 메서드에서 items 리스트에 품목명과 갯수를 추가해준다. total_price 메서드에서는 총 금액을 계산해주는 기능을 갖고 있다.
이제 상품 클래스를 보도록 하자.
상품 이름과 가격을 인스턴스 변수로 선언을 하였다. 이후 print()문의 출력으로는 매직매서드를 통해 상품이름이 출력되도록 하였다.
이후, 로지텍키보드, LG모니터, 그래픽카드4090 상품 객체 3개를 선언을 해주었다.
hojun_cart라는 장바구니 객체도 선언을 해줬으며, add_item 메서드를 활용하여 물건을 추가해 주었다.
마지막으로 최종 금액을 반환하도록 했다.

클래스 메서드

# 클래스 메서드는 클래스 변수를 변경하고 싶을 때 사용
# 주의해야 할 점이 첫번째 인자로 오는 cls는 관습으로 고정
# self가 a로 바꾸면 작동은 하지만 관습적으로 안되는 것처럼 cls도 바꾸면 안됨
# cls는 class를 나타낸다.

class MyClass:
    count = 0

    @classmethod
    def increment(cls):
        cls.count += 1

MyClass.increment()
print(MyClass.count)  # 출력: 1

정적 메서드

# 정적 메서드
# 언제 쓰는가? 정적 메서드는 self로 내부 변수에 접근이 안되서
# 책 클래스 만든 것 중 할인율 같이 관련은 있는데 밖으로 함수를 빼기 부담스러운 것들을 이렇게 사용한다.

class MyClass:
    @staticmethod
    def my_method(x, y):
        return x + y

print(MyClass.my_method(5, 3))  # 출력: 8

예시를 살펴보겠다.

class Book:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    @staticmethod
    def 할인율(원가, 할인):
        return 원가 * (1-(할인/100))

Book.할인율(9000, 10)   # 밖으로 뺄 수 있는 함수이고, 굳이 안에 들어갈 필요가 없다
# 그런데 할인율은 Book과 연관이 있다.
# 이렇게 코딩을 하면 유지보수 하기 좋다.

위와 같은 결과를 출력하지만 권장하지는 않는 코드를 소개하겠다.

# 비권장하는 케이스
# 위와 같은 코드

class Book:
    def __init__(self, name, price):
        self.name = name
        self.price = price

def 할인율(원가, 할인):
    return 원가 * (1-(할인/100))

boo1 = Book('python 100제', 9000)

할인율(boo1.price, 10)

위의 코드는 할인율 함수를 클래스 밖으로 뺐다는 특징이 있는데 이러면 나중에 잘못되었을 때 유지보수가 어렵다는 단점이 있다.


속성 접근자(Property)

# 중요도 높지 않음
# 별표 0.5개
# 속성 접근자 (Property)
class Person:
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name

    @property   # 이 부분 때문에 함수를 속성처럼 사용 가능
    def full_name(self):
        return f'{self._first_name}{self._last_name}'

licat = Person('li', 'cat')
print(licat._first_name)
print(licat._last_name)
print(licat.full_name)
# print(licat.full_name())  # 이렇게 안써도 된다는 것!

덕타이핑

# 덕타이핑
# 별표 0.3개
# https://world.weniv.co.kr/

# licat.move()
# move() => 직관적이고 누가 움직이는지는 알고 싶지 않고 주인공을 앞으로 한 칸 움직이고 싶다.

class Duck:
    def quack(self):
        print('꽥꽥!')

class Person:
    def quack(self):
        print("안녕하세요!")

def quack(obj):
    obj.quack()

duck = Duck()
person = Person()

quack(duck)  # 출력: 꽥꽥! duck.quack() 대신 quack(duck)를 사용하겠다.
quack(person)  # 출력: 안녕하세요! person.quack() 대신 quack(person)를 사용하겠다.

오버라이딩

# 오버라이딩
# 자식이 부모의 메서드를 덮어씌우는 것

class Animal:
    def sound(self):
        print("기본 동물 울음 소리, 악!")

class Dog(Animal):
    def sound(self):
        print("왈왈!")

class Cat(Animal):
    def sound(self):
        print("냐옹!")

# super()를 사용해서 부모의 메서드를 사용 할 수 있다.
class Bird(Animal):
    def sound(self):
        print("짹짹!")

super() 메서드

class Person:
    def __init__(self, name):
        self.name = name

class Student(Person):
    def __init__(self, name, student_id):
        super().__init__(name)  # 부모 클래스의 __init__ 메서드 호출
        # self.name = name 위 코드와 이코드는 동일하다.
        self.student_id = student_id

s = Student("Alice", "S12345")
print(s.name)        # 출력: Alice
print(s.student_id)  # 출력: S12345

상속 관계에 있는 부모 클래스의 메서드를 호출하는데에 사용한다.


추상클래스

# 추상 클래스
# 중요도는 높지만 빈도가 낮아 별 0.5개
# 추상 클래스: 반드시 구현되어야 하는 메서드를 명시하면
# 그것을 상속한 클래스에서는 반드시 그 메서드를 구현해야 한다.
# 언제 사용할까? 예를 들어 빠트리면 안되는 메서드가 있는 경우
# 게시판 만드는데 게시물 업데이트 날짜, 생성 날짜를 추상 클래스로 구현할 수 있다.

from abc import ABC, abstractmethod

class AbstractClassExample(ABC):

    @abstractmethod
    def do_something(self): # 필수로 구현해야 하는 메서드, 안하면 에러남
        pass

class Person(AbstractClassExample):

    def __init__(self, name):
        self.name = name

    def print_name(self):
        print(f'제 이름은 {self.name}입니다.')

    def do_something(self):
        print('hello')

hojun = Person('hojun')
hojun.print_name()

비공개 속성(시큐어 코딩)

# 비공개 속성
# 시큐어 코딩
# 별표 0.3개

class MyClass:
    __a = 10 # 비공개 속성(Private Attributes)
    _a = 100
    b = 20

    def __init__(self, c, d):
        self.__c = c
        self.d = d

c = MyClass(30, 40)
c._a    # 다른 언어에서는 보통 _ 1개가 private value
# c.__a   # 어? 접근이 안되네?! 이걸로 변수를 감추면 되겠다?! => 이렇게 생각하면 안된다.


# c.__a #error
# c._a # 출력: 100
# c.b # 출력: 20
# # c.__c # error
# c.d # 출력: 40

print(c._MyClass__c)    # 실제로는 이렇게 출력할 수 있다.

연습문제

class Comment를 작성해주세요.

  • 요구사항

    • 각 댓글은 고유한 ID, 작성자 이름, 댓글 내용, 작성 시간을 가져야 합니다.
    • 댓글은 하나 이상의 대댓글을 가질 수 있어야 합니다.
    • 대댓글도 Comment 클래스의 인스턴스로 생성되며, 원 댓글에 종속됩니다.
    • 시간은 '년-월-일 시:분' 형식으로 표시되어야 합니다.
    • (선택 과제) 댓글 출력 기능 - 계층적 출력을 해보세요.
    • (선택 과제)댓글에 대한 좋아요 기능을 추가할 수 있습니다.
import datetime

class Comment:
    def __init__(self, id, author, content):
        self.id = id
        self.author = author
        self.content = content
        self.timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        self.replies = []   # 대댓글을 저장하는 리스트
        self.likes = 0  # 좋아요 카운트 

    def add_reply(self, comment):   # 대댓글 추가하는 기능 
        self.replies.append(comment)

    def add_like(self): # 좋아요 추가 
        self.likes += 1

    def display(self, level=0): # 댓글 출력 
        print(f"{'-'*level}{self.author}({self.timestamp})[{self.likes} likes]: {self.content}")
        for reply in self.replies:
            reply.display(level + 1)

# 댓글 객체 생성
comment1 = Comment("c1", "User1", "첫 번째 댓글입니다.")
comment2 = Comment("c2", "User2", "두 번째 댓글입니다.")

# 대댓글 객체 생성 및 추가
reply1 = Comment("r1", "User3", "첫 번째 댓글에 대한 대댓글입니다.")
comment1.add_reply(reply1)

reply2 = Comment("r2", "User4", "두 번째 댓글에 대한 대댓글입니다.")
comment2.add_reply(reply2)

# 좋아요 추가
comment1.add_like()
reply2.add_like()

# 댓글 및 대댓글 출력
comment1.display()
comment2.display()
# 출력
User1(2024-01-15 12:55)[1 likes]: 첫 번째 댓글입니다.
-User3(2024-01-15 12:55)[0 likes]: 첫 번째 댓글에 대한 대댓글입니다.
User2(2024-01-15 12:55)[0 likes]: 두 번째 댓글입니다.
-User4(2024-01-15 12:55)[1 likes]: 두 번째 댓글에 대한 대댓글입니다.

피드백

저번주부터 시작한 클래스 대장정이 드디어 끝이 났다. 클래스를 제대로 다뤄본 적이 없어서 애를 많이 먹었는데 계속 새로운 클래스 만들고 기능 추가하고, 새로운 클래스 만들고 하다보니 어느정도 가닥은 잡힌 것 같다. 어제까지의 연습문제는 나름 할만했다고 생각을 했는데 오늘 연습문제는 꽤나 오래 생각했던 것 같다. 다같이 타이핑 할때는 내것이 된 것 같아도 혼자 해보니 그건 또 아닌 것 같다 ㅎㅎ. 나 혼자서 클래스를 짤 수 있을때까지 복습을 해야겠다는 생각이 든다. 계속 쓰일테니까... 할 수 있을 것이다.

profile
익숙해지기 위해 기록합니다

0개의 댓글