a, b = map(int, input().split()) -> map(변환_함수, 반복_가능한_객체)
방법
1. print(a+b)
2. print("%d" %(a+b))
3. print("{}".format(a+b))
주의해야 할 점: 1. %d와 {}에 넣을때에는 ""로 감싸야한다. 2. %d + %d로 안 한 이유는 딱 값이 그렇게 들어가기만 하고 연산은 되지 않는다.(a + b -> 딱 이렇게만 보이게됨) 그리고 무조건 format이나 %d나 마찬가지로 print()안에 다 들어가야한다. 예: print("{}").format() -> x print("{}".format()) -> o 3. format도 마찬가지로 {}하나만 써서 format()안에 연산을 넣는다.
그...오늘 너무 피곤해서 코딩 테스트는 여기까지....
디자인 패턴
디자인 패턴(Design Pattern)은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 재사용 가능한 설계 방법입니다. 주로 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 '규약' 형태로 만들어 놓은 것을 의미.
디자인 패턴은 더 쉽게 말하면, 디자인 패턴은 소프트웨어 개발에서 자주 발생하는 문제들을 해결하기 위해 정리된 "코딩 방식"입니다.(즉, 미리 정의된 코딩을 하는 방법이라고 보면 됩니다.) 즉, 여러 개발자들이 겪었던 문제들을 해결하면서 쌓인 "베스트 프랙티스(최적의 해결책)"를 정리한 것이라고 보면 됩니다.(그냥 이렇게, 저렇게 하자라는 하나의 설명서)
같은 문제를 매번 새롭게 해결하는 게 아니라, 검증된 방법을 활용하며, 유지보수와 확장성을 높이고, 협업할 때 코드 스타일을 통일하자라는 느낌입니다. 예를 들어, 싱글톤 패턴 → 객체가 여러 개 생기면 안 되는 경우(예: 데이터베이스 연결) 팩토리 패턴 → 객체 생성을 한 곳에서 관리하고 싶을 때 옵저버 패턴 → 특정 이벤트가 발생하면 여러 곳에 알림을 주고 싶을 때
1. 싱글톤 패턴 싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴입니다. 즉, 하나의 클래스에 대해 단 하나의 인스턴스만 생성되도록 보장하는 패턴입니다. 하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만, 그렇게 하지 않고 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는 데 쓰이며, 데이터베이스 연결 모듈에 많이 사용합니다. 예시:
class Singleton:
_instance = None # 클래스 변수로 인스턴스를 저장
def __new__(cls):
if cls._instance is None: # 인스턴스가 없으면 생성
cls._instance = super().__new__(cls)
return cls._instance
드럽게 어렵게 생겼네요 하나하나 보겠습니다
_instance = None
이게 무엇이냐 하면, 변수 앞에 _을 붙이면, 특정 의미를 가진 규칙이나 관례를 가집니다. 단일 언더스코어 (_) 단일 언더스코어는 변수나 메서드가 "내부용"임을 나타내는 관례입니다. 이 변수나 메서드는 외부에서 사용하지 말고 내부에서만 사용하도록 의도된 것입니다. 예를 들어, 클래스나 함수 외부에서 이 변수나 메서드를 직접 접근하는 것은 권장되지 않지만, Python에서 강제로 막지는 않습니다. 즉, "가시성"에 대한 힌트를 제공합니다.
class MyClass:
def __init__(self):
self._internal_variable = 10 # 내부적으로만 사용
def get_internal(self):
return self._internal_variable
obj = MyClass()
print(obj._internal_variable) # 여전히 접근 가능하지만, 권장되지 않음
더블 언더스코어 (__) 두 개의 언더스코어는 "이름 맹글링(name mangling)"을 유발합니다. 이는 클래스 내에서 변수나 메서드 이름을 바꿔서, 하위 클래스에서 의도하지 않게 접근하는 것을 방지하려는 기술입니다. 즉, 클래스 내에서 __로 시작하는 변수나 메서드는 "강제로 숨겨지는" 것처럼 동작합니다.
class MyClass:
def __init__(self):
self.__private_variable = 42 # 외부에서 접근할 수 없도록 숨김
obj = MyClass()
# print(obj.__private_variable) # AttributeError: 'MyClass' object has no attribute '__private_variable'
# 하지만 내부에서는 여전히 이름이 맹글링되어 사용 가능
print(obj._MyClass__private_variable) # 42, 맹글링된 이름을 통해 접근 가능
언더스코어가 클래스 변수명 앞에 붙은 경우 (_instance) _instance와 같은 변수는 클래스 변수로 사용되며, 일반적으로 해당 클래스의 인스턴스를 관리하기 위한 용도로 사용됩니다. 이 변수는 주로 싱글턴 패턴에서 인스턴스를 한 번만 생성하고 재사용할 때 사용됩니다. 언더스코어를 붙여서 "내부적"으로 사용하는 변수임을 명시합니다.
class Singleton:
_instance = None # 이 변수는 클래스 내부에서만 사용되도록 의도
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True, 두 인스턴스는 동일
단일 언더스코어 (_) 함수나 메서드 앞에 쓰는 경우 _를 메서드 이름 앞에 붙이면, 해당 메서드는 "비공개"로 사용되어야 함을 나타냅니다. 하지만 여전히 외부에서 접근은 가능합니다. 다만, 이는 "내부적인 용도"임을 알리는 관례에 해당합니다.
class MyClass:
def __init__(self):
self._hidden_variable = 20
def _private_method(self):
print("This is a private method.")
obj = MyClass()
obj._private_method() # 외부에서 접근은 가능하지만, '내부' 용도임을 나타냄
여기서 외부 사용 자제란 뜻은 변수를 _로 시작하는 경우 이 변수나 메서드를 외부에서 직접 접근하거나 사용하는 것을 피해야 한다는 의미입니다. 즉, 이 변수나 메서드는 클래스 내부에서만 사용되도록 의도된 것이므로, 다른 코드에서 직접 접근해서 사용하지 말고 내부에서만 관리하고 사용하는 것이 좋다는 것입니다. 클래스 안에서 _을 붙인 변수나 메서드는 외부에서 직접 사용하지 말고, 그 값을 외부에서 접근할 필요가 있다면 공식적인 방법인 메서드를 통해 접근하라는 뜻입니다.
class MyClass:
def __init__(self):
self._internal_variable = 10 # 외부에서 사용하지 말라는 의도
def _private_method(self):
print("This is a private method.") # 내부에서만 호출하도록 의도
def get_internal_variable(self):
return self._internal_variable # 안전하게 값에 접근할 수 있는 방법
obj = MyClass()
# 권장되지 않는 방법
print(obj._internal_variable) # 이건 가능하지만, 의도된 사용법은 아님
# 권장되는 방법
print(obj.get_internal_variable()) # 공식적인 방법으로 값을 가져오는 것
def __new__(cls):
if cls._instance is None: # 인스턴스가 없으면 생성
cls._instance = super().__new__(cls)
return cls._instance
이제 이 부분입니다. __new__ 메서드: __new__는 객체가 생성될 때 호출되는 메서드입니다. 이 메서드는 새로운 객체를 만드는 역할을 합니다. cls._instance is None이면 인스턴스가 아직 생성되지 않았다는 뜻이므로, super().__new__(cls)를 호출하여 새로운 인스턴스를 생성하고 _instance에 저장합니다. 이미 _instance에 값이 있다면, 새로운 인스턴스를 생성하지 않고 기존 인스턴스를 반환합니다.
그런데 저 cls는 뭘까요? cls는 클래스를 가리키는 참조입니다. 파이썬에서 self는 인스턴스를 가리키는 참조이고, cls는 클래스를 가리키는 참조입니다. __new__ 메서드는 클래스 메서드처럼 동작하며, 첫 번째 인자로 클래스 자체 (cls) 를 받습니다. 즉, __new__는 객체가 생성되기 전에 호출되므로, 아직 self가 존재하지 않고, 클래스 자체가 첫 번째 인자로 전달됩니다. 예시:
class MyClass:
def __new__(cls): # cls는 현재 생성 중인 클래스 (MyClass)
print(f"__new__ called! cls: {cls}")
instance = super().__new__(cls) # 새로운 객체 생성
return instance # 생성된 객체 반환
def __init__(self):
print("__init__ called!")
obj = MyClass()
여기서 cls는 MyClass 자체를 의미합니다. 그런데 여기서 super()를 왜 쓸까요? 여기서 중요한 점은, Python의 모든 클래스는 object를 기본적으로 상속받습니다.
class MyClass:
def __new__(cls):
print("__new__ called!")
instance = super().__new__(cls) # object.__new__(cls)를 호출하여 메모리에 객체 생성
return instance # 생성된 객체 반환
def __init__(self):
print("__init__ called!")
obj = MyClass()
즉, __new__에서 super().__new__(cls)를 호출해야 객체가 정상적으로 생성됩니다.
솔직히 이래선 이해가 잘 안가실 수 있습니다 더 자세히....더 자세히.... 먼저, super().__new__(cls)는 사실 곧 object.__new__(Singleton)라고 할 수 있습니다.
그런데 Singleton을 ()안에 넣어주는 이유가 뭘까요? Python에서 __new__(cls)는 새로운 객체를 만들 때 사용되며, 이때 cls에는 객체를 생성할 클래스가 들어갑니다. 즉, object.__new__(Singleton)에서 Singleton을 전달하는 이유는 "이 객체는 Singleton 클래스의 인스턴스여야 한다"는 것을 Python에 알려주기 위해서입니다.
전체 흐름을 보면 object.__new__(Singleton) 내부 동작 Python 내부에서 object.__new__(cls)가 실행되면 다음과 같은 과정이 일어납니다: 새로운 메모리 공간 할당 Python이 Singleton 클래스 크기에 맞는 새로운 메모리 공간을 확보합니다. 이 메모리는 객체의 속성(__dict__)과 클래스 정보를 저장할 공간이 됩니다. 객체의 __class__ 속성 설정 Python은 새롭게 생성된 객체의 클래스 정보를 Singleton으로 설정합니다. 즉, object.__new__(Singleton)이 반환하는 객체는 Singleton의 인스턴스가 됩니다. 객체 반환 object.__new__(Singleton)은 새롭게 생성된 Singleton 객체를 반환합니다. 이후 __init__()이 실행되어 객체가 초기화됩니다.
간단히 말해서, cls._instance = super().__new__(cls) → 이 코드는 super().__new__(cls)를 통해 Singleton 클래스의 새로운 인스턴스를 생성합니다. → 여기서 생성된 인스턴스는 Singleton 클래스에 정의된 모든 메서드와 속성을 갖습니다.(어떻게 보면 super().__new__(cls)를 호출할 때, cls를 인자로 넘김으로써 새롭게 만들어질 인스턴스가 어떤 클래스의 인스턴스가 되어야 하는지를 알려주는 역할을 합니다.) → 그리고 그 생성된 인스턴스를 클래스 변수인 _instance에 저장합니다.
나중에 s1 = Singleton() 같은 호출이 일어나면, → 이미 저장된 _instance를 반환하게 되어, → 결과적으로 Singleton 클래스의 메서드와 속성을 가진 동일한 인스턴스가 변수 s1에 할당됩니다. 즉, "cls._instance = super().__new__(cls)는 Singleton 클래스의 설계도에 따라 필요한 메서드와 속성을 모두 갖춘 인스턴스를 생성하고, 그 객체를 _instance에 저장하여 반환하는 역할을 한다." 이렇게 설명할 수 있습니다.
중요한 점은 보이지 않더라도 모든 클래스는 object라는 기본 클래스를 상속받고, 이 object가 제공하는 기본 메서드들(예: __new__, __init__, __str__ 등)을 물려받게 됩니다. 그래서 Singleton 클래스에서도 super().__new__(cls)를 호출하면, 결국 object.__new__(cls)를 실행하게 되는 것입니다.
그렇다면 이게 어떻게 한번만 인스턴스를 만들 수 있게 하는걸까요? 이것을 알려면 클래스 변수와 인스턴스 변수의 차이를 알아야합니다. 클래스 변수 클래스 변수는 클래스에 속하는 변수입니다. 모든 인스턴스가 공유하는 변수로, 클래스의 이름을 통해 접근합니다. 클래스 레벨에서 한 번만 정의되며, 클래스의 모든 인스턴스에서 같은 값을 가집니다.
인스턴스 변수 인스턴스 변수는 인스턴스에 속하는 변수입니다. 각각의 객체가 생성될 때마다 별도로 생성되며, 각 객체마다 다른 값을 가질 수 있습니다. 객체의 상태를 나타내는 변수입니다.
class MyClass:
# 클래스 변수 (모든 인스턴스가 공유)
class_variable = 0
def __init__(self, instance_value):
# 인스턴스 변수 (각각의 인스턴스마다 다른 값을 가짐)
self.instance_variable = instance_value
def show(self):
print(f"클래스 변수: {MyClass.class_variable}, 인스턴스 변수: {self.instance_variable}")
# 두 인스턴스 생성
obj1 = MyClass(1)
obj2 = MyClass(2)
# 클래스 변수는 클래스 이름으로 접근
print(f"클래스 변수 접근 (obj1): {obj1.class_variable}")
print(f"클래스 변수 접근 (obj2): {obj2.class_variable}")
# 인스턴스 변수는 각각의 인스턴스에 따라 다름
obj1.show() # obj1의 클래스 변수와 인스턴스 변수 출력
obj2.show() # obj2의 클래스 변수와 인스턴스 변수 출력
# 클래스 변수는 공유되므로, 한 인스턴스에서 값을 변경하면 다른 인스턴스에도 반영됨
obj1.class_variable = 10 # obj1에서 클래스 변수 변경
obj1.show() # 변경된 값 출력
obj2.show() # 다른 인스턴스에도 반영된 값 출력
출력
클래스 변수 접근 (obj1): 0
클래스 변수 접근 (obj2): 0
클래스 변수: 0, 인스턴스 변수: 1
클래스 변수: 0, 인스턴스 변수: 2
클래스 변수: 10, 인스턴스 변수: 1
클래스 변수: 10, 인스턴스 변수: 2
클래스 변수: class_variable = 0은 클래스 변수로, obj1과 obj2가 모두 공유합니다. 클래스 변수는 클래스 이름을 통해 접근할 수 있습니다. 예를 들어, obj1.class_variable이나 obj2.class_variable에서 모두 동일한 값을 가집니다. 클래스 변수의 값은 모든 인스턴스에서 동일하게 공유되며, 하나의 인스턴스에서 값을 변경하면 다른 인스턴스에도 영향을 미칩니다. (위 예시에서는 obj1.class_variable = 10으로 변경한 후, 두 객체에서 모두 반영된 값을 볼 수 있습니다.)
인스턴스 변수: instance_variable은 인스턴스 변수로, 각 인스턴스에 대해 별도로 값을 가질 수 있습니다. obj1의 instance_variable 값은 1이고, obj2의 instance_variable 값은 2입니다. 인스턴스 변수는 각 객체별로 다른 값을 유지합니다. 즉, obj1과 obj2는 각각 독립적인 값을 가집니다.
즉, 한 객체에서 클래스 변수를 변경하면 모든 객체에 그 변경 사항이 반영됩니다. 이유는 클래스 변수가 클래스 자체에 속하는 변수이기 때문입니다. 클래스 변수는 모든 인스턴스가 공유하는 변수이기 때문에, 하나의 인스턴스에서 값을 바꾸면 그 값이 다른 모든 인스턴스에도 영향을 미칩니다.
이제 돌아와서 이것이 왜 싱글톤이냐 하면, 보시는 바와 같이,
class Singleton:
_instance = None # 클래스 변수로 인스턴스를 저장
def __new__(cls):
if cls._instance is None: # 인스턴스가 없으면 생성
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True (같은 인스턴스)
s1에서 이미 Singleton의 속성을 가지는 인스턴스를 생성해서 instance에 들어갔고, 그리고 저것은 클래스 변수이기 때문에, s2에서 이미 _instance = None이 아니라, 특정 값으로 바뀌게 되며, None 아니기 때문에, 그 값이 그대로 들어가서, 같다고 나오게 됩니다. 이것이 바로 싱글톤 즉, 하나의 클래스는 하나의 인스턴스만 가지도록 할 수 있는 겁니다.
팩토리 패턴 팩토리 패턴은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴입니다. 상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가지기 때문에 더 많은 유연성을 갖게 됩니다. 즉, 객체 생성을 별도의 메서드에서 처리하여, 객체 생성 코드를 한 곳에 모아놓는 패턴입니다.
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
def animal_factory(animal_type):
animals = {"dog": Dog, "cat": Cat}
return animals.get(animal_type, Dog)() # 기본적으로 Dog 반환
pet = animal_factory("cat")
print(pet.speak()) # Meow!
아 참고로 설명드릴 것은 파이썬에서 클래스는 전역 스코프(global scope)에 정의된 것이므로, 함수 내에서 바로 접근할 수 있습니다. 함수 내에서 전역 변수나 클래스에 접근하는 것은 파이썬이 전역 스코프에 있는 것을 자동으로 찾을 수 있기 때문입니다.
변수같은 건 global을 통해서 쓰는데, 문제는 global은 전역에 있는 변수를 "바꿀때"쓰는 겁니다. 즉, global 키워드는 변수 값을 수정할 때 필요하며, 값을 읽는 것은 global 키워드 없이도 가능합니다. 즉, 전역 변수의 값을 읽을 때는 global을 사용할 필요가 없습니다.
아무튼 먼저 클래스입니다 1. 클래스 (Class)
class Dog:
def speak(self):
return "Woof!"
Dog 클래스는 개를 표현하는 설계도입니다. 이 클래스는 speak라는 메서드를 가지고 있으며, "Woof!"라는 소리를 반환합니다.
class Cat:
def speak(self):
return "Meow!"
Cat 클래스는 고양이를 표현하는 설계도입니다. 이 클래스도 speak라는 메서드를 가지고 있으며, "Meow!"라는 소리를 반환합니다.
인스턴스 (Instance)
def animal_factory(animal_type):
animals = {"dog": Dog, "cat": Cat}
return animals.get(animal_type, Dog)() # 기본적으로 Dog 반환
animal_factory 함수는 인스턴스를 생성하는 역할을 합니다. animal_factory("cat")와 같은 호출이 들어오면, 함수 내부에서 Cat 클래스의 인스턴스를 생성하여 반환합니다. animal_type이 "dog"이면 Dog 클래스의 인스턴스를, "cat"이면 Cat 클래스의 인스턴스를 반환합니다. animals.get(animal_type, Dog): 여기서 animal_type에 따라 Dog 또는 Cat 클래스를 선택하고, 그 선택된 클래스의 인스턴스를 생성합니다. 기본값으로 Dog를 반환하도록 되어 있기 때문에, "cat"을 입력하지 않으면 기본적으로 "dog" 인스턴스가 반환됩니다.
객체 (Object)
pet = animal_factory("cat")
print(pet.speak()) # Meow!
pet 변수는 animal_factory("cat") 호출을 통해 생성된 Cat 클래스의 인스턴스를 담고 있습니다. 이 객체는 고양이 객체입니다. pet.speak()를 호출하면, 고양이 객체의 speak 메서드가 실행되어 "Meow!"를 반환합니다. pet은 실제로 Cat 클래스의 객체로, 그 객체는 speak 메서드를 통해 고양이의 소리를 반환합니다.
즉, animal_factory("cat")이 호출되면 animal_type에는 "cat"이 전달됩니다. animals.get(animal_type, Dog)에서 animal_type에 해당하는 값이 animals 딕셔너리에서 검색됩니다. "cat"에 해당하는 값은 Cat 클래스입니다. animals.get("cat", Dog)은 Cat을 반환하게 되고, 그 뒤에 ()를 붙여 Cat() 객체를 생성합니다. 결국 Cat()이 반환되어 pet 변수에 할당됩니다. 따라서 pet = animal_factory("cat") 코드의 실행 결과는 pet이 Cat 클래스의 인스턴스가 되는 것입니다.
이런 흐름 자체가 팩토리 패턴입니다. 원래라면 클래스를 정의하고 밑에 객체를 만들겠지만, 함수를 따로 만들어서 따로 분리를 할 수 있게 해주는 겁니다. 더 설명하자면, animal_factory 함수는 Dog 또는 Cat 클래스를 생성하는 방법을 외부에서 알 필요 없이 단순히 호출하여 객체를 얻을 수 있게 만듭니다. 클라이언트는 animal_factory("cat")만 호출하면 자동으로 고양이 객체가 생성되므로, Cat 클래스가 어떻게 구현되어 있는지 알 필요가 없습니다. 게다가 팩토리 함수(animal_factory)는 동적으로 어떤 객체를 생성할지 결정할 수 있습니다. animal_type이 "cat"이면 Cat 객체를 생성하고, "dog"이면 Dog 객체를 생성합니다. 이로 인해 객체 생성의 유연성이 증가합니다. 새로운 동물 클래스를 추가하거나 객체 생성 방식을 바꾸는 데 있어 클라이언트 코드에 영향을 미치지 않습니다.