- 예제코드는 https://github.com/iseohyun/python-tutorial에서 다운로드 받을 수 있습니다.
- 예제코드 적용 방법 및 환경설정은 여기를 참고하십시오
- VScode의 사용법 안내는 여기를 참고하십시오
목차보기
- 환경구축하기 : 실습 준비, 프로그램, 예제코드 다운로드 방법
- 시작하기 1_HelloWorld : 예제코드 실행 및 코드 보는 방법
- 변수 2_variable : 데이터를 순서대로 읽고, 쓰는(저장하는) 방법
- 출력 테크닉 3_print : 저장된 데이터를 잘(보기 좋게) 출력하는 방법
- 로직 4_Logic : 조건에 따라 실행 순서를 변경(추가, 건너뜀, 반복)하는 방법
- 함수 5_Function : 만들어진 로직을 재사용 하는 방법
- 콜렉션 6_Collections : [저장 단위 확장] 코드에 사용될 데이터를 묶음 단위로 사용하는 방법
- 모듈 7_Module : [프로그램 연계] 작성한(작성된) 코드를 파일 단위로(또는 더 큰 단위로) 가져다 쓰는 방법
- 예외 8_Exception : 프로그램 오류에 대한 대처
- 객체 9_Class : [더 큰 프로젝트] 프로그램을 순서지가 아닌 객체로(대상으로) 대하는 자세(문법)
- 표준 라이브러리 10_Std lib : 기본으로 제공되는 객체(예제)
객체9_Class
객체 생성
객체란 객체가 가진 특성(Attribute)과 기능(method)으로 정의됩니다. 객체 지향 프로그래밍은 코드 뭉치를 하나의 대상으로 취급하는 방법론입니다. 이 때 코딩은 인간의 인지와 유사하게 작동합니다. 작은 개념들을 연결해서 더 큰 개념을 만들기 때문에 보다 직관적이고, 거대한 프로젝트를 가능하게 합니다. class 문법은 객체를 기술하는 방법을 제시합니다.
- 절차적 프로그래밍(Procedural Programming)
→ 객체 지향 프로그래밍(OOP, Object-Oriented Programming) - 객체(object) = 속성(attribute) + 메서드(method, 기능)
- 객체 접근 : '객체명.속성' 또는 '객체명.함수'
- (권장)객체명은 대문자로 시작하고(단어마다), '_'는 사용 X
class MyClass:
i = 12345
def f():
print(f"MyClass")
MyClass.f()
print(MyClass.i) # 12345
Instance
class는 '설계도', instance(인스턴스)는 '실체'입니다. instance에서 호출되는 method는 self argument를 반드시 갖습니다.
9_Class > 00-1_Instance.pyclass MyClass:
i = 12345
def printi(self): # instance에서 사용하는 method는 'self' argument를 갖습니다.
print(self.i)
def seti(self, i):
self.i = i
my1 = MyClass() # Instance 생성
my1.printi() # 12345
my1.seti(99999) # i = 99999
초기화
instance가 생성될 때, 반드시 실행되는 method의 이름은 __init__ 내장변수를 사용합니다.
9_Class > 02_Init.pyclass MyClass:
def __init__(self, i): # 초기화 할 수 있다. 생성시 반드시 호출된다.
self.i = i
my1 = MyClass(777) # 초기화 i = 777
method object
instance의 method를 복사한 것이 object로 볼 수 있는가에 대한 설명으로, python의 모든 것이 객체이므로 부를 수 있다와 애초에 복사된 method라는 주장이 가능하지만, 어쨌든 공식적으로 "메서드 객체"라고 부릅니다.
9_Class > 03_method object.pymy1 = MyClass(777)
mo = my1.printi # method object 생성
mo()
attribute 공유
9_Class > 04_Instance Variable.pyclass Dog:
kind = "canine" # 모든 instance에 공유되는 class 변수
def __init__(self, name):
self.name = name # 각각의 instance에서 개별적으로 관리하는 variable
d = Dog("Fido") # name = "Fido"
e = Dog("Buddy") # name = "Buddy"
Dog.kind = "Hound" # Dog.kind = Hound > d: Hound , e: Hound
# d는 앞으로 개별관리 됨
d.kind = "pooch" # d.kind = pooch > d: pooch , e: Hound
Dog.kind = "Furry" # Dog.kind = Furry > d: pooch , e: Furry
리스트인 경우
9_Class > 04-1_List variable.pyclass Dog:
tricks = [] # mistaken use of a class variable
def add_trick(self, trick):
self.tricks.append(trick)
d.add_trick("roll over") # ['roll over']
e.add_trick("play dead") # ['roll over', 'play dead']
각자 관리되는 리스트
9_Class > 04-2_List variable2.pyclass Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
d.add_trick("roll over") # ['roll over']
e.add_trick("play dead") # ['play dead']
함수 결합
9_Class > 05-Declaration.pydef f1(self, x, y): # outside the class
return min(x, x + y)
class C:
f = f1 # 외부에서 정의된 함수 결합
def g(self):
return "hello world"
h = g # 본인 함수 복사
상속
상속은 이미 만든 객체에서 기능을 추가/변경 할 때 사용됩니다. 간단한 개념을 구현한 뒤에 조금의 노력으로 더 큰 개념을 구현할 수 있게 합니다.
9_Class > 06_Extends.py# 부모 클래스: Parent
class Parent:
def __init__(self, name):
self.name = name
def greet(self):
return f"I am {self.name}, a Parent."
# 자식 클래스: Child
class Child(Parent): # 상속 문법 : class 클래스명(상속받을 클래스1, ...)
def greet(self):
return f"I am {self.name}, a Child."
# 객체 생성
parent_instance = Parent("Alice")
child_instance = Child("Bob")
# 동작 확인
print(parent_instance.greet()) # I am Alice, a Parent.
print(child_instance.greet()) # I am Bob, a Child.
Super
부모의 객체게 자식의 객체에서 동일한 이름을 갖는 경우는 빈번하게 일어납니다. 그 이유는 동일한 기능을 업그레이드 하기 위함입니다. 이를 오버라이딩(overriding)이라고 합니다.
객체가 오버라이딩 되었을 때, 컴퓨터가 객체를 호출하는 방식은 호출하는 instance의 type에 의존합니다. 따라서, 부모가 obj를 호출 할 때 "부모.obj"가 자식이 obj를 호출하면 "자식.obj"가 호출되는 형식입니다.
이 때, 자식의 obj가 오버라이딩 되더라도 부모의 obj가 사라지는 것(덮어씌움)은 아니고, 감춰집니다. "자식.부모.obj"를 호출하려면 super 키워드를 사용합니다.
9_Class > 07_Super.pyclass Child(Parent):
data = "Child" # Parent에도 data가 있음
def pdata(self):
return super().data # Parent의 data를 참조
def show(self):
super().show() # Parent의 show를 실행
다형성
9_Class > 07-2_Polymorphism.py객체가 상속받을 때, 상속된 객체끼리는 속성과 메서드를 공유합니다. 따라서 상속받은 객체들의 이름이 다를지라도 충분히 코드를 재사용 할 수 있습니다.
class Parent:
def hello(self):
return "I'm parent"
class Child(Parent):
def hello(self):
return "I'm first child"
def hello2(self):
return "I'm first child"
class Child2(Parent):
def hello(self):
return "I'm second child"
def greeting(person: Parent): # person의 type이 Parent임을 힌트
print(f"Greeting: {person.hello()}")
p = Parent()
c = Child()
c2 = Child2()
greeting(p) # Greeting: I'm parent
greeting(c) # Greeting: I'm first child
greeting(c2) # Greeting: I'm second child
강제성
다만 기존의 언어들(예: c, java)의 '컴파일 타입 강제'가 아닌, 힌트 제공의 역할만 수행합니다.
def greeting2(person: Child): # Child type으로 강제하지 않음
print(f"Greeting2: {person.hello()}")
greeting2(p) # 에러가 발생하지 않는다
greeting2(c)
class Stranger:
def hello(self):
return "I'm ananymous."
s = Stranger()
greeting2(s) # 심지어 힌트와 연간도 없음, 하지만 통과했죠
다중 상속
9_Class > 08_Extends_Multi.pyclass C(A, B):
def __init__(self, a, b):
A.__init__(self, a)
B.__init__(self, b)
일부 언어(예: java, C#)에서는 다중 상속을 불허합니다. 이유는 부모1과 부모2에 동일한 이름의 객체가 있을 때(충돌시), 호출의 모호함이 발생하기 때문입니다. 하지만 단일 속성 설계로 인해서 설계에 제약이 생기는 것도 사실입니다. python에서는 다중상속을 지원하고(예: C++, python), 충돌이 있을 때, 우선 상속한 객체를 따릅니다.
9_Class > 08-2_Preference.pyclass C(A, B):
def __init__(self):
super().__init__()
class D(B, A):
def __init__(self):
super().__init__()
c = C() # A를 먼저 상속받음, A 호출
d = D() # B를 먼저 상속받음, B 호출
import
'import 폴더.클래스명' 또는 'import 폴더.클래스명.메서드'
9_Class > 09_Package.pyimport myPackage.myPack1
pack = myPackage.myPack1.pack1()
from myPackage.myPack1 import pack1
pack = pack1()
from myPackage.myPack2 import *
pack = pack2()
비공개 변수
Name Mangling(네임 맹글링)은 __ 접두사를 추가하여 외부에서 접근하기 어렵게 만들었습니다.
10_Private > filename.pyclass Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # sub클래스에서 재정의 되지 않도록 보장
class MappingSubclass(Mapping):
def update(self, keys, values):
for item in zip(keys, values):
self.items_list.append(item)
keys = ["key1", "key2", "key3"]
values = ["value1", "value2", "value3"]
mapping_subclass.update(keys, values)
# [4, 5, 6, ('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]
mapping_subclass = MappingSubclass([4, 5, 6]) # [4, 5, 6]
데이터 클레스
단순 정보 저장용 객체
9_Class > 11_Dataclass.py@dataclass
class Employee:
name: str
dept: str
salary: int
이터레이터
- __next__()를 정의하는 iterator객체를 반환
- next()를 통해, __next__()를 호출하지만, 남은 요소가 없을 때, stopIteration예외가 발생
s = 'abc'
it = iter(s)
print(next(it)) # a
print(next(it)) # b
print(next(it)) # c
print(next(it)) # StopIteration Error
표준 라이브러리10_Std lib
이 단원은 유용한 내장 모듈을 열거식으로 소개합니다.
디렉토리 정보조회, 생성, 변경
10_Std lib > 01_Os interface.py 10_Std lib > 01-1_shutil.pyimport os
os.getcwd() # 현재 작업중인 디렉토리 정보 가져오기
os.chdir("otherDir") # 작업디렉토리 변경
os.system("mkdir data") # data라는 디렉토리 생성
디렉토리 내용조회
10_Std lib > 02_Glob.pyimport glob
files = glob.glob("*")
for file in files:
print(file)
입력인자 분석
10_Std lib > 03_PrintArgs.pyimport sys
print(sys.argv)
# 입력 예: c:/> python example.py abc de fgh
# 출력 예: ['c:/example.py', 'abc', 'de', 'fgh']
10_Std lib > 03-1_Argparse2.py
import argparse
parser = argparse.ArgumentParser(
prog='top',
description='Show top lines from each file')
parser.add_argument('filenames', nargs='+')
parser.add_argument('-l', '--lines', type=int, default=10)
args = parser.parse_args()
# 입력 예:
# python script.py file1.txt file2.txt -l 5
# 출력 예:
# Namespace(filenames=['file1.txt', 'file2.txt'], lines=5)
에러출력과 종료
10_Std lib > 04_Error redirection.pyimport sys
sys.stderr.write("Warning, log file not found starting a new one\n")
sys.exit() # 프로그램 종료
print("running") # 출력되지 않음
정규표현식
정규표현식(Regular Expression)은 문자열의 검색, 편집과 관련하여 정확한 문자열이 아닌 문자열 패턴을 기술하는 방법론입니다. 거의 모든 프로그램 언어 및 문서 편집기가 지원할 정도로 막강하고 보편적이지만 표현방식 숙지가 요구됩니다. 해당 칼럼은 여기를 참고해주십시오.
10_Std lib > 05_Re.pyimport re
r = re.findall(r"\bf[a-z]*", "which foot or hand fell fastest")
# ['foot', 'fell', 'fastest']
r = re.sub(r"(\b[a-z]+) \1", r"\1", "cat in the the hat")
# cat in the hat
통계
10_Std lib > 06_Statistics.pyimport statistics
data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
statistics.mean(data) # 평균
statistics.median(data) # 중간값
statistics.variance(data) # 분산
크롤링
10_Std lib > 07_Internet access.pyfrom urllib.request import urlopen
site = "iseohyun.com"
with open(f"10_Std lib/{site}.html", "w+", encoding="utf8") as f:
with urlopen(f"https://{site}") as response:
for line in response:
line = line.decode() # Convert bytes to a str
f.write(line.rstrip())
압축과 해제
10_Std lib > 08_zlib.py### 압축 : zlib, gzip, bz2, lzma, zipfile, tarfile 지원 ###
import zlib
s = b"witch which has which witches wrist watch" # 압축 전 : 41 byte
t = zlib.compress(s) # 압축 후 : 37 byte
zlib.decompress(t) # 압축 해제
zlib.crc32(s) # CRC코드: 226805979 - 압축 해시, 확인용
성능 측정
10_Std lib > 9_Performace.pyfrom timeit import Timer
print(Timer("t=a; a=b; b=t", "a=1; b=2").timeit()) # 1번 임시변수
print(Timer("a,b = b,a", "a=1; b=2").timeit()) # 2번 튜플 언패킹(승)
# 출력 예:
# 0.012652999954298139
# 0.010185600025579333
1번과 2번은 모두 a <-> b swap(교환)방법으로, 임시변수를 사용한 방법과 튜플 언패킹을 사용한 방법의 성능차이를 보여줍니다.
코드 테스트(검증)
- 방법 1
10_Std lib > 10_Doctest.py
def average(values): """Computes the arithmetic mean of a list of numbers. >>> print(average([20, 30, 70])) 40.0 >>> print(average([59, 30, 70])) 53.0 """ return sum(values) / len(values) import doctest resault = doctest.testmod() # >>>로 정의된 TC(Test Case)를 수행합니다.
- 방법 2
10_Std lib > 10-1_unittest.py
import unittest def average(values): return sum(values) / len(values) class TestStatisticalFunctions(unittest.TestCase): def test_average(self): self.assertEqual(average([20, 30, 70]), 40.0) # test-case 1 self.assertEqual(round(average([1, 5, 7]), 1), 4.3) # test-case 2 with self.assertRaises(ZeroDivisionError): average([]) with self.assertRaises(TypeError): average(20, 30, 70) resault = unittest.main() # 테스트 수행
인터프리터 출력
- 너비 조정
10_Std lib > 11_reprlib.py
import reprlib print(set("supercalifragilisticexpialidocious")) print(reprlib.repr(set("supercalifragilisticexpialidocious"))) # {'t', 'i', 'e', 'c', 's', 'f', 'x', 'u', 'r', 'g', 'p', 'l', 'o', 'a', 'd'} # {'a', 'c', 'd', 'e', 'f', 'g', ...}
- 자료형 정렬
10_Std lib > 11-1_pprint.py
import pprint t = [[[["black", "cyan"], "white", ["green", "red"]], [["magenta", "yellow"], "blue"]]] pprint.pprint(t, width=30) # [[[['black', 'cyan'], # 'white', # ['green', 'red']], # [['magenta', 'yellow'], # 'blue']]]
- 출력 폭 변경
10_Std lib > 11-2_textwrap.py
import textwrap doc = """The wrap() method is just like fill() except that it returns a list of strings instead of one big string with newlines to separate the wrapped lines.""" print(textwrap.fill(doc, width=40)) # The wrap() method is just like fill() # except that it returns a list of strings # instead of one big string with newlines # to separate the wrapped lines.
- 출력 형식 변경
10_Std lib > 11-3_locale.py
import locale locale.setlocale(locale.LC_ALL, "English_United States.1252") # locale.setlocale() : 숫자, 날짜, 통화 등에서 언어와 지역 특성을 반영하도록 세팅 # 영어(English), 미국(United States), Windows-1252(Western European language encoding) conv = locale.localeconv() x = 1234567.8 print(locale.format_string("%d", x, grouping=True)) # 1,234,567 print(locale.format_string( "%s%.*f", (conv["currency_symbol"], conv["frac_digits"], x), grouping=True ) ) # $1,234,567.80
템플릿
10_Std lib > 12_template.pyfrom string import Template
t = Template("$village에서 $cause으로 10,000원을 기부해주셨습니다.")
print(t.substitute(village="거제", cause="지역나눔"))
# 거제에서 지역나눔으로 10,000원을 기부해주셨습니다.
print(t.safe_substitute(village="부산", cause="현장모금"))
# 부산에서 현장모금으로 10,000원을 기부해주셨습니다.
쓰레드(멀티테스킹)
10_Std lib > 13_Thread.pyimport threading
import time
def print_numbers(name, start, end):
"""주어진 범위의 숫자를 출력하는 함수"""
for i in range(start, end):
print(f"{name}: {i}")
time.sleep(0.1) # 0.1초 대기 (시뮬레이션용)
# 스레드 생성
thread1 = threading.Thread(target=print_numbers, args=("Thread-1", 1, 6))
thread2 = threading.Thread(target=print_numbers, args=("Thread-2", 6, 11))
# 스레드 시작
thread1.start()
thread2.start()
# 메인 스레드가 두 스레드가 끝날 때까지 기다림
thread1.join()
thread2.join()
로깅
- 로깅 레벨의 의미
- DEBUG : 전혀 이상이 없지만, 임의의 순간에서 정보를 확인하려는 목적(개발자 마음)
- INFO : 전혀 이상이 없지만, 중요한 이벤트가 달성 되었는지 확인하려는 목적(빈도↓)
- WARNING : 예상 가능한 수준에서 문제가 발생했다는 경고 목적
- ERROR : 예상하지 못한 문제가 발생
- CRITICAL : 예상하지 못한 문제가 발생(심각), ERROR와 차별하기 위한.
- 빈도 <<<< DEBUG - INFO - WARNING - ERROR - CRITICAL >>> 중요도
- 예시. 출력레벨=WARNING (WARNING, ERROR, CRITICAL만 보여줌)
import logging
logging.basicConfig(level=logging.DEBUG) # 기본 로깅 수준을 DEBUG로 설정
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')
약한 참조
10_Std lib > 15_Weakref.pyclass A:
def __init__(self, value):
self.value = value
def __repr__(self):
return str(self.value)
a = A(10) # Step 1. 원본 작성
d = weakref.WeakValueDictionary() # Step 2. 약한 참조 생성
d["primary"] = a # Step 3. 연결
print(d["primary"])
del a # Step 4. 원본(reference)를 삭제
gc.collect() # Step 5. garbage collection(메모리에서 제거)
print(d["primary"]) # Step 6. 약한참조 조회 - ERROR: 존재하지않음
리스트(내장형)
10_Std lib > 16_Array.pyfrom array import array
a = array("H", [4000, 10, 700, 22222]) # 'H'는 부호없는 2byte숫자를 의미(0 ~ 65,536)
from collections import deque
d = deque(["task1", "task2", "task3"])
import bisect
scores = [(100, "perl"), (200, "tcl"), (400, "lua"), (500, "python")]
bisect.insort(scores, (300, "ruby")) # 순서에 맞춰 중간에 들어감
# [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]
2진 트리
10_Std lib > 16-4_Heapq.pyfrom heapq import heapify, heappop, heappush
data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
heapify(data) # rearrange the list into heap order
# [0, 1, 2, 6, 3, 5, 4, 7, 8, 9]
heappush(data, -5) # add a new entry
[-5, 0, 1]
print([heappop(data) for i in range(3)]) # fetch the three smallest entries
# [0단계] # [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] # 1 # / \ # 3 5 # / \ / \ # 7 9 2 4 # / \ / # 6 8 0 # [1단계] 삼각형에서 가장 낮은 숫자가 위로 이동 # 1 # / \ # 3 2 [3, 7, 9] 유지 # / \ / \ [5, 2, 4] 변경 [2, 5, 4] # 7 9 5 4 # / \ / # 6 8 0 # [2단계] # 1 # / \ # 3 2 # / \ / \ # 6 9 5 4 # / \ / [7, 6, 8] 변경 [6, 7, 8] # 7 8 0 # [3단계] 0입력 # 0 [1, 3, 2] 보다 더 작음 (1 -> 0)교체 # / \ # 3 2 # / \ / \ # 6 9 5 4 # / \ # 7 8 # [3단계-1] 1입력 # 0 # / \ # 1 2 [3, 6, 9]보다 작음 (3 -> 1)교체 # / \ / \ # 6 9 5 4 # / \ # 7 8 # [3단계-2] 3입력 # 0 # / \ # 1 2 # / \ / \ # 6 3 5 4 [1, 6, 9]에서 가장 큰 9를 밀어냄 (9 -> 3) # / \ # 7 8 # [최종] 9입력 # 0 # / \ # 1 2 # / \ / \ # 6 3 5 4 # / \ / # 7 8 9 # [0, 1, 2, 6, 3, 5, 4, 7, 8, 9] # [-5 추가] # -5 # / \ # 0 2 # / \ / \ # 6 1 5 4 # / \ / \ # 7 8 9 3 # [-5, 0, 2, 6, 1, 5, 4, 7, 8, 9, 3]
유효자리
2진수의 소수점 표현식은 유사 표현이기 때문에 오차가 누적되면 엉뚱한 결과가 도출됩니다. 2진수의 소수점 표현과 관련된 자세한 정보는 이곳을 참고하십시오.
10_Std lib > 17_Decimal.pyfrom decimal import *
print(Decimal("1.00") % Decimal(".10")) # 1을 0.1로 나누면... : 0 (정상)
print(1.00 % 0.10) # 0.09999999999999995 (비정상)
파이썬 예제에서, 1행과 2행의 비교가 부자연스러운 이유(sum함수 적용 유무, 동일 조건이 아님)는 파이썬 내장 함수 sum에서 유효자리 보정을 지원하기 때문이다.
print(sum([Decimal("0.1")] * 10) == Decimal("1.0")) # true
print(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0) # false
# 파이썬 내장 함수가 오류를 예방해준다.
print(sum([0.1] * 10) == 1) # true
따라서, sum 함수를 멍청(단순)하게 업그레이드 해주면, 정상적으로 Fail 할 수 있습니다.