Auspice by Goorm, Manage by DAVIAN @ KAIST

Lecture 7: Pythonic Programming

Comprehension

  • List, Dictionary 등을 빠르게 만드는 기법.
  • for + append 보다 속도 빠름.
  • ```python result = [] for i in range(10): result.append(i * 2)

example

result = [i * 2 for i in range(10)]

if 문을 마지막에 달아 원하는 요소만 추가 가능.

evens = [i for i in range(100) if i % 2 == 0]

겹 for문 사용 가능.

result = [(i,j) for i in range(5) for j in range(i)]

다차원 배열 만들기 매우 유용.

eye = [[int(i==j) for j in range(5)] for i in range(5)]


### Generator

- 요소를 하나씩 생성해서 반환하는 객체를 **Generator**라고 함.
example) **range** 함수의 경우 숫자를 하나씩 생성하여 반환.

```python
def my_range(stop):
	number = 0
	while number < stop:
		yield number # function에 yield를 사용할 시 Generator가 됨.
		number += 1  # yield 하는 위치에서 값을 반환. 다시 값을 요청 받을 시 
					 # yield 다음 줄부터 실행. Return 될 시 반복을 멈춤.

for i in my_range(5):
	print(i)

Sequence 전체를 생성하는 것이 아니므로 메모리 효율적. 매우 큰 데이터 셋을 처리할 땐 Generator 사용 권장.

Built-in Functions

sum, any, all, max, min, zip, enumerate, Lambda Function, map, filter …

zip

  • 2개 이상의 순환 가능한 객체를 앞에서부터 한번에 접근할 때 사용.
seq1 = ['What', 'is', 'zip']  
seq2 = [True, False, True]  
seq3=[1,2,3,4] #길이가 안 맞을 경우 남는 건 버림  
for w1, w2, w3 in zip(seq1, seq2, seq3): # 앞에서 부터 하나씩 빼어 Tuple로 반환  
	print(w1, w2, w3)
	
>>> What True 1
>>> is False 2
>>> zip True 3

enumerate

  • For문이 Sequence를 돌 때 해당하는 idex가 필요할 때 사용함.
  • zip과 enumerate를 동시에 사용하는 등 여러 Generator를 한번에 사용.
seq1 = ['This', 'sentence']  
seq2 = [True, False]  
for i, (a, b) in enumerate(zip(seq1, seq2)):  
    print(i, a, b)
>>> 0 This True
>>> 1 sentence False

Lambda Function

  • 함수의 이름 없이 빠르게 만들어 쓸 수 있는 익명 함수.
  • 여러 줄을 쓸 수 없음.
  • 공식적으로는 Lambda의 사용을 권장하지는 않지만, 많이 씀. 문서화 지원 미비. 이름이 존재하지 않는 함수가 생성. 복잡한 함수 lambda로 작성할 시 가독성 하락.
def add(a,b):
	return a + b

add = lambda a, b: a + b

Lecture 8: Object-Oriented Programming (OOP)

  • 방법 1: 절차 지향 프로그래밍 교수용 프로그램과 학생용 프로그램을 따로 만듦. 수강 신청과정을 처음부터 끝까지 작성.

  • 방법 2: 객체 지향 프로그래밍 (OOP) 수강신청을 하는 주체들(학생, 교수)의 행동과 데이터를 정의. 각 개체들을 엮어 유기적인 수강신청 API 작성.

OOP Example

Class (설계도)

  • Attribute (데이터, 속성)
    • 이름
    • 학번
    • 현재 수강중인 강의
  • Method (행동)
    • 특정 강의를 수강하기.
    • 특정 강의를 드랍하기.

Instance (객체)

  • Attribute (데이터, 속성)
    • 이창석
    • 2015000000
    • CS241
  • Method (행동)
    • 특정 강의를 수강하기.
    • 특정 강의를 드랍하기.

Class

Class Example

class Student(object):  
    SCHOOL = "goorm" 					   # 클래스 속성 (Class attribute)  
  def __init__(self, name: str, sid: int): # 생성자  
	  self.name = name                     # 속성 (Attribute)  
	  self.sid = sid  
      self.classes = set()  
  
    # 클래스 함수 (Method)  
    def take_class(self, class_name: str) -> None:  
        self.classes.add(class_name)  
  
    def drop_class(self, class_name: str) -> None:  
        self.classes.discard(class_name)
  • 학생 객체(Instance)를 만들고 써보기.
