Study/Programming

Python 요약 2

빨간당무 2017. 10. 6. 18:26

# Class

# __init__() # 인스턴스 생성자

# __new__() # 클래스 생성자

# 인스턴스 메소드는 항상 self 매개변수를 포함해야

class Car:

    # 인스턴스 생성자

    def __init__(self): 

        # 인스턴스 변수들

        self.color = 0xFF0000

        self.wheel_size = 16

        self.displacement = 2000

    def forward(self):

        print('call car.forward()')

    def backward(self):

        print('call car.backward()')

    def turn_left(self):

        print('call car.turn_left()')

    def turn_right(self):

        print('call car.turn_right()')


if __name__ == '__main__':

    my_car = Car()

    print('0x{:02X}'.format(my_car.color))

    print(my_car.wheel_size)

    print(my_car.displacement)

    

    my_car.forward()

    my_car.backward()

    my_car.turn_left()

    my_car.turn_right()



class ClassVar:

    # 클래스 변수

    text_list = [] # 여러 인스턴스에서 함께 접근이 가능함

    

    def add(self, text):

        self.text_list.append(text)

    

    def print_list(self):

        print(self.text_list)


if __name__ == '__main__':

    a = ClassVar()

    a.add('a')

    a.print_list() # ['a'] 출력을 기대함

    

    b = ClassVar()

    b.add('b')

    b.print_list() # ['b'] 출력을 기대하나 실제 ['a', 'b'] 출력됨



class InstanceVar:

    def __init__(self):

        self.text_list = []

        

    def add(self, text):

        self.text_list.append(text)

    

    def print_list(self):

        print(self.text_list)


if __name__ == '__main__':

    a = InstanceVar()

    a.add('a')

    a.print_list() # ['a'] 출력을 기대함

    

    b = InstanceVar()

    b.add('b')

    b.print_list() # ['b'] 출력을 기대한 처럼 실제 ['b'] 출력됨



class ContactInfo:

    # 인스턴스 생성자 지정시 self 외에 추가로 매개변수를 지정 가능함

    def __init__(self, name, email):

        self.name = name

        self.email = email

    

    def print_info(self):

        print('{0} : {1}'.format(self.name, self.email))


if __name__ == '__main__':

    md = ContactInfo('명덕','email')

    md2 = ContactInfo('명덕2','email2')

    

    md.print_info()

    md2.print_info()



# 클래스에 속한 메소드로

# 정적 메소드(Static Method) @staticmethod

#   정적 메소드는 self 매개변수를 전달 받을 방법이 없음

#   객체/인스턴스의 변수에 접근할 없음. 정적 메소드는 객체의 데이터 속성과 관계가 없는 코드로 구현되는 것이 일반적임

# 클래스 메소드(Class Method) @classmethod

#   cls 통해 클래스 자체를 전달

# 인스턴스 메소드(Instance Method) #데코레이터 없음

#   self 통해 해당 인스턴스 자체를 전달


class Calculator:

    @staticmethod

    def plus(a, b):

        return a + b

    @staticmethod

    def minus(a, b):

        return a - b

    @staticmethod

    def multiply(a, b):

        return a * b

    @staticmethod

    def divide(a, b):

        return a / b


if __name__ == '__main__':

    print('{0} + {1} = {2}'.format(7, 4, Calculator.plus(7, 4)))

    print('{0} - {1} = {2}'.format(7, 4, Calculator.minus(7, 4)))

    print('{0} * {1} = {2}'.format(7, 4, Calculator.multiply(7, 4)))

    print('{0} / {1} = {2}'.format(7, 4, Calculator.divide(7, 4)))



class InstanceCounter:

    count = 0

    def __init__(self):

        InstanceCounter.count += 1

    

    @classmethod # 클래스 메소드를 정의하기 위해 @classmethod 데코레이터를 앞에 붙임

    def print_instance_count(cls): # 메소드의 매개변수를 하나 이상 정의함 cls 클래스를 나타내는 필수 매개변수

        print(cls.count)


if __name__ == '__main__':

    a = InstanceCounter()

    InstanceCounter.print_instance_count()

    

    b = InstanceCounter()

    InstanceCounter.print_instance_count()

    

    c = InstanceCounter()

    InstanceCounter.print_instance_count()



