반응형
Python for AI¶
- Basics 보다 좀 더 Advanced 한 개념을 주로 정리했음
- 클래스, 클로져, 제너레이터 등등..
클래스 설계¶
In [2]:
# 학생 정보를 담는 클래스를 설계
class Student():
# 생성자
def __init__(self, name, number, grade, details):
# 객체 초기화
self.name = name
self.number = number
self.grade = grade
self.details = details
def __str__(self):
return 'str 메소드 호출 : {}'.format(self.name)
In [3]:
# 학생을 만들자
# 클래스를 기반으로 생성한 인스턴스
student1 = Student('Kim', 1, 1, {'gender':'Male', 'score':95})
student2 = Student('Park', 2, 3, {'gender':'Female', 'score':90})
student3 = Student('Baek', 3, 5, {'gender':'Male', 'score':100})
In [4]:
# 학생 1번이 어떤 속성을 갖는지 확인하자
student1.__dict__
Out[4]:
In [6]:
# __str__ 메소드 사용하기
# 객체에 어떤게 들었는지 참고할 때 사용하면 좋다
student_list = []
student_list.append(student1)
student_list.append(student2)
student_list.append(student3)
for i in student_list:
print(i)
In [10]:
# 파이썬 변수의 범위
b = 10
def func_v1(a):
print(a)
print(b)
func_v1(5)
In [12]:
b = 10
def func_v2(a):
print(a)
print(b)
b = 5
func_v2(5)
### local variable 'b' referenced before assignment 에러가 뜬다
- 위의 func_v1 함수는 b가 글로벌 변수이기 때문에 함수에서 print()가 된다
- 하지만 func_v2 함수에서는 print(b)를 하는데 b가 자신의 지역범위 안에 있는 것만 확인되고 b에 값이 할당되는것은 print 다음이기 때문에 error가 발생한다.
- 이는 지역변수가 글로벌 변수보다 우선이기 때문이다.
위의 개념을 생각해보면 closure를 이해하기 쉬워짐¶
- 반환되는 내부 함수에 대해서 선언된 연결을 가지고 참조하는 방식임
- 반환 당시, 함수의 유효범위를 벗어난 변수 혹은 메소드에 직접 접근을 하는 것
In [27]:
a = 10
print(a + 10)
print(a + 20)
print(sum(range(1,10)))
- print문 안이 범위이기 때문에 a를 아무리 더해도 누적이 되지 않는다.
- 하지만 sum을 사용하면 누적된 합이 나온다.
- 이것처럼 내가 a를 계속 더하는 것을 (결과를) 누적시킬 수 있을까?
- 그리고 이것을 클래스 형태로 만들 수 있을까?
In [15]:
# 평균을 만들어주는 클래스를 만들어보자
class Averager():
def __init__(self):
self._series = []
def __call__(self, v):
self._series.append(v)
print('class >>> {} / {}'.format(self._series, len(self._series)))
return sum(self._series) / len(self._series)
In [24]:
# 인스턴스 생성
avg_cls = Averager()
In [25]:
avg_cls(10)
avg_cls(20)
avg_cls(30)
Out[25]:
- 점점 누적이 되고 있는 것을 볼 수 있다.
- 클래스 인스턴스 변수 안에 계속 append()하고
- 이것을 속성값으로 계속 갖고 있기 때문에 호출할 때마다 append하고 나눠준다.
위에서 만든 Averager 클래스를 closure로 만들자¶
In [28]:
def closure_avg1():
# 여기가 클로져 영역! (free variable 영역)
# averager 함수의 유효 범위를 벗어나 있지만 접근이 가능하다
series = []
def averager(v):
series.append(v)
print('class >>> {} / {}'.format(series, len(series)))
return sum(series) / len(series)
return averager # 함수를 return
In [29]:
avg_closure1 = closure_avg1()
In [30]:
# 함수를 리턴 받았음
avg_closure1
Out[30]:
In [31]:
avg_closure1(10)
avg_closure1(20)
avg_closure1(30)
Out[31]:
- 전역변수의 사용을 감소할 수 있다
- 디자인 패턴 적용이 가능함
- 하지만 메모리 사용 측면에서 좋진 않음
데코레이터¶
- 이것도 클로져처럼 외부에 있는 것을 내부에서 사용하게 해준다는 개념이긴 함
- 하지만 클로져보다 간결하고 조합해서 사용하기에 용이하다
In [49]:
# 함수의 실행시간을 측정해보는 함수를 만들어보자
import time
def time_check(func):
def performance_clocked(*args):
# 시작 시간
st = time.perf_counter() # perf_counter는 time 모듈 내장함수 (코드 시간 잼)
result = func(*args)
# 종료 시간
et = time.perf_counter() - st
# 함수명
name = func.__name__
# 매개변수
arg_str = ','.join(repr(arg) for arg in args)
# 출력
print('Result: [%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result))
return result
return performance_clocked
In [45]:
# time_check 함수에 넣은 function들 만들기
# 데코레이트용 함수 만들기
def time_func(seconds):
time.sleep(seconds)
def sum_func(*numbers):
return sum(numbers)
데코레이터 안 쓸 경우¶
In [46]:
non_decor1 = time_check(time_func)
non_decor2 = time_check(sum_func)
In [47]:
# 안에 뭐가 들었는지 확인해보자
# func 을 갖고 있는것을 알 수 있음
print(non_decor1, non_decor1.__code__.co_freevars)
In [55]:
# 시간 재보기
print('*' * 30)
non_decor1(2)
# 더하기 함수
print('*' * 30)
non_decor2(10, 20, 30)
Out[55]:
- 나는 time_func을 실행했는데, 선행으로 performance_clocked 함수 안의 부분들이 다 실행되어 나왔음
- 이런식으로 함수를 실행할 때 원하는 것들을 같이 실행 및 꾸밀 수 있다.
데코레이터를 사용할 경우¶
- @ + 사용할 함수 이름 붙이면 끝
In [56]:
@time_check
def time_func(seconds):
time.sleep(seconds)
@time_check
def sum_func(*numbers):
return sum(numbers)
In [57]:
print('*' * 30)
time_func(2)
print('*' * 30)
sum_func(10, 20, 30)
Out[57]:
Generator¶
- 호출할 때마다 반환해줌
- 지능형 리스트, 딕셔너리, 집합의 경우, 데이터 셋이 증가될 경우 메모리 사용량이 증가한다.
- 이때, 제너레이터가 이것을 완화할 수 있다.
- 그리고 단위 실행 가능한 coroutine 구현에 사용된다.
클래스를 만들고, 이를 제너레이터로 바꿔보자¶
- 문자열을 받으면, split해서 iteration 하는 클래스를 만들어보고
- 이를 제너레이터로 활용해서 수정하기
In [21]:
class WordSplitIter():
def __init__(self, text):
self._idx = 0
self._text = text.split(' ')
# 다음 순서를 기억하기 위해 idx가 필요함
def __next__(self): # 호출때 마다 인덱스가 바뀌면서 단어가 나옴
try:
word = self._text[self._idx]
except IndexError:
raise StopIteration()
self._idx += 1
return word
def __iter__(self): # 클래스를 반복할 것임
print('Called __iter__')
return self
def __repr__(self): # 객체 호출 때 어떤 정보가 있는지 알아보기
return 'WordSplitIter(%s)' % (self._text)
In [22]:
wi = WordSplitIter('who says the nights are for sleeping')
# repr 메소드를 부르면서 객체에 어떤 정보가 저장됬는지 확인
wi
Out[22]:
In [23]:
# 이제 next를 하면서 단어를 하나씩 부름
print(next(wi))
print(next(wi))
print(next(wi))
- 다음에 나올 단어를 커서로 가르키고, next가 실행되면 반환된다.
- 끝까지 next를 실행하면 인덱스가 리스트 범위를 넘어가면서 IndexError가 생김 ### 위의 클래스를 Generator로 바꿔보자
In [28]:
class WordSplitIter():
def __init__(self, text):
self._text = text.split(' ')
# generator가 있기 때문에 따로 idx를 만들 필요가 없음. (내부적으로 기억함)
# 아까 만든 __next__()도 필요가 없음. 그리고 IndexError도 알아서 나옴
def __iter__(self):
# 여기가 바로 제너레이터!!
for word in self._text:
yield word # 여기서 알아서 다 처리함
return
def __repr__(self): # 객체 호출 때 어떤 정보가 있는지 알아보기
return 'WordSplitIter(%s)' % (self._text)
In [29]:
wi = WordSplitIter('who says the nights are for sleeping')
# repr 메소드를 부르면서 객체에 어떤 정보가 저장됬는지 확인
wi
Out[29]:
In [32]:
# iter 함수를 호출해서 iteratable하게 바꿔줘야 함
wg = iter(wi)
# 이제 next를 하면서 단어를 하나씩 부름
print(next(wg))
print(next(wg))
print(next(wg))
- yield 예약어 때문에 쉽게 구현이 가능해졌다.
In [79]:
def generator_ex1():
print('start')
yield 'AAA'
print('continue')
yield 'BBB'
print('end')
temp = iter(generator_ex1())
In [71]:
# 순서대로 나옴
print(next(temp))
print(next(temp))
# print(next(temp))
In [74]:
# 반복문에서 주로 사용함
for i in generator_ex1():
print(i)
괄호를 사용하면 지능형 제너레이터를 만든다¶
In [80]:
temp2 = [x * 3 for x in generator_ex1()]
temp3 = (x * 3 for x in generator_ex1())
In [82]:
print(temp2)
print(temp3)
반응형
'Info' 카테고리의 다른 글
도커 개념 정리하기 (도커 이미지, 도커 컨테이너, 도커 허브) (0) | 2022.01.23 |
---|---|
인공지능을 활용한 디지털 마케팅 사례 (0) | 2022.01.16 |
미국판 배달의 민족, 도어대시의 압도적인 점유율 (0) | 2022.01.14 |
뉴로(Nuro) 신형 자율주행 배달차 (0) | 2022.01.13 |
애자일 프로젝트 관리, Jira(지라)와 Asana(아사나) 비교하기 (1) | 2022.01.07 |
댓글