changseok_lee = Student('ChangSeok Lee', 2015000000)  
  
# 속성 출력  
print(changseok_lee.name, "in", Student.SCHOOL)
>>> ChangSeok Lee in goorm

changseok_lee.take_class("CS101")  
changseok_lee.take_class("CS202")  
changseok_lee.drop_class("CS101")
print(changseok_lee.classes)
>>> {'CS202'}

Class Declaration: Class 선언부

  • 클래스 이름은 CamelCase가 관습적으로 사용됨. (ex: NormalCase.. 낙타 등 모양)
  • 부모 클래스가 지정되지 않았을 시 object가 자동 상속됨.

    class Student(object): 예약어(class) / 클래스 이름(Student) / 부모 클래스(object)

Class Attribute: Class 속성

 class Student(object):
	 SCHOOL = 'goorm' # 클래스 속성 (Class attribute)
  • 클래스 전체가 공유하는 속성 값.
  • 모든 객체(instance)가 같은 값을 참조.
  • 남용하면 스파게티 코드의 원인이 됨.

Class.attr 형태로 접근. (속성 출력)

print(Student.SCHOOL)
>>> goorm

Method: Class 함수

 class Student(object):
	# 클래스 함수 (Method)
	def take_class(self, class_name: str) -> None:  
		   self.classes.add(class_name)	 
  • 각 객체에 적용이 가능한 함수.
  • 현재 수정하고자 하는 객체를 “self“로 지칭. (관습) 파이썬은 “self”를 첫번째 파라미터로 명시적으로 받음.
  • Class.method(instance, args, ..) 혹은 instance.method(args, …)
# 메소드 실행 [ instance.method(args, ...) ]
changseok_lee.take_class("CS101")

Class Attribute: 객체 속성

class Student(object):
	def __init__(self, name: str, sid: int): # 생성자
		self.name = name					 # 속성 (Attribute)
  • 각각의 객체가 개인적으로 가지는 값.
  • instance.attr의 형태로 접근
  • class 형태로 선언되어 나온 객체는 언제 어디서는 attribute 수정 가능.

Magic Method

  • 메소드 이름이 __METHOD__ 형태일 경우 특별한 Magic Method.

Initializer

생성자 : __init__

class Student(object):
	def __init__(self, name: str, sid: int): # 생성자
		self.name = name					 # 속성 (Attribute)
		self.sid = sid
		self.classes = set()
  • 객체를 생성할 때 호출됨.
  • 일반적으로 객체의 속성을 초기화 하는데 사용.
  • Class(args, ..) 형태로 호출하여 객체 생성.
changseok_lee = Student('ChangSeok Lee', 2015000000)
  • 거의 유일하게 정해진 Argument format이 없는 Magic Method.

Destroyer

소멸자 : _del_

class Student:
	def __del__(self):
		self.classes.clear()
  • 객체를 소멸할 때 호출 됨.
  • 파이썬은 Garbage Collection, GC로 메모리 관리. 객체가 어디에서도 참조되지 않을 때 객체가 소멸. 소멸 타이밍을 잡기 어려워 잘 사용되지 않음.
  • del 명령어 del changseok_lee 변수 이름을 명시적으로 없애기 가능. 참조를 명시적으로 삭제하는 것이고, 객체를 명시적으로 삭제하는 것이 아님.

#### Indexing Indexing 메소드 : __getitem__, __setitem__

class DoubleMapper(object):  
    def __init__(self):  
        self.mapping = {}  
    def __getitem__(self, index):	    # Indexing get  
	    return self.mapping.get(index, index * 2)  
    def __setitem__(self, index, item): # Indexing set  
		self.mapping[index] = item
		
mapper = DoubleMapper()  
print(mapper[10], mapper[1, 2])
>>> 20 (1, 2, 1, 2)

mapper[10] = 15  
print(mapper[10], mapper[1, 2])
>>> 15 (1, 2, 1, 2)
  • [] indexing을 재정의.