# Private member 

# 1. 개의 밑줄 __ 접두사여야 . ) __number

# 2. 접미사는 밑줄이 개까지만 허용됨. ) __number_

# 3. 접미사의 밑줄이 이상이면 퍼블릭 멤버로 간주함. ) __number__

class HasPrivate:

    def __init__(self):

        self.public = 'public'

        self.__private = 'private'

    

    def print_from_internal(self):

        print(self.public)

        print(self.__private)

        

if __name__ == '__main__':

    obj = HasPrivate()

    obj.print_from_internal()

    

    print(obj.public)

    # print(obj.__private) # 오류 발생

    # AttributeError: 'HasPrivate' object has no attribute '__private'



# 상속(Inheritance)

# class 기반클래스:

#     # 멤버 정의

# class 파생클래스(기반클래스):

#     # 아무 멤버를 정의하지 않아도 기반클래스의 모든 것을 상속 받음

#     # , Private member(__ 시작되는) 제외

class Base:

    def base_method(self):

        print('base_method')


class Derived(Base):

    pass


if __name__ == '__main__':

    base = Base()

    base.base_method()

    

    derived = Derived()

    derived.base_method()



# 기반클래스의 명시적 생성자(__init__) 호출

# 모든 클래스는 object 파생클래스임

# super() 기반클래스의 객체 역할을 하는 프록시(Proxy) 반환하는 내장 함수

class A:

    def __init__(self):

        print('A.__init__()')

        self.message = 'Hello'


class B(A):

    def __init__(self):

        #A.__init__(self) # B 클래스 안에서 A 클래스의 __init__() 메소드를 호출

        super().__init__() # 코드와 동일한 결과

        print('B.__init__()')


if __name__ == '__main__':

    obj = B()

    obj.message # error 발생

    # AttributeError: 'B' object has no attribute 'message'

    # 기대와는 다르게 A.__init__ 호출 되지 않음

    # 파이썬은 암묵적(Implicit) 것을 실헝하고 명시적인 것을 선호함

    # 따라서 위에 A.__init__(self) 메소드를 호출해야



# super() 없이 상위 클래스의 __init__() 메소드를 호출하는 경우

# 기반 클래스의 __init__() 메소드가 호출됨

class Base:

    def __init__(self):

        print('Base')


class Derived(Base):

    pass


if __name__ == '__main__':

    d = Derived()



# 다중 상속

class A:

    def method(self):

        print('A')


class B(A):

    def method(self):

        print('B')


class C(A):

    def method(self):

        print('C')


class D(B, C):

    pass


class E(C, B):

    pass


if __name__ == '__main__':

    obj = D()

    obj.method() # 다중 상속의 문제 (다이아몬드 형태의 상속)

    # B, C 클래스가 A 클래스를 상속 받아 method() 메소드를 오버라이딩(Overriding) 하고 

    # D 클래스가 B, C 클래스를 다중 상속 받아 method() 메소드가 겹쳐지는 문제가 발생할

    # 기반 클래스 목록에서 먼저 선언된 (B C보다 먼저 선언됨) B 클래스의 method() 상속받음

    # 오버라이딩(Overriding) 기반 클래스로부터 상속받은 메소드를 다시 정의하는

    obj2 = E()

    obj2.method()



# 오버라이딩되기 이전의 기반 클래스의 메소드를 호출하는 경우

class Car:

    def ride(self):

        print('Run')


class FlyingCar(Car):

    def ride(self):

        super().ride() # super() 통해 기반 클래스의 메소드를 호출함

        print("Fly")


my_car = FlyingCar()

my_car.ride()



# 데코레이터: 함수를 꾸미는 객체

# __call__ 객체를 함수 호출 방식으로 사용하게 만드는 특별 메소드

class Callable:

    def __call__(self):

        print('I am Called.')


if __name__ == '__main__':

    obj = Callable()

    obj()



