인자에 기본값 지정해주기
#함수를 선언할 때 인자에 기본값을 지정해 줄 수 있다.
EXPRESSION = {
0: lambda x, y: x+y,
1: lambda x, y: x-y,
2: lambda x, y: x*y,
3: lambda x, y: x/y
}
def calc(num1, num2, option=None): #인자로 option이 들어오지 않는 경우
""" #docstring
option
- 0: 더하기
- 1: 뺴기
- 2: 곱하기
- 3: 나누기
"""
return EXPRESSION[option] (num1, num2) if option in EXPRESSION.keys() else False
print(calc(10,20)) #False
print(calc(10, 20, 0)) # 30
print(calc(10, 20, 1)) # -10
print(calc(10, 20, 2)) # 200
print(calc(10, 20, 3)) # 0.5
args/kwargs에 대한 이해
args(arguments)와 keyword arguments(kwargs)는 함수에서 인자로 받을 값들의 갯수가 불규칙하거나 많을 때 주로 사용한다. 인자로 받을 값이 정해져있지 않기 때문에 함수를 더 동적으로 사용할 수 있다. 함수를 선언할 떄 args는 앞에 * 를 붙이고, kwargs는 앞에 ** 를 붙여 명시한다.
args 활용 (args는 tuple로 받아 불가변)
def add(*args):
#args = (1, 2, 3, 4)
result = 0
for i in args:
result += i
return result
print(add())
print(add(1,2,3))
print(add(1,2,3,4))
import pprint #정렬 출력
def set_profile(**kwargs):
"""
kwargs = {
name: "lee",
gender: "man",
age: 32,
birthday: "01/01",
email: "python@sparta.com"
}
"""
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
profile = set_profile(
name = "lee",
gender ="man",
age = "32",
birthday = "01/01",
email = "python@sparta.com",
)
pprint.pprint(profile)
def print_arguments(a, b, *args, **kwargs):
print(a)
print(b)
print(args)
print(kwargs)
print_arguments(
1, #a
2, #b
3, 4, 5, 6, #*args
hello="world", keyword="argument" #kwargs
)
패킹(packing)과 언패킹(unpacking)은 단어의 뜻 그대로 요소들을 묶어주거나 풀어주는 것
- list 혹은 dictionary의 값을 함수에 입력할 때 주로 사용
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))와 동일
import pprint
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",
}
pprint.pprint(set_profile(**user_profile))
"""아래 코드와 동일
profile = set_profile(
"name": "lee",
"gender": "man",
"age": 32,
"birthday": "01/01",
"email": "python@sparta.com",
)
"""
객체지향(Object-Oriented Programming)
: 객체를 모델링하는 방향으로 코드를 작성하는 것
특성
1) 캡슐화: 특정 데이터의 엑세스를 제한해 데이터가 직접적으로 수정되는 것을 방지, 검증된 데이터만 사용 가능
2) 추상화: 사용되는 객체의 특성 중 필요한 부분만 사용하고 필요하지 않은 부분 제거
3) 상속: 기존에 작성된 클래스의 내용을 수정하지 않고 그대로 사용하기 위해 사용되는 방식. 클래스 선언할 때 상속받을 클래스를 지정할 수 있다
4) 다형성: 하나의 객체가 다른 여러 객체로 재구성되는 것. ex) 오버라이드, 오버로드
객체지향의 장/단점
📌 장점: 클래스 상속을 활용하기 때문에 코드의 재사용성이 높다, 데이터를 검증하는 과정이 있기 때문에 신뢰도가 높다, 모델링을 하기 수월하다, 보안성이 높다
📌 단점: 난이도가 높다, 코드의 실행 속도가 비교적 느린 편이다, 객체의 역할과 기능을 정의하고 이해해야 하기 때문에 개발 속도가 느려진다.
객체지향 예제 코드
import re
# 숫자, 알파벳으로 시작하고 중간에 - 혹은 _가 포함될 수 있으며 숫자, 알파벳으로 끝나야 한다.
# @
# 알파벳, 숫자로 시작해야 하며 . 뒤에 2자의 알파벳이 와야 한다.
email_regex = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+')
class Attendance:
count = 0
def attendance(self):
self.count += 1
@property
def attendance_count(self):
return self.count
class Profile(Attendance):
def __init__(self, name, age, email):
self.__name = name # __를 붙여주면 class 내부에서만 사용하겠다는 뜻
self.__age = age
self.__email = email
@property # 읽기 전용 속성
def name(self):
return self.__name
@name.setter # 쓰기 전용 속성
def name(self, name):
if isinstance(name, str) and len(name) >= 2:
print("이름은 2자 이상 문자만 입력 가능합니다.")
else:
self.__name = name
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if isinstance(age, int):
self.__age = age
else:
print("나이에는 숫자만 입력 가능합니다.")
@property
def email(self):
return self.__email
@email.setter
def email(self, email):
if re.fullmatch(email_regex, email):
self.__email = email
else:
print("유효하지 않은 이메일입니다.")
def attendance(self): # override
super().attendance() # 부모 메소드 사용하기
self.count += 1
def __str__(self):
return f"{self.__name} / {self.__age}"
def __repr__(self):
return f"{self.__name}"
리스트 작업의 의미
list = []
list.insert(0, 'test')
list.insert(0, 'sample')
list.insert(1, 'mid')
list.append('end')
print(list) #['sample', 'mid', 'test', 'end']
print("samplie is at", list.index('sample'),'th')
#samplie is at 0 th
list.pop(0) #['mid', 'test', 'end']
리스트는 데이터에 순서를 매겨 늘어놓은 자료구조
연결리스트는 예를 들어 A-F까지 연결되어 있다면, 누군가를 건너뛰거나 뒤돌아 앞 사람에게 연결될 수 없는 구조
연결 리스트에서 각각의 원소를 노드
라고 한다. 노드가 갖고 있는 것은 데이터와 뒤쪽 노드를 가리키는 포인터
이다. 원소를 저장하는 건 item 필드
다음 노드를 가리키는 건 next 필드
라고 한다
맨 앞에 있는 노드를 머리 노드
맨 끝에 있는 노드를 꼬리 노드
라고 한다
__head #첫 번째 노드에 대한 레퍼런스
__numItems #연결 리스트에 들어 있는 원소의 총 수
get(i) #연결 리스트의 i번 원소를 알려준다
index(x) #원소 x가 연결 리스트의 몇 번 원소인지 알려준다
isEmpty() #연결 리스트가 빈 리스트인지 알려준다
size() #연결 리스트의 총 원소 수를 알려준다
cleae() #연결 리스트를 청소한다
count(x) #연결 리스트에서 x원소가 몇 번 나타나는지 알려준다
extend(a) #연결 리스트에서 나열할 수 있는 객체 a를 풀어서 추가한다
copy() #연결 리스트 복사해서 새 연결 리스트를 리턴
reverse() #연결 리스트 순서 역으로 변경
sort() #연결 리스트 원소 정렬
배열 리스트와 연결 리스트 비교
연결 리스트 클래스 LinkedList
연결 리스트 클래스 LinkedList는 다음과 같이 필드 3개로 구성된다
예)
class LinkedList:
"""연결 리스트 클래스"""
def __init__(self) -> None:
"""초기화"""
self.no = 0
self.head = None
self.current = None
def __len__(self) -> int:
"""연결 리스트의 노드 개수를 반환"""
return self.no
자료구조 수업을 나가기 전에 집에 있는 책으로 혼자 공부를 했다. 알고리즘 성능 부분은 이해하기가 어려웠고 피보나치 수열, 하노이의 탑은 호출 과정이 전반적으로 어떻게 이루어지는 건지 궁금하면서 이해가 가지 않았다. 하노이의 탑은 이동 횟수가 호출 횟수와 같다는데 어떤 방식으로 호출이 이루어지는 건지 모르겠다. 연결 리스트는 천천히 몇 번 읽어보면서 흐름만 이해할 수 있었다. 내일부터는 혼자 책만 붙잡고 있지 말고 강의를 보면서 자료구조 챕터를 이해할 수 있도록 해야겠다.