#### Length Length 메소드 : __len__

class Dataset:  
    def __init__(self, data, times=3):  
        self.data = data  
        self.times = times  
    def __len__(self): # len(instance) 호출될 시 호출  
		return len(self.data) * self.times  
    def __getitem__(self, index):  
        if index > len(self):  
            raise IndexError()  
        return self.data[index % len(self.data)]  

dataset = Dataset([10, 2, 5, 2], times=5)  
print(len(dataset))
>>> 20

#### Typing

  • 객체를 다른 타입으로 형 변환할때 호출.
  • 이외에도 __int__,__float__,__bool__ 등 존재.
class Student:  
    def __init__(self, name: str, sid: int):  
        self.name = name  
        self.sid = sid  
    def __str__(self): # str 형변환  
		return self.name + '_' + str(self.sid)  
  
changseok_lee = Student("ChangseokLee", 2015000000)  
print(changseok_lee) # str 형태 출력
>>> ChangseokLee_2015000000

#### Comparison Operator

  • A < B를 호출 -> A.__lt__(B)를 호출. ( lt == less than )
  • 이외에도 __le__,__gt__,__ge__,__eq__,__ne__ 가 존재.
class Student:  
    def __init__(self, name: str, sid: int):  
        self.name = name  
        self.sid = sid  
    def __lt__(self, other):  
        return self.sid < other.sid # < 연산자를 재정의.  