class MyDecorator:

    # __init__() 메소드의 매개변수를 통해 함수를 받아들이고 데이터 속성에 저장함

    def __init__(self, f):

        print('Initializing MyDecorator...')

        self.func = f

    

    def __call__(self):

        print('Begin :{0}'.format(self.func.__name__))

        # __call__() 메소드가 호출되면 생성자에서 저장해둔 함수(데이터 속성) 호출함

        self.func()

        print('End :{0}'.format(self.func.__name__))


# 데코레이터를 사용하는 방법 1: 생성자

def print_hello():

    print('Hello')


print_hello = MyDecorator(print_hello)

print_hello()


# 데코레이터를 사용하는 방법 2: @기호

@MyDecorator

def print_hello2():

    print('Hello')


print_hello2()


# 생성자 방법과 @기호 방법은 기능적으로 완전히 똑같이 동작함



# for문으로 list 순회하는 예제

list = [1, 2, 3, 4]

for e in list:

    print(e)


print('--')


for i in range(3):

    print(i)


print('--')


iterator = range(3).__iter__()

print(iterator.__next__()) # 0

print(iterator.__next__()) # 1

print(iterator.__next__()) # 2

#print(iterator.__next__()) # error 발생, StopIteration 예외 발생


# range() __iter__() 메소드를 구현하는 순회가능한 객체를 반환하는 함수



# for문으로 순회 가능(Iterable) 객체 생성

class MyRange:

    def __init__(self, start, end):

        self.current = start

        self.end = end

    

    def __iter__(self):

        return self

    

    def __next__(self):

        if self.current < self.end:

            current = self.current

            self.current += 1

            return current

        else:

            raise StopIteration()


for i in MyRange(0, 5):

    print(i)



# 제네레이터(Generator) 이터레이터(Iterator)처럼 동작하는 함수

# Generator 클래스를 정의하지 않아도 되고, __iter__(), __next__() 메소드를 구현할 필요 없음

# 다만, yield 문을 이용해 값을 반환하면

# yield문은 return 처럼 함수를 실행하다가 값을 반환하지만, return 처럼 함수를 종료시키지 않고 중단시켜 놓기만

def generator():

    yield 0

    yield 1

    yield 2

    yield 3

    yield 4


iterator = generator()

print(iterator.__next__())

print(iterator.__next__())

print(iterator.__next__())

print(iterator.__next__())

print(iterator.__next__())

# print(iterator.__next__()) # error 발생


for i in generator():

    print(i)


def YourRange(start, end):

    current = start

    while current < end:

        yield current

        current += 1

    return


for i in YourRange(0, 5):

    print(i)



# 추상 기반 클래스(Abstract Base Class) 파생 클래스가 갖춰야 특징(메소드) 강제하는 기능을

# 추상 클래스를 정의할 때는 abc 모듈의 ABCMeta 클래스와 @abstractmethod 데코레이터를 이용함

# 메타 클래스는 클래스에 대한 정보를 갖고 있는 클래스를 말함

# ABCMeta 클래스는 클래스가 특정 메소드를 구현하는지를 테스트하는 기능을 갖고 있음

from abc import ABCMeta

from abc import abstractmethod


class AbstractDuck(metaclass = ABCMeta):

    @abstractmethod

    def Quack(self):

        pass


#class Duck(AbstractDuck):

#    pass


#duck = Duck() # error 발생


# TypeError: Can't instantiate abstract class Duck with abstract methods Quack

# AbstractDuck 상속 받는 파생 클래스인 Duck Quack() 메소드를 반드시 구현해야


class Duck(AbstractDuck):

    def Quack(self):

        print('[Duck] Quack')


duck = Duck()

duck.Quack()



# 예외처리 try ~ except ~ else ~ finally

# 예외발생 raise


def my_power(y):

    print("숫자를 입력하세요.")

    x = input()

    return int(x) ** y


my_power(2) # a 입력 ValueError: invalid literal for int() with base 10: 'a'



my_list = [1, 2, 3]


try:

    print('첨자를 입력하세요.')

    index = int(input())

    print(my_list[index]/0)

except ZeroDivisionError as err: # 0으로 나눌 예외 발생

    print('0으로 나눌 없습니다. ({0})'.format(err))

except IndexError as err: # 인덱스 예외 발생

    print('잘못된 첨자입니다. ({0})'.format(err))

