새로운 한주가 시작됐습니다.
오전에는 LMS 통해서 강의를 들었습니다.
배운 것도 있고, 새로운 것도 있었습니다.
Numpy, Pandas, Visualization 은 처음 보는 것들이었습니다.
같이 코딩도 해보면서 들었는데 그랬더니 시간이 부족하네요.
Visualization 강의는 조금밖에 못들었습니다.
내일도 예정되어 있는데 맛?을 본다는 느낌으로 들어야 할 것 같습니다.


그리고 오후 강의..
여전히 클래스를 배우고 있습니다.
강사님께서 재밌는 얘기를 많이 해주셔서 흥미롭게 수업을 받고 있습니다.
강의 도중에 떠오르는 생각들을 전달해 주고 싶은데 지금 강의랑의 조금 동떨어져 있을때
"이것은 지금 말씀 안드리겠습니다." 라고 하시는데 ㅎㅎ
이미 말해주고 싶어 하시는게 표정과 말투에서 보입니다.^^


사실 진도가 좀 천천히 나가더라도 다 듣고 싶은데 듣고 싶다고 말씀 드리기가
어느 순간 어렵더라고요.
따봉버튼 숫자가 뭔가 많이 안 올라가는거 같을 때 같이 강의 듣는 친구들이
어려워 하는거 같은데 혼자 듣겠다고 말해주세요! 라고 하면
안 그래도 어려워 하는 친구들이 더 헷갈릴거 같아서 참고 있습니다.


가까운데 사시니까 관계가 좀 더 가까워진다면 식사 한 번 하면서 다양한 얘길
듣고 싶다는 생각을 했습니다. 기회가 있겠지요^^


복습

LLL = ['{} X {} = {}'.format(i, j, i*j) for i in range(2, 10) for j in range(1, 10)]
print(LLL)


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.
d.strftime('%Y-%m-%d %H:%M')  # 가장 많이 사용하는 date format.

for문에서 i의 의미에 대해 얘기해주시기도 하셨고, 시간을 다루는 방법을 말씀해주셨습니다.

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 = password    
        # 실무에서 이렇게 저장 절대 안합니다.(Django에서도 이렇게 저장해서 오류나는 경우 많음)
        # self.password = FEBD93F04BDA1AEC0D374F8FD014D062525934FEB1F1B81EE7C64D61F66B84B1 + salt
        # Django 에서도 sha256을 사용합니다. 은행권에서는 이 알고리즘이 깨졌다고 보고 있습니다.
        # sha512를 사용하려 노력을 합니다. 하지만 리소스가 많이 사용됨.
        # MD5 라는 알고리즘을 암호화 알고리즘으로 많이 사용했었는데 이게 깨졌습니다. 레인보우 어택으로 깰수 있음.

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

    def change_password(self, 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}')

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.name



user1 = User('성춘향', '2024/01/15', '여', 14, 'sung@gmail.com', '1q2w3e4r!')
user1.name
user1.accessdate

User.user_count = 1
user2 = User('홍길동', '2024/01/15', '남', 20, 'hong@gmail.com', '1q2w3e4r!!')
user2.name

User.user_count = 2
User.user_count
# 무엇을 알 수 있나요? 실제로 class 와 인스턴스의 메모리 영역은 교집합 상태
# 한 곳에서 수정이 되면 모두 수정이 됩니다!
# 그래서 처음에 클래스를 설계 할때 모든 인스턴스에 있을 변수(클래스 변수)와
# 인스턴스에만 있는 변수(인스턴스 변수)를 나누는 것이 매우 중요!
# 채팅 프로그램 만들때 5천명 이하면 DB에 바로 써도 되지만
# 10만명 이렇게 사용하게 되면 DB에 바로 쓰면 다운됨.
# 

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

클래스 강의 하시면서 여러 설명들 해주시면서 예제를 만들어 보고 있습니다.
password 관련해서 바로 저장하면 안 된다고 말씀해주셨고, 간단하게 예제 코드를 보여주셨습니다.
재밌는 얘기들은 들으면서 주석에 다 포함하려고 했습니다.

# 온라인 쇼핑몰에서 장바구니에 넣기
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)

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

buy_cart.items
buy_cart.total_price()

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



정적 메서드

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

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

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

Book.할인율(boo1.price, 10) 

할인율 함수는 밖으로 뺄 수도 있는 함수라서 굳이 클래스 안에 넣어야 하나? 생각할 수도 있습니다.
할인율은 Book 클래스와 연관이 있고, 밖의로 함수 빼기도 부담스러울때 정적 메서드를 사용합니다.
향후 유지보수에도 좋습니다.


속성 접근자(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)

클래스 내부의 속성에 접근할 수 있도록 메서드를 만드는 건데 잘 안 쓸것 같습니다.^^


덕타이핑

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):
        super().sound()
        print("짹짹!")

b = Bird()
b.sound()

정말 많이 쓰겠지요? 클래스의 기본이지 않을까 싶습니다.


추상클래스

# 추상 클래스
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}입니다.')

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

위의 코드 실행하면 에러가 납니다.
@abstractmethod 라고한 do_something(self) 함수가 하위클래스에서 구현되지 않았기 때문입니다.
추상클래스는 반드시 구현되어야 하는 것을 명시하면 그것을 상속한 클래스에서 그 메서드를 구현해야 합니다.
예를 들면 빠트리면 안되는 메서드가 있는 경우 게시판 만드는데 게시물 업데이트 날짜,
생성 날짜를 추상 클래스로 구현할 수 있습니다.


비공개 속성

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    # 보통 다른 언어에서는 _ 한개가 private value입니다.
# c.__a #error    어 접근이 안되네!? 이걸로 변수를 감추면 되겠다!? => 이렇게 생각하면 안됨

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

print(c._MyClass__c)  # 출력: 30

클래스 내부에서만 접근이 가능하며 외부에서는 접근이 제한되는 속성을 의미합니다.
언더스코어 _ 를 2개 붙여서 해당 속성을 비공개처럼 표현할 수 있습니다.
객체.속성으로 접근하지 못합니다.