플젝 카카오톡 챗봇 만들기 도전! (오픈빌더 신청 중...)
서버리스 카카오톡 챗봇 만들기 다음에 참조하기
OOP 생활코딩 OOP 강의
import
로 끌어오는 것을 말하는 듯 하다. JS의 Object랑 비슷한 것 같기도 한데, JS의 Object를 모듈로 봐도 되려나?# first.py
def myfunc():
return 'hello'
# second.py
def myfunc():
return 'Soo'
# main.py
import first, second
print(first.myfunc())
print(second.myfunc())
결과물
hello
Soo
OOP class, instance 개념
object = class + instance
name = String.new('Soo')
# String; class
# name; 'Soo'을 가지는 String 클래스의 복제품, instance
name.reverse()
# name이 String 클래스의 메소드를 그대로 가지고 있어 사용 가능
OOP
class Cal(object):
def __init__(self, v1, v2):
c1 = Cal(10, 20) # v1 = 10, v2 = 20
def __init__
으로 시작한다. 생성자의 맨 처음에 self
가 들어가야 하는 이유는... 다음 강의에서 설명해주신다고 한다 😮 (self
를 출력했을 때 메모리 주소같은 게 나온 걸로 봐서는 c1이라는 인스턴스의 주소(인스턴스 그 자체)를 말하는 게 아닐까...?)OOP
self
가 필요한 이유self.instance_var = input_var
로 정의한다. 메소드의 첫 번째 인자(self
)는 항상 인스턴스 그 자체를 가리키기 때문에 인스턴스 내에서 사용하는 전역변수를 위처럼 표현하는 것이다. 참고로 반드시 self
라는 명칭을 사용할 필요는 없지만 관용적으로 이렇게 쓴다. 간혹 s
로 표기하는 경우도 있다.class Cal(object):
def __init__(self, v1, v2): # 첫 번째 인자는 인스턴스 그 자체(self)
self.var1 = v1
self.var2 = v2 # 인스턴스 변수 선언
def add(self):
return self.var1+self.var2
c1 = Cal(10, 20)
print(c1.add())
❗ 메소드의 첫 번째 인자는 항상 인스턴스이다!
위의 예시에서def add():
와 같이 인자를 주지 않고 메소드를 만들어도 될 것 같지만, 막상c1.add()
를 실행하면 "takes 0 positional arguments but 1 was given" 에러가 나온다. 말인즉슨add()
메소드는 인자를 아무것도 받지 않도록 만들어졌는데 하나가 들어갔다는 뜻이다.
엥? 나는 인자 안 줬는데? 라고 생각하겠지만 우리가 주지 않아도 알아서 인스턴스를 첫 번째 인자로 알아서 가져오는 바람에 하나가 들어갔다고 인식하는 것이다.
이렇게 하는 이유를 찾아보았지만 "ruby처럼@v1 = v1
의 방식으로 인스턴스 변수를 참조하지 않기 때문에 이런 방식으로 만들었다" 와 같은 닭이 먼저인지 달걀이 먼저인지 하는 모호한 설명만 찾았다.
(인강 다 듣고 나면 이걸 썼을 때 왜 전역 변수를 클래스로 없앨 수 있는지 고민 + 찾기)
OOP 객체를 사용하는 이유
항상 OOP를 할 필요는 없다. 다만 객체를 사용하면 함수(메소드) 간의 연관을 더 쉽게 파악할 수 있고 오류 가능성이 줄어든다.
위의 코드를 OOP가 아닌 방식으로 바꾸면 아래와 같다.
def add(v1, v2):
return v1+v2
def subtract(v1, v2):
return v1-v2
n1 = 10
n2 = 20
print(add(n1, n2))
print(subtract(n1, n2))
이 코드를 보면 add
와 subtract
가 연관이 있는 함수라고 알아차리기 어렵고, 함수에 들어가는 인수를 잘못 입력하거나 add
라는 다른 함수를 실수로 또 만들어 오류가 날 수도 있다.
객체를 부품의 개념으로 본다면(=캡슐화), 부품을 다른 곳에 조립하더라도 외부의 요인(예상치 못한 변수 등)으로부터 더 안전하기 때문에 대규모 프로그래밍에서는 OOP를 사용한다. 진행 순서대로 쓰는 대신 함수를 만드는 것과 비슷한 이유이다.
OOP
myclass.value
와 같이 인스턴스 변수 value
에 직접 접근할 수 있다. 다만 Ruby에서는 myclass.value
에서 value
를 메소드로 인식하기 때문에 외부에서 직접 인스턴스 변수에 접근할 수 없고 메소드를 통해서만 할 수 있다.class C
def initialize(v)
@value = v
end
def show()
p @value
end
end
# p c1.value # 이렇게 하면 c1.value() 메소드로 인식해서 에러 발생
c1.show() # 메소드를 통해서는 @value에 접근 가능
set
, get
;set
, get
메소드라고 부른다.class C
def initialize(v)
@value = v
end
def getValue()
return @value
end
def setValue(v)
@value = v
end
end
c1 = C.new(10)
p c1.getValue()
c1.setValue(20)
파이썬에서도 c1.value
처럼 직접 접근하는 대신 메소드를 통해 인스턴스 변수에 접근할 수 있다. 직접 접근 대신 get
, set
메소드로 접근하는 것이 더 바람직한데 그 이유는 변수 접근에서 변수 type 지정 등의 정확도를 높이는 절차를 추가할 수 있기 때문이다.
class Cal(object):
# 이전과 동일
def setV1(self, v):
if isinstance(v, int): # 첫 번째 인자(v)가 두 번째 인자(int)의 인스턴스라면 True를 반환한다. 즉 v가 integer 라면 True를 반환한다.
self.v1 = v
c1 = Cal(10, 20)
# c1.v1 = 'one'
# c1.add() # 직접 접근할 경우 잘못된 v1을 주어 이후 메소드에서 에러가 발생
c1.setV1 = 'one1
c1.add() # set 메소드를 사용할 경우 잘못된 인스턴스 변수로 인한 에러를 방지할 수 있다.
type
이랑isinstance
랑 무슨 차이지?
그 내용은 이 글에 나와있는데 음... 어려워서 다 이해하지는 못했지만type
은 상속된 클래스를 부모 클래스랑 동일한 것으로 보지 않는 모양이다...? 나중에 상속까지 배워야 이 글을 이해할 수 있겠다 ㅠㅠ
(여기에서도type
은 subclass를 제외하고 체크한다는 걸 보니 상속 문제가 맞는 듯)
플젝
아... 챗봇 쉽지 않네 🙃 백엔드는 고사하고 블록 연결이라도 잘 해야 하는데...
'조회' 시나리오에서 블록끼리 자동 연결하는 방법을 찾자.
(근데 몇 개 찾아보니까 블록은 정말 발화 단위로 시작하는 거라서 내가 원하는대로 슈루룩 진행하려면 API로 진행해야 하는 걸지도...? 이거 보니까 제네릭 메뉴로 바로 블록 연결할 수 있으니까 시작은 이걸로 하는 게 좋겠다.)
플젝
조회
, 등록
, 삭제
버튼을 클릭한다.@날짜
의 담당자를 조회/등록/삭제합니다."라는 메시지를 출력한다.3번은 날짜 엔티티를 써서 간단히 해결했지만 1~2번을 해결할 방법을 몰라 고민했다. 원래 사용자가 먼저 말을 해야 블록이 실행되는데, 버튼을 눌렀을 때 그냥 날짜를 입력받는 블록으로 연결해버리면 '날짜를 입력하세요'라는 안내를 보낼 수가 없지 않은가?
하지만 막상 연결을 하고 나니 정상적으로 안내가 나왔다. 그 이유는 블록으로 연결되는 그 순간 사용자의 입력이 없는 것으로 받아들여서 자동으로 '되묻기 질문'이 나오기 때문이다. 그래서 버튼을 누르면 자동으로 "날짜를 입력해주세요" 되묻기 질문이 나와 사용자에게 안내가 간다.
지금은 이렇게 작동한다! 근데 한 가지 이상한 점은 맨 처음 대화를 시작할 때 버튼을 누르지 않고 임의로 '월요일'이라고 입력하면 자동으로 조회
블록이 실행된다는 거다... 조회
블록의 파라미터에 날짜를 등록해놔서 먼저 실행되는 것 같다. 이걸 막을 방법이 없을까?
Correlation vs. Linear Regression
의미
둘 다 두 개의 변수 사이의 관계를 보는 건 맞다. 다만 Correlation은 '같은 방향으로 움직이는지'만 보는 거고, Linear Regression은 '같은 방향인데 얼마만큼 움직여야하는지'까지 보는거다.
그래서 Correlation은 같은 방향인지 아닌지만 판단하면 되니까 -1~+1 사이의 단위가 없는 값이고, Regression은 '얼마만큼'인지도 봐야하니까 두 변수의 단위가 붙어있다.
예를 들어 키와 몸무게의 경우 Correlation = 0.6, Regression = (몸무게 한 단위당 키 증가량) 1cm/kg 으로 표현 가능.
두 계수 간의 상관(correlation 말고 relationship)
두 변수가 정규화 되어있으면 Correlation = Regression이다. 왜냐면 정규화하는 순간 단위가 사라지니까.
정규화가 안 되어있다면 Regression(beta) = Correlation(r) * SD(x)/SD(y)와 같다. 즉 correlation을 구하기 위해 정규화한 변수를 다시 비정규화시킴으로써 단위를 돌려내는 것이다.
Inverse Probability Weighting (인과추론)
설명이 잘 된 글이 있어 참고를 위해 따로 저장.
OOP
c.value
와 같이 외부에서 직접 인스턴스 변수에 접근할 수 있다.class C
attr_reader :value # @value를 읽을 수 있게 한다.
attr_writer :value # @value를 쓸 수 있게 한다.
# attr_accessor :value # @value를 읽고 쓸 수 있게 한다.
def initialize(v)
@value = v
...
여기서 attr
은 attribute의 약자로, Ruby에서 외부에서 접근 가능한 변수를 의미한다. 즉 위 코드는 @value
를 attribute로 만드는 것이다.
c.value
와 같이 접근이 불가능하게 만들 수 있다.class C(object):
def __init__(self, v):
self.__value = v # __를 변수 앞에 붙인다.
OOP 상속
기존 객체의 기능을 유지(상속)하면서 새로운 기능을 추가하는 행위. 내가 만든 것이 아니라 클래스를 수정할 수 없는 상황 등에서 사용할 수 있다.
class Class1(object):
def method1(self):
return 'm1'
class Class2(Class1): # 새로운 Class를 선언할 때 기존 Class를 부모로 입력하면 기존 Class의 메소드를 상속한다.
def method2(self):
return 'm2'
c = Class2()
print(c.method1()) # Class2 내에서 method1을 선언하지 않았음에도 사용할 수 있다.
print(c.method2())
c.method1()
을 실행하면 파이썬은 c
내부에 method1()
이 존재하는지 확인하고, 없다면 그 부모 클래스로 이동해 확인 후 실행한다.
OOP 상속
자식 클래스에 생성자def __init__
이 없으면 부모 클래스의 생성자를 사용한다.
자식 클래스에 생성자가 있으면 어떻게 될지 궁금해서 실험을 해봤다.
class Class1(object):
def __init__(self, v1, v2):
self.var1 = v1
self.var2 = v2
def add(self):
return self.var1+self.var2
class Class2(Class1):
def __init__(self, v1):
self.var1 = v1
def two_plus(self):
return self.var1 + 2
c1 = Class1(1,2)
print(c1.add())
c2 = Class2(2)
print(c2.two_plus())
# c3 = Class2(3)
# print(c3.add()) # v2가 없어서 에러 발생
# c4 = Class2(3,1) # 생성자에서 에러 발생(지정된 arg는 2개인데 3개가 들어옴)
자식 클래스에 생성자를 따로 만드는 것도 가능하며, 인스턴스를 만들 때에는 비어있지 않은 한 그 클래스의 생성자를 따른다는 것을 알 수 있다. 물론 이렇게 쓰는 일은 거의 없겠지만 궁금해서 해봤다 :9
카카오 챗봇도 그렇고 업무도 그렇고 결국 웹 개발을 알아야 하는 상황이라 다시 JS로 복귀 ^-^... 이거 떼고 Node.js 하는 게 목표!
JS Array-Like Object (유사 배열)
1) 숫자로 인덱싱이 가능하고 2) length 프로퍼티가 있지만 3) 배열 메소드(push
, splice
등) 사용이 불가능한 객체를 유사 배열이라고 부른다.
대표적인 예는 getElementsByClassName
으로 획득한 객체이다.
JS
이벤트 핸들링/핸들러
addEventListner
에 들어가는 함수를 '이벤트 핸들러'라고 부른다. 용어를 알면 검색이 편해지니 잘 알아두자.
onclick
vs. addEventListener
지난 번에는 myTag.addEventListener('click', clickFunc)
로 click 이벤트를 핸들링했지만 myTag.onclick = clickFunc
과 같이 할 수도 있다. 기능은 동일하다.
둘의 차이를 찾아보니(블로그) onclick
는 두 가지 이상의 이벤트를 동시에 핸들링하지 못하고 버블링/캡처링 옵션이 없다고 한다. 그래서 요즘은 대부분 addEventListener
을 쓴다고.
하지만 저게 무슨 말인지 이해가 안 되어서 찾아봤다.
먼저, 여러 이벤트를 핸들링할 수 없다는 것은 아래 코드를 실행했을 때 func2
만 실행된다는 의미이다.
myTag.onclick = clickFunc1;
myTag.onclick = clickFunc2;
반면 addEventListener
은 click 이벤트에 여러 함수를 사용해도 모두 실행할 수 있다.
둘째로 버블링/캡처링은 중첩된 요소에서 event 핸들링이 전파되는 방향을 의미한다. 이 블로그를 보면 예시가 잘 나와 있는데, <ul><li>Soo</li></ul>
에서 <li>
요소에 이벤트 핸들러를 넣었을 때 그것이 <ul>
, <body>
...window까지 모두 실행되게 만든다(버블링). 역으로 window로부터 요소까지 진행할 수도 있다(캡처링).
블로그 설명에 따르면 버블링은 특정 요소의 정보를 상위에 위임하기 위해서 주로 사용된다고 한다(마치 정보를 상부 조직으로 넘기는 것처럼). 캡처링은 그다지 자주 쓰이지 않는다고 한다.
JS
window
객체
브라우저 창 그 자체이면서 JS의 최상단 객체인 전역 객체(Global Object)이다. 즉 console
, document
와 같은 객체를 모두 포함하는 객체이다. 참고로 window.console
과 같이 쓰지 않는 이유는 어차피 모든 객체가 window
안에 있으니 굳이 적지 않는 것이다.
DOM (Document Object Model)
웹페이지(웹문서)의 html 전체를 객체로 표현한 것. 다만 html = DOM은 아니라고 한다. 좀 헷갈리는데... html을 그대로 쓰는 게 아니라 깊이에 따라 트리 형태로 정리한 걸 말하는 것 같다. 모질라 보니까 API니 뭐니 얘기하는데 여기서부터는 이해가 잘 안 간다... 지금으로서는 html을 트리 구조로 정리하고 + JS로 수정이 가능하게 만든 객체를 DOM이라고 이해해야겠다. (참고: DOM이란 정확히 무엇일까?)
지금 보는 JS 강의는 문법 위주로 가르쳐서 홈페이지 바로 만드는 코스를 병행하는 게 좋겠다.
그러고보니 OOP도 마저 들어야겠네!? 상속까지만 해놓고 잊고 막상 내 코드는 안 바꿔놨다
JS
DOM 트리
html 태그를 계층 구조로 나타낸 것을 의미한다. 그 안의 각 객체는 노드(node)라고 부른다. 노드는 계층에 따라 부모, 자식, 형제로 표현하며 parentElement
, childeren
, firstElementChild
, previousElementSibling
등으로 불러올 수 있다.
유용한 프로퍼티
innerHTML
; 말 그대로 안의 html 태그를 불러온다. 예) <h1>Hi</h1>
textContent
; html이 아닌 텍스트 그 자체를 불러온다. 예) 'Hi'
그래서 innerHTML
에 <h1>Soo</h1>
을 넣으면 기대한대로 h1 태그가 수정되지만 textContent
에 위 문자열을 넣으면 '<h1>Soo</hi>'
라는 텍스트가 통째로 나온다. 불필요한 태그 변환을 막고싶다면 후자가 더 좋다.
Node.js(병행)
Node.js는 JS를 실행시킬 수 있는 환경으로 브라우저와 유사한 것이다. 보통 서버용으로 많이 사용하고 JS로 백엔드까지 구현할 수 있게 해주어 인기가 많다. 다만 Node.js는 Django처럼 모든 틀이 다 짜여진 '프레임워크'는 아니다.
OOP 클래스 멤버
우리가 이전까지 다룬 멤버(변수, 메소드)는 '인스턴스'의 것이었다. 그러나 인스턴스가 아닌 클래스에 속해있는 멤버를 '클래스 멤버'라고 부른다. 즉 클래스의 메소드를 인스턴스가 전부 다 쓸 수 있는 것은 아니라는 뜻이다. 루비의 Date
클래스로 예를 들면 다음과 같다.
require 'date'
d1 = Date.new(2000, 1, 1)
p d1.year() # year 메소드는 d1 인스턴스에 종속되어 있다.
p Date.today() # today 메소드는 Date 클래스에 종속되어 있다.
# p d1.today() # d1 인스턴스에는 해당 메소드가 없다.
# p Date.year() # Date 클래스에는 해당 메소드가 없다.
인스턴스는 보통 클래스에 다른 정보를 추가할 때 만든다. 그래서 별도 정보가 필요없는 today
메소드는 Date
클래스에서만 작동하고(클래스 멤버), 정보가 필요한 year
메소드는 Date
의 인스턴스에서만 작동하는 것이다(인스턴스 멤버).
OOP 클래스 메소드
파이썬의 클래스 멤버(메소드)에는 'static', 'class' 두 종류가 있다.
각각 @staticmethod
, @classmethod
를 붙여 정의할 수 있으며 'class'의 경우 첫 번째 인자로 cls
를 받는다. 이것은 self
가 인스턴스를 가리키는 것처럼 클래스 그 자체를 가리키는 인자이다.
'static'과 'class' 메소드의 차이는 클래스 변수까지 배우고 난 다음에 설명한다.
class Cs:
@staticmethod # 이 장식자를 붙인 함수는 static method가 된다.
def static_method(): # 인자를 받지 않아도 된다.
print("Static")
@classmethod # 이 장식자를 붙인 함수는 class method가 된다.
def class_method(cls): # 클래스를 가리키는 'cls'가 첫 인자로 들어간다.
print("Class")
def instance_method(self):
print("Instance")
i = Cs()
Cs.static_method()
Cs.class_method()
i.instance_method()
OOP
count
라는 변수를 만들어보자.class Cs:
count = 0 # 메소드 밖에서 선언된 변수는 자동으로 클래스 변수가 된다.
def __init__(self):
# 인스턴스가 생성될 때마다 count를 1씩 더한다.
# 메소드 내에서 클래스 변수에 접근하려면 '클래스.변수'로 불러와야 한다.
Cs.count = Cs.count + 1
@classmethod
def getCount(cls):
return Cs.count
# cls가 클래스이므로 cls.count는 동일한 결과를 내놓는다.
cls
를 인자로 받는가(class), 아닌가(static)이다. cls
는 클래스 그 자체를 가리키는 것이라 Class method 내에서 cls.변수명
을 통해 클래스 변수에 접근할 수 있다. Static method는 cls
인자를 받지 않기 때문에 접근하려면 클래스명.변수명
과 같이 접근해야 한다.class Cs(object):
num = 1
@staticmethod
def stmethod(x):
# Cs.num = Cs.num +1 # 이런 식으로 클래스 변수 편집이 가능하긴 하다. 막연히 '클래스 상태에 접근할 수 없다'는 설명을 듣고 변수 편집이 불가능한가 싶었는데 이건 가능한 듯.
return x + Cs.num # 클래스명.num 으로 쓸 수 있다.
@classmethod
def csmethod(cls,x):
# cls.num = cls.num+1 # 클래스 변수 편집 가능
return x + cls.num # cls.num 으로 쓸 수 있다.
일단 지금까지 이것저것 실험해 본 결과 Static method를 쓴다고 해서 클래스 변수를 읽고 쓰는 게 안 되는 것은 아니었다. 클래스 변수를 써야한다면 굳이 Static으로 할 필요가 없을 뿐(Class method를 쓰면 편하게 cls.변수명
을 쓰면 되니까).
그래서 보통은 클래스 변수를 쓸 필요가 없을 때(utility functions) Static, 클래스 변수를 사용해야 할 때(factory method) Class를 쓰는 듯 하다.
(출처: Geeks for Geeks, 블로그)
JS 요소 노드 추가/삭제
const newElm = document.createElement('li');
newElm.textContent = '추가하려는 요소 내용';
// myList 자식 노드 추가
const myList = document.querySelector("#myList"); // <ul> 태그로 가정
myList.prepend(newElm); // 맨 앞에 추가
myList.append(newElm); // 맨 뒤에 추가
// myList 형제 노드 추가
myList.before('자식 노드 아닌 형제 노드');
myList.after('문자열 넣어도 작동함');
// myList 자식 노드 삭제
myList.children[0].remove();
// otherList -> myList로 자식 노드 이동
// append, prepend, after, before을 기존 요소에 사용하면 '이동'시킨다.
myList.children[0].after(otherList.children[1]);
OOP
Cal
클래스에 이전 기록을 출력해주는 클래스 메소드를 만들어보자.class Cal(object):
_history = [] # 앞에 _ (underscore)가 붙으면 비공개 클래스 변수가 되어 외부에서 호출할 수 없게 된다. 이전에 본 self.__var 와 같은 원리.
def __init__(self,v1,v2):
self.var1 = v1
self.var2 = v2
@classmethod
def history(cls):
for item in cls._history:
print(item)
def add(self):
result = self.var1+self.var2
Cal._history.append(f"{self.var1}+{self.var2} = {result}") # 기록을 history에 저장
return result
def subract(self):
result = self.var1-self.var2
Cal._history.append(f"{self.var1}-{self.var2} = {result}")
return result
c1 = Cal(10,20)
print(c1.add())
print(c1.subract())
Cal.history()
_
(underscore)을 붙이면 외부에서 접근할 수 없는 변수가 된다. 클래스 앞에는 하나, 인스턴스 앞에는 두 개.class Class_open(object):
open = 'Open Class Variable'
def __init__(self, v):
self.var = v
class Class_hide(object):
_hide = 'Hide Class Variable'
def __init__(self, v):
self.__var = v
open = Class_open('Open up!')
hide = Class_hide('Slam the door!')
print(Class_open.open)
print(open.var)
# print(Class_hide._hide) # 접근 불가
# print(hide.var) # 접근 불가