except : # 앞서 정의 예외 발생 모든 예외 처리

    print('예외가 발생했습니다.')



my_list = [1, 2, 3]

try:

    print('첨자를 입력하세요')

    index = int(input())

    print("my-list[{0}]: {1}".format(index, my_list[index]))

except Exception as err:

    print("예외가 발생했습니다. ({0})".format(err))

else :

    print("리스트의 요소 출력에 성공하였습니다.")

finally:

    print("마지막에 무조건 실행됩니다.")



try:

    raise Exception("예외를 발생시킴")

except:

    print('예외가 일어남')



def some_function():

    print("1~10 사이의 수를 입력하세요:")

    num = int(input())

    if num < 1 or num > 10:

        raise Exception("유효하지 않은 숫자입니다.: {0}".format(num))

    else:

        print("입력한 수는 {0}입니다.".format(num))


try:

    some_function()

except Exception as err:

    print("예외가 발생했습니다. {0}".format(err))



# 발생된 예외처리를 상위 호출자에게 전달

def some_function():

    print("1~10 사이의 수를 입력하세요:")

    num = int(input())

    if num < 1 or num > 10:

        raise Exception("유효하지 않은 숫자입니다.: {0}".format(num)) # -1- : 예외처리 발생

    else:

        print("입력한 수는 {0}입니다.".format(num))


def some_function_caller():

    try:

        some_function()

    except Exception as err: # -2- : -1-에서 발생시킨 예외를 받아서 처리함

        print("1) 예외가 발생했습니다. {0}".format(err))

        raise # -3- : 앞서 받은 예외를 그대로 다시 발생


try:

    some_function_caller()

except Exception as err: # -4- : -3-에서 발생시킨 예외를 받아서 처리함

    print("2) 예외가 발생했습니다. {0}".format(err))



# 사용자 정의 예외 형식

class MyException(Exception):

    def __init__(self):

        super().__init__("MyException 발생했습니다.")

        

# raise MyException() # MyException: MyException 발생했습니다.



# 문자열을 정수로 변환하는 convert_to_integer() 함수가 매개변수를 확인하여

# 자신이 처리할 없는 문자가 들어 있으면 사용자 정의 예외인 InvalidIntException() 일으키는 코드

class InvalidIntException(Exception):

    def __init__(self, arg):

        super().__init__('정수가 아닙니다. {0}'.format(arg))


def convert_to_integer(text):

    if text.isdigit(): # 부호(+, -) 처리 못함

        return int(text)

    else:

        raise InvalidIntException(text)


if __name__ == '__main__':

    try:

        print('숫자를 입력하세요:')

        text = input()

        number = convert_to_integer(text)

    except InvalidIntException as err:

        print('예외가 발생했습니다 ({0})'.format(err))

    else:

        print('정수 형식으로 변환되었습니다 : {0}({1})'.format(number, type(number)))



my_list = [1, 2, 3, 4, 5]


def print_element(arg, index):

    try:

        print(arg[index])

    except IndexError as err:

        print("유효한 인덱스 범위를 벗어났습니다. {0}".format(err))


print_element(my_list, 2)

print_element(my_list, 4)

print_element(my_list, 5)



# 파일 처리 : 열기 -> 읽기/쓰기 -> 닫기

# file = open()

# file.read() / file.write()

# file.close()


file = open('test.txt', 'w')

file.write('Hello')

file.close()



file = open('test.txt', 'r')

str = file.read()

print(str)

file.close()



# 자원 누수 방지 with ~ as

with open('test.txt', 'r') as file: # __enter__() 호출

    str = file.read()

    print(str) # 마지막 라인에서 __exit__() 호출

    #file.close() # 생략해도 컨텍스트 매니저(Context Manager) 통해 블록을 관리함

    # 컨텍스트 매니저는 __enter__() __exit__() 메소드를 구현함



class open2(object):

    def __init__(self, path):

        print('initialized')

        self.file = open(path)

    

    def __enter__(self):

        print('entered')

        return self.file

    

    def __exit__(self, ext, exv, trb):

        print('exited')

        self.file.close()

        return True


with open2("test.txt") as file:

    s = file.read()

    print(s)



# 컨텍스트 매니저를 구현하기 위한 클래스

# @contextmanager 데코레이터

from contextlib import contextmanager # contextlib 모듈로부터 contextmanager 반입


@contextmanager # @contextmanager 데코레이터로 함수 수식

def 함수이름():

    # 자원 획득

    try:

        yield 자원 # yield문을 통해 지원 반환 : with문의 코드블록이 시작될 실행됨

    finally:

        # 자원 해제 # with문의 코드블록이 종료될 실행됨



# @contextmanager 데코레이터 예제

from contextlib import contextmanager


@contextmanager

def open3(path):

    print('opening file...')

    file = open(path)

    try:

        print('yielding file...')

        yield file

    finally:

        print('closing file...')

        file.close()


with open3("test.txt") as file:

    s = file.read()

    print(s)



# open 함수의 8 매개변수

# open(file, mode='r', buffering=-1. encoding=None, errors=None, newline=None, closefd=True, opener=None)


# mode - 파일 접근 모드 설정 (가본값은 'rt')

# 'r' = 읽기용(기본값) / 'w' = 쓰기용 / 'x' = 배타적 생성모드로 열기, 파일이 존재하면 IOError 예외발생 / 

# 'a' = 쓰기용, 'w'와는 다르게 기존에 파일이 존재하는 경우 기존 내용에 덧붙임 / 

# 'b' = 바이너리 모드 / 't' = 텍스트 모드(기본값) / '+' = 읽기/쓰기용으로 파일 읽기


# buffering - 버퍼링 모드 (기본값은 -1)

# 0 - 버퍼링 미수행 - 바이너리 모드에서만 사용 가능

# 1 - 개행 문자(\n) 만날 때까지 버퍼링하는 라인 버퍼링 수행 - 텍스트 모드에서만 사용 가능

# 2 - 임의의 값으로 버퍼의 크기를 직접지정하고 싶을 때는 1보다 큰수를 입력


# encoding - 텍스트 인코딩

# 파이썬 지원 인코딩 - https://docs.python.org/3/library/codecs.html#standard-encodings


# errors - encoding 매개변수와 관련하여 문자열 인코딩과 디코딩 수행시 발생하는 에러처리 방법 설정

# 'strict' - 인코딩 에러가 발생할 ValueError 예외를 발생, None 똑같은 효과

# 'ignore' - 에러 무시

# 'replace' - 기형적인 데이터가 있는 경우 대체 기호(?) 삽입

# 'surrogateescape' - U+DC80 ~ U+DCFF 사이에 있는 유니코드 사용자 자유 영역의 잘못된 바이트를 코드 포인트로 나타냄

# 'xmlcharrefreplace' - 파일에 기록하려는 텍스트 안에서 지정된 인코딩에서 지원되지 않는 문자를 &#NNN; 꼴의 XML 문자 참조로 바꿔 기록, 쓰기만

# 'backslashreplace' - 파일에 기록하려는 텍스트 안에서 지정된 인코딩에서 지원되지 않는 문자를 역슬래시(\) 시작되는 이스케이프 시퀀스로 바궈 기록, 쓰기만


# newline - 파일 읽고 줄바꿈을 어떻게 처리할지를 나타냄

# None, '', '\n', '\r', '\r\n' 하나를 입력


# closefd - file 매개변수에 파일 경로가 아닌 파일 기술자가 입력되었을 사용됨


# opener - 파일을 여는 함수를 직접 구현하고자 사용



import os

os.linesep # 현재 시스템이 지원하는 개행 문자



# 문자열을 담은 리스트를 파일에 쓰는 write() 메소드

lines = ["We'll find a way we always have - Interstellar\n", 

        "I'll find you and I'll kill you - Take\n", 

        "I'll be back - Terminator 2\n"]


with open('movie_quotes.txt', 'w') as file:

    for line in lines:

        file.write(line) # 마다 쓰기



# 문자열을 담은 리스트를 파일에 쓰는 writelines() 메소드

lines = ["We'll find a way we always have - Interstellar\n", 

        "I'll find you and I'll kill you - Take\n", 

        "I'll be back - Terminator 2\n"]


with open('movie_quotes.txt', 'w') as file:

    file.writelines(lines) # 모든 줄을 한꺼번에 쓰기