students = [
	Student("Lee", 1234,
	Student("Chang", 4566),
	Student("Seok", 2267)
]  
  
print(*[student.name for student in sorted(students)]) # Sorted 사용 가능
>>> Lee Seok Chang

#### Arithmetic Operator

class MyComplex:  
    def __init__(self, real, imaginary):  
        self.real = real  
        self.imaginary = imaginary  
    def __str__(self):  
        return str(self.real) + '+' + str(self.imaginary) + "j"  
  def __add__(self, other): # + 연산자 재정의  
  return MyComplex( self.real + other.real,  
  self.imaginary + other.imaginary) # Out-place 연산  
  
a = MyComplex(3, -5)  
b = MyComplex(-6, 7)  
print(a + b)
>>> -3+2j

#### Callable 함수화 메소드 : __call__

class AdditionNumber(object):  
    def __init__(self, number: int):  
        self.number = number  
    def __call__(self, number: int):  
        return number + self.number  
  
addition_5 = AdditionNumber(5)  
print(addition_5(10))
>>> 15
  • 생성된 객체를 호출 가능하게 만듦.
  • instance(agrs, …)가 instance.__call__(args, …)를 호출.

Iterable

# 평소 사용하는 for문 
seq = [1,2,3,4,5]  
for elem in seq:  
  print(elem, end=' ')  
>>> 1 2 3 4 5

# for문이 내부에서 돌아가는 원리.  
seq = list([1,2,3,4,5])  
iterable = iter(seq) 
while True:  
    try:  
        elem = next(iterable)  
    except StopIteration:  
        break  
  print(elem, end=' ')
>>> 1 2 3 4 5

실제 for문에서 일어나는 일은?

  • iter 내장 함수 해당 객체의 순환자 반환. __iter__ 호출.
  • next 내장 함수 해당 순환자를 진행. __next__ 호출.

  • 끝에서 StopIteration Exception
  • Generator는 자동으로 __iter____next__가 구현.

Context Manager

class Student:  
    def __init__(self, name, sid):  
        self.name = name  
        self.sid = sid  
  
    def __enter__(self):  # with 구문에 들어갈 때 사용, return 값이 as 이하로 할당  
  self.classes = set()  
        return self  
  def __exit__(self, exc_type, exc_value, trace): # with 구문 나갈 때 사용  
  self.classes.clear()  
          
CS_Lee = Student("LCS", 2015)  
with CS_Lee:  
    CS_Lee.classes.add('CS201')  
  
with Student("LCS", 2015) as CS_Lee:  
    CS_Lee.classes.add('CS201')
  • 소멸자 대용으로 특정 Block 입장/종료 시 자동으로 호출.
  • File description 등을 자동으로 닫고자 할 때 사용.

#### Property

값을 가져오는 Method를 getter. 값을 저장하는 Method를 setter.

  • Property를 통해 getter와 setter를 명시적으로 설정 가능.
  • Encapsulation 등에 활용.
class Person:
	def __init__(self):
		self.__age = 0

	@property
	def age(self): # getter
		return self.__age

	@age.setter
	def age(self, value): # setter
		self.__age = value

LCS = Person()
LCS.age = 27
print(LCS.age)
>>> 27

#### Static & Class Method 파이썬에는 2가지 정적 함수 존재.

  • 객체에는 접근 불가능.
  • 일반적으로 Class.method 형태로 사용.
  1. Static Method @staticmethod 꾸밈자 사용. 특별한 argument를 받지 않음. 일반적으로 class 내 유틸 함수로 사용. Class를 일종의 Namespace로 사용.

  2. Class Method @classmethod 꾸밈자 사용. 호출된 class인 cls를 받음. (self와 비슷) factory 패턴에서 사용

class Number:  
    Constant = 10  
  
  @staticmethod  
  def static_factory():  
        obj = Number()  
        obj.value = Number.Constant  
        return obj  
  
    @classmethod  
  def class_factory(cls):  
        obj = cls()  
        obj.value = cls.Constant  
        return obj  
  
number_static = Number.static_factory()  
number_class = Number.class_factory()  
print(number_static.value, number_class.value)
>>> 10 10

상속하면 차이가 발생!

  • @staticmethod는 유틸리티 성격에 맞게 사용 가능.
  • @classmethod는 클래스의 어떤 옵션을 바꾸는 용도로 사용 가능.

## Three Elements of OOP

  • 상속 (Inheritance) 기존에 구현되어 있는 틀을 상속받아 새로운 틀을 만드는 것. 기존의 틀: 부모 Class, 새로운 틀: 자식 Class 자식 Class에서는 부모의 기능을 이용할 수 있음.

  • 다형성 (Polymorphism) 같은 이름의 메소드를 다르게 작성하는 것. 각 자식 클래스가 다른 클래스와 차별화 됨.

Python에서의 상속과 다형성

  1. 다중 상속 지원 => 죽음의 다이아몬드 [메소드 탐색 순서를 따름 (mro)]
  2. super 내장 함수를 이용하여 상위 클래스 접근 가능.
class Student:  
    def __init__(self, name: str, sid: int):  
        self.name = name  
        self.sid = sid  
        self.classes = []  
    def __str__(self):  
        return self.name + "_" + str(self.sid)  
    def take_class(self, class_name: str) -> None:  
        self.classes.append(class_name)  
  
class Master(Student): # Student 상속  
	def __init__(self, name: str, sid: int, professor: str):  
	    super().__init__(name, sid) # 부모 클래스 생성자 접근, 정해진 부르는 타이밍은 없다.  
		self.professor = professor  
    def __str__(self): # __str__ 재정의 → 다형성  
		return super().__str__() + "_" + str(self.professor)  
  
master = Master('ChangseokLee', 2015, 'goorm')  
print(master)
>>> ChangseokLee_2015_goorm
print(super(Master, master).__str__()) # super로 언제나 원하는 상위 클래스로 변환
>>> ChangseokLee_2015
  • 가시성, 캡슐화 (Visibility)

가시성이란 사용자에게 모델의 내부를 감추는 것을 의미.

  1. 캡슐화, 정보 은닉, 클래스 간 간섭 최소화.
  2. 최소한의 정보만을 특정 API로 공개.

Python에서의 가시성.

  1. 명시적인 private & protected 범위가 없음 -> 모두 public
  2. private 변수/함수 이름 앞에 __를 붙임. ex) self.__name
  3. protected 변수/함수 이름 앞에 _를 붙임. ex) self._name
class TestClass(object):
	def __init__(self):
		self.attr = 1
		self._attr = 2
		self.__attr = 3

instance = TestClass()
  • __의 경우 변수명 앞에 Class 이름을 넣어 Mangling - 자식과 이름이 안겹침.
  • private와 protected는 코드 완성 등에서 안보임.
  • 그러나 둘 다 public과 기능적 차이는 없음. (밖에서 접근 가능함)

댓글남기기