목차보기

객체9_Class

객체 생성

객체란 객체가 가진 특성(Attribute)과 기능(method)으로 정의됩니다. 객체 지향 프로그래밍은 코드 뭉치를 하나의 대상으로 취급하는 방법론입니다. 이 때 코딩은 인간의 인지와 유사하게 작동합니다. 작은 개념들을 연결해서 더 큰 개념을 만들기 때문에 보다 직관적이고, 거대한 프로젝트를 가능하게 합니다. class 문법은 객체를 기술하는 방법을 제시합니다.

9_Class > 00_Class.py
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.py
class 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.py
class 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.py
my1 = MyClass(777)

mo = my1.printi  # method object 생성

mo()

attribute 공유

9_Class > 04_Instance Variable.py
class 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.py
class 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.py
class 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.py
def 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.py
class 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.py
class 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.py
class 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.py
import myPackage.myPack1
pack = myPackage.myPack1.pack1()

from myPackage.myPack1 import pack1
pack = pack1()

from myPackage.myPack2 import *
pack = pack2()

비공개 변수

Name Mangling(네임 맹글링)은 __ 접두사를 추가하여 외부에서 접근하기 어렵게 만들었습니다.

10_Private > filename.py
class 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

이터레이터

9_Class > 12_Iteration.py
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.py
import os

os.getcwd()  # 현재 작업중인 디렉토리 정보 가져오기
os.chdir("otherDir")  # 작업디렉토리 변경
os.system("mkdir data")  # data라는 디렉토리 생성

디렉토리 내용조회

10_Std lib > 02_Glob.py
import glob

files = glob.glob("*")
for file in files:
    print(file)

입력인자 분석

10_Std lib > 03_PrintArgs.py
import 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.py
import 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.py
import 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.py
import 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.py
from 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.py
from 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(교환)방법으로, 임시변수를 사용한 방법과 튜플 언패킹을 사용한 방법의 성능차이를 보여줍니다.

코드 테스트(검증)

인터프리터 출력

템플릿

10_Std lib > 12_template.py
from 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.py
import 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()

로깅

10_Std lib > 14_log.py
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.py
class 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.py
from 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.py
from 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.py
from 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 할 수 있습니다.

이전 | 객체지향 | 마지막