# 단위로 텍스트를 읽는 readline() 메소드

with open('movie_quotes.txt', 'r') as file:

    line = file.readline() # 읽기

    

    while line != '': # readline() 메소드는 파일의 끝에 도달하면 '' 반환함, 빈줄의 경우 개행 문자를 반환함

        print(line, end='')

        line = file.readline()



# 단위로 텍스트를 읽는 readlines() 메소드

with open('movie_quotes.txt', 'r') as file:

    lines = file.readlines() # 모든 읽기

    #line = ''

    for line in lines:

        print(line, end='')



lines = ['안녕하세요?\n', 

        'Hello\n']


with open('greetings_utf8.txt', 'w', encoding='utf-8') as file:

    for line in lines:

        file.write(line)



with open('greetings_utf8.txt', 'r', encoding='utf-8') as file:

    lines = file.readlines()

    for line in lines:

        print(line, end = '')

# 한글 버전 윈도우8/윈도우10 명령 프롬프트의 기본 인코딩은 CP949



# 유니코드 문서를 강제로 ascii 인코딩으로 읽는 예제

with open('greetings_utf8.txt', 'r', encoding='ascii', errors='ignore') as file:

    lines = file.readlines()

    line = ''

    for line in lines:

        print(line)



# 바이너리 파일 처리 - struct 모듈 이용

import struct

packed = struct.pack('i', 123) # 'i' 따라 4바이트 크기의 bytes 객체를 packed 준비하고 123 값을 복사함

for b in packed:

    print(b)

    

unpacked = struct.unpack('i', packed) # 튜플 형식을 반환

unpacked

type(unpacked)

# struct.pack(fmt, v1, v2, ...) # 데이터 -> bytes

# struct.unpack(fmt, buffer) # bytes -> 튜플(데이터)



# pack unpack 형식문자열(fmt) pp.262~264 참고

# 예를 들어 =2f12si 경우, 바이트 순서(=) 시스템 바이트 순서를 따르며, 

# 4바이트 부동소수형 2(2f), 크기가 12 bytes(12s), 4바이트 부호있는 정수형 1(i) 이루어진 구조를 나타냄

# Ref. https://docs.python.org/3/library/struct.html#format-strings

import struct

packed = struct.pack('f', 123.456) # float

unpacked = struct.unpack('f', packed)

print(unpacked) # (123.45600128173828,)


packed = struct.pack('12s', '빨간당무'.encode()) # 12 bytes

unpacked = struct.unpack('12s', packed)

print(unpacked) # (b'\xeb\xb9\xa8\xea\xb0\x84\xeb\x8b\xb9\xeb\xac\xb4',)


packed = struct.pack('2d2i', *(123.456, 987.765, 123, 456)) # double x2, integer x2

# *연산자가 튜플이나 리스트의 요소를 하나씩 불리해서 매개변수로 입력시켜줌

unpacked = struct.unpack('2d2i', packed)

print(unpacked) # (123.456, 987.765, 123, 456)



# 바이너리 데이터 쓰기 예제

import struct


struct_fmt = '=16s2fi' # char[16], float[2], int

city_info = [

    # CITY, Latitude, Longitude, Population

    ('서울'.encode(encoding='utf-8'), 37.566535, 126.977969, 9820000),

]


with open('cities.dat', 'wb') as file:

    for city in city_info:

        file.write(struct.pack(struct_fmt, *city))



# 바이너리 데이터 읽기 예제

import struct


struct_fmt = '=16s2fi' # char[16], float[2], int

struct_len = struct.calcsize(struct_fmt)


cities = []

with open('cities.dat', 'rb') as file:

    while True:

        buffer = file.read(struct_len)

        if not buffer: break

        city = struct.unpack(struct_fmt, buffer)

        cities.append(city)

    

    for city in cities:

        name = city[0].decode(encoding='utf-8').replace('\x00', '')

        # pack()하는 과정에서 문자를 할당하고 남은 공간에 채워진 \x00 디코딩한 문자열로 다시 바꿔 넣음

        print('City:{0}, Lat/Long:{1}/{2}, Population:{3}'.format(name, city[1], city[2], city[3]))