일단계 파이썬 v1.0 (2020-DEC-10)

'프로그래밍언어.Lib > 파이썬' 카테고리의 다른 글

pyfirmata 기초  (0) 2019.12.02
ipython 의 매직명령어들  (0) 2016.10.18
ipython 기본 사용법  (0) 2016.10.18
MicroPython 개발자의 IoT 발표 (pyCon2016)  (0) 2016.09.27
윈도7에서 ipython 설치하기  (0) 2016.01.07
Posted by 살레시오

댓글을 달아 주세요

1. 아두이노에 firmata 설치하기


예제를 읽어들인 후 아두이노에 업로드한다.


2. pyfirmata 설치하기


pip3 insall pyfirmata


3. 파이썬으로 코딩하기

3.1 디지털핀


디지털 핀의 경우 pyfirmata의 Arduino 객체의 get_pin() 메서드를 이용한다.


import pyfirmata as pf

import time


ard = pf.Arduino('COM7')

ard.get_pin('d:13:o') #디지털핀 13번을 출력으로 설정

while True:

    ard.digital[13].write(1)

    time.sleep(0.5)

    ard.digital[13].write(0)

    time.sleep(0.5)


또는


import pyfirmata as pf

import time


ard = pf.Arduino('COM7')

p13 = ard.get_pin('d:13:o') #디지털핀 13번을 출력으로 설정

while True:

    p13.write(1)

    time.sleep(0.5)

    p13.write(0)

    time.sleep(0.5)



3.2 아날로그핀


import pyfirmata as pf

import time


ard = pf.Arduino('COM7')

print('connected.')


# 아날로그핀을 사용하려면 반드시 아래와 같은 두 줄이 필요함.

pf.util.Iterator(ard).start()

ard.analog[0].enable_reporting()


while True:

a0 = ard.analog[0].read() # [0,1]범위의 실수 반환

print(a0)


또는


import pyfirmata as pf

import time


ard = pf.Arduino('COM7')


pf.util.Iterator(ard).start()

ard.analog[0].enable_reporting()

a0p = ard.get_pin('a:0:i')


while True:

a0 = a0p.read()

print(a0)


3.3 pwm

아두이노 우노는 pwm기능이 3,5,6,9,10,11번 핀에만 있다.


import pyfirmata as pf

import time

from math import pi, sin


ard = pf.Arduino('COM7')

pwm = ard.get_pin('d:10:p')


gap = 2*pi/100

t = 0

while True:

y = (sin(t)+1)/2

pwm.write(y)

t += gap

time.sleep(0.01)


A0핀에 가변저항이 연결되어 있다고 가정했을 때 11번 핀에 연결된 LED의 밝기를 가변저항으로 제어하는 예제는 다음과 같다.


import pyfirmata as pf

import time


ard = pf.Arduino('COM7')

pwm = ard.get_pin('d:10:p')


pf.util.Iterator(ard).start()

ard.analog[0].enable_reporting()


while True:

a0 = ard.analog[0].read()

pwm.write(a0)

print(a0)


3.4 서보모터

 

import pyfirmata


DELAY = 1

MIN = 5

MAX = 175

MID = 90


board = pyfirmata.Arduino('COM7')


servo = board.get_pin('d:11:s') #11번핀을 서보모터 신호선으로 설정

 

def move_servo(v):

    servo.write(v)

    board.pass_time(DELAY)


move_servo(MIN)

move_servo(MAX)

move_servo(MID)


board.exit()


Posted by 살레시오

댓글을 달아 주세요

2. 매직 명령어들  c{ipy02}

 IPython에는 매직명령어라는 것들이 있는데 %로 시작하는 명령어들이다. 보통 %문자로 시작하지만 만약 사용자 변수가 선언되지 않았다면 %문자 없이도 기능을 수행한다. 예를 들어 cls라는 변수가 선언되어 있다면 %cls라고 입력해야 하지만 cls라는 변수가 없다면 그냥 cls라고 입력해도 동작을 수행한다.


매직명령어

기능

%magic

%lsmagic

모든 매직 명령어의 도움말 출력

매직 명령어 리스트

%automagic

매직함수를 %없이도 실행하게끔 함(default) 또는 %를 붙여야만 실행하게끔 함(실행할 때마다 전환됨)

%pprint

pretty print 모드의 on/off 전환

%exit

%quit

물어보지 않고 IPython을 종료시킨다.

%cls

화면 클리어

%who

%who_ls

%whos

변수의 리스트를 보여준다.

변수 리스트를 파이썬 리스트로 반환한다.

변수명 뿐만 아니라 변수 값도 보여준다.

%reset

%reset -f

작업공간을 초기화 시킨다.

수행 여부를 묻지 않고 초기화 시킨다.

%run file.py

file.py 파일을 실행시킨다.

%paste

클립보드의 코드를 실행한다.

%cpaste

%edit (or %ed)

코드 블럭을 직접 입력한 후 실행한다.

텍스트에디터를 실행시킨 후 거기에 입력한 코드를 실행한다.

%time

%timeit

실행시간을 측정해서 보여준다.

여러 번 실행한 후 실행시간을 분석한다.

%hist

과거 명령어 리스트(history) 출력


%run 명령은 파이썬 스크립트 파일을 빈 작업공간에서 실행시킨다. 이 말은 현재 IPython 작업공간에 생성된 변수나 객체를 스크립트 파일 내에서 접근하지 못한다는 것이며 시스템 쉘에서 python file.py 와 같이 실행시키는 것과 동일한 효과를 가진다. 하지만 실행 후에는 스크립트 파일에 import 되거나 정의된 모든 함수/변수/객체는 IPython에서 접근가능하므로 편리하게 결과를 확인해 볼 수 있다.


 만약 실행 중인 코드를 중간에 멈출 필요가 있다면 <ctrl>+<c>를 누르면 된다.

 

시스템 명령은 다음과 같은 것들이 있다.


매직명령어

기능

! 명령어

쉘명령어를 실행 ( 결과를 리스트로 반환. 예를 들면 a = ! ls)

%pwd

현재 디렉토리 표시

%cd

디렉토리 바꾸기

%pushd

%popd

%dirs

현재 디렉토리를 스택에 저장

스택에 저장된 디렉토리를 빼내어 거기로 이동

디렉토리 스택의 내용 표시

%ls

리스트

%alias

%unalias


%cp

복사


'프로그래밍언어.Lib > 파이썬' 카테고리의 다른 글

<일단계 파이썬> 교재  (0) 2021.12.11
pyfirmata 기초  (0) 2019.12.02
ipython 기본 사용법  (0) 2016.10.18
MicroPython 개발자의 IoT 발표 (pyCon2016)  (0) 2016.09.27
윈도7에서 ipython 설치하기  (0) 2016.01.07
Posted by 살레시오

댓글을 달아 주세요

1. 기본 사용법    c{ipy01}

 IPython은 python쉘에 시스템 명령과 각종 편의 기능을 추가한 강력한 파이썬 실행 환경이다. 2001년 Fernando Perez가 개발을 시작했고 이 후에 현재까지 파이썬 모듈 중에서 매우 중요한 도구로 (특히 과학 계산 분야에서) 널리 사용되고 있다.


 먼저 IPython을 실행시키면 눈에 띄는 기능이 pretty print 기능이다. 예를 들어 파이썬쉘에서 dir()명령을 수행하면 다음과 같이 그냥 나열인데


>>> a=list(range(10))

>>> a

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> dir(a)

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__'

, '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__'

, '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__'

, '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_e

x__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__s

izeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'ex

tend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']


IPython 쉘에서는 다음과 같이 보기 쉽게 세로로 나열해 준다.


In [12]: a=list(range(10))


In [13]: a

Out[13]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


In [14]: dir(a)

Out[14]:

['__add__',

'__class__',

'__contains__',

'__delattr__',

'__delitem__',

'__dir__',

'__doc__',

'__eq__',

'__format__',

'__ge__',

'__getattribute__',

'__getitem__',

'__gt__',

'__hash__',

'__iadd__',

'__imul__',

'__init__',

'__iter__',

'__le__',

'__len__',

'__lt__',

'__mul__',

'__ne__',

'__new__',

'__reduce__',

'__reduce_ex__',

'__repr__',

'__reversed__',

'__rmul__',

'__setattr__',

'__setitem__',

'__sizeof__',

'__str__',

'__subclasshook__',

'append',

'clear',

'copy',

'count',

'extend',

'index',

'insert',

'pop',

'remove',

'reverse',

'sort']


이 기능은 특히 데이터(행렬이나 테이블)를 화면에 표시할 경우 눈에 읽기 쉽게 잘 정렬해서 보여주게 된다.


 또한 탭 완성기능이 있는데 배쉬쉘에서처럼 경로/파일이름을 자동 완성해주거나 또는 파이썬 객체의 이름을 자동으로 완성시켜주어서 매우 편리하게 사용할 수 있다. 예를 들면 아래와 같이 rand 모듈의 하위 함수들을 모두 표시하고 싶다면 rand.<tab>을 입력한다.


In [19]: import random as rand

In [20]: rand.<tab>
rand.BPF             rand.expovariate     rand.randrange
rand.LOG4            rand.gammavariate    rand.sample
rand.NV_MAGICCONST   rand.gauss           rand.seed
rand.RECIP_BPF       rand.getrandbits     rand.setstate
rand.Random          rand.getstate        rand.shuffle
rand.SG_MAGICCONST   rand.lognormvariate  rand.triangular
rand.SystemRandom    rand.normalvariate   rand.uniform
rand.TWOPI           rand.paretovariate   rand.vonmisesvariate
rand.betavariate     rand.randint         rand.weibullvariate
rand.choice          rand.random

예를 들어 rand.w<tab> 을 입력하면 후보가 하나밖에 없으므로 나머지를 자동으로 완성시켜 준다. 한 가지 주의할 점은 ,<tab>을 입력했을 때 내부 변수( _, __로 시작하는 속성)들은 화면에 표시되지 않는다.


In [22]: a=list(range(10))

In [23]: a.<tab>
a.append  a.copy    a.extend  a.insert  a.remove  a.sort
a.clear   a.count   a.index   a.pop     a.reverse

내부 변수들을 보고 싶다면 a._<tab> 이라고 입력하면 _로 시작하는 모든 필드를 보여 줄 것이다.


In [23]: a._<tab>
a.__add__          a.__ge__           a.__le__           a.__reversed__
a.__class__        a.__getattribute__ a.__len__          a.__rmul__
a.__contains__     a.__getitem__      a.__lt__           a.__setattr__
a.__delattr__      a.__gt__           a.__mul__          a.__setitem__
a.__delitem__      a.__hash__         a.__ne__           a.__sizeof__
a.__dir__          a.__iadd__         a.__new__          a.__str__
a.__doc__          a.__imul__         a.__reduce__       a.__subclasshook__
a.__eq__           a.__init__         a.__reduce_ex__
a.__format__       a.__iter__         a.__repr__

어떤 함수나 객체에 대한 도움말을 보고 싶다면 객체명 앞이나 뒤에 ?를 붙이면 된다.


In [32]: ?a.append
Docstring: L.append(object) -> None -- append object to end
Type:      builtin_function_or_method

In [33]: a.pop?
Docstring:
L.pop([index]) -> item -- remove and return item at index (default last).
Raises IndexError if list is empty or index is out of range.
Type:      builtin_function_or_method

만약 도움말이 길다면 <enter> 키를 누르면 그 다음 페이지를, <q>키를 누르면 종료가 된다. 함수명 앞이나 뒤에 ??와 같이 물음표를 두 개를 붙이면 도움말과 함께 (가능한 경우에만)소스 코드도 같이 보여준다.


In [43]: def main():print('hello world.')

In [44]: main?
Signature: main()
Docstring: <no docstring>
File:      c:\users\jhpark\<IPython-input-43-c8161681d5fc>
Type:      function

In [45]: main??
Signature: main()
Source:    def main():print('hello world.')
File:      c:\users\jhpark\<IPython-input-43-c8161681d5fc>
Type:      function

기본적인 키 입력은 다음과 같다.


기능

⍐⍗

ctrl + r

히스토리 검색

입력된 단어를 포함하는 히스토리 실시간 검색

⍈⍇

ctrl +⍈⍇

한 글자 좌우로 이동

한 단어씩 좌우로 이동

ctrl+shift+v

클립보드에서 붙어녛기

ctrl + K

ctrl + U

ctrl + L

커서로부터 줄 끝까지 지우기

현재 줄 지우기

현재 화면 전체를 지우기

ctrl + c

실행 중지

ctrl + z

직전 작업 취소


여기서 ctrl+c는 복사명령어가 아니라 실행을 중시키시는 키이다. 무한루프 실행을 중단시키거나 시간이 오래걸리는 코드를 중간에 멈출 때 사용된다.


Posted by 살레시오
TAG ipython

댓글을 달아 주세요

  이번 pyCon2016에서는 MicroPython 개발자인 D. George 의 두 가지 발표가 눈에 띈다. 참 이력이 독특하다고 생각되는 사람인데, 첫 번째는 마이크로파이썬의 시작부터의 과정을 설명해주는 영상이 있다. 앞으로의 지속적인 개발에 필요한 투자를 유치한 것 같아서 마이크로파이썬에 관심이 많은 사람으로서 기분이 좋다.

 두 번째는  IoT에 대한 발표 (pyCon2016) 이다. 아래에 링크를 걸었다.

제목 : Scripting the Internet of Things


Posted by 살레시오

댓글을 달아 주세요

1.1 windows에서 설치하는 방법   c{ipy03}

먼저 윈도우 커맨드창을 반드시 '관리자 모드'로 실행시켜야 한다. (설치과정에서 시스템의 임시폴더를 접근하므로) 이것을 위해서 c:\windows\system32\cmd.exe 를 찾아서 마우스 우클릭후 '관리자모드로 실행'을 선택하여 커맨드창을 연다


그 다음 파이썬과 pip 모듈이 설치되었다고 가정하고 다음과 같이 명령을 내린다.


easy_install ipython[all]

혹은


python -m pip ipython[all]

이렇게 하면 ipython을 실행하기 위해서 필요한 모듈들이 모두 설치된다. 설치하는 데 조금 시간이 걸린다. 이제 다음과 같은 명령을 내리면 ipython이 실행된다.


python -m IPython

여기서 IPython의 첫 두 글자 'I'와 'P' 가 대문자이므로 유의해야 한다.



Posted by 살레시오

댓글을 달아 주세요

 클래스의 메서드는 일반 함수를 정의하는 것과 동일한데 한 가지 첫 번째 인자는 반드시 self가 되어야 한다는 점이 다르다.  관례적으로 메서드 정의들 사이에는  한 줄 공백을 준다.


class Robot:

   def __init__(self, name='dummy'):
       self.name = name

   def talk(self):
       print('Hi. I am %s.'% self.name)


self라는 변수를 클래스 함수의 첫번째 인자로 받아야 하는 것은 파이썬만의 (불필요해 보이는) 특징이다. 문법이 이렇게 규정되어 있으니 사용자는 거기에 맞추어 작성해 주어야만 한다. 메서드 내에서는 이 self 변수를 통해서 기정의된 필드를 접근할 수 있으며 또 한 새로운 필드를 생성하는 것도 가능하다. talk() 메서드 내부에서 name 이라는 필드를 self를 통해서 (즉, slef.name 와 같이) 사용했음을 알 수 있다. 객체의 필드는 이와 같이 메서드 내에서 반드시 self 변수를 통해서만 접근할 수 있다.


 객체의 필드와 메서드 목록을 확인하고 싶으면 내장함수 dir()를 이용하면 된다.


>>> asimo=Robot(‘asimo’)
>>> dir(asimo)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'talk']


위에서 보면 매직 메서드들 다음에 name, talk 등 필드와 메서드의 이름을 모두 확인할 수 있다.


   이제 asimo라는 Robot 클래스 객체를 통해서 메서드를 호출할 수 있다. 객체명 뒤에 점(.)을 찍고 함수명을 명시하면 된다.


>>> asimo.talk()
Hi. I am asimo.


메서드를 호출하는 쪽에서는 정의부의 self인자는 무시하고 (없는 것으로 간주하고) 인자를 주면 된다. 따라서 asimo.talk() 메서드는 아무런 인자를 주면 안된다. __init__() 메서드와 마찬가지로 self 인자 뒤에 일반인자를 줄 수 도 있다.


class Robot:

   def __init__(self, name = 'dummy'):
       self.name = name

   def talk(self):
       print('Hi. I am %s.'% self.name)

   def walk(self, step):
       print('Sorry. No legs.')

   def run(self, dist, speed = 10):
       print('Must go %dm at %dm/s speed.'%(dist, speed))
       print('Sorry. No legs.')


이렇게 정의하면 walk()함수는 하나의 일반 인자를 주고 호출해야 하며 run()은 하나 혹은 두 개의 일반 인자를 주어야 한다.


>>> asimo.walk(5)
Sorry. No legs.
>>> asimo.run(100, 50)
Must go 100m at 50m/s speed.
Sorry. No legs.
>>> asimo.run(100)
Must go 100m at 10m/s speed.
Sorry. No legs.


이와 같이 메서도는 첫 번째 인자가 self 라는 점만 제외하면 일반 함수와 정의와 호출하는 방법이 동일하다.



Posted by 살레시오

댓글을 달아 주세요

  1. JJ 2016.07.04 15:20  댓글주소  수정/삭제  댓글쓰기

    강좌 잘 봤습니다! 다음 글도 기다리겠습니다~

 파이썬 3.4 이상에서 표준화된 Enum객체를 지원한다. 자세한 설명은 여기에 있다. 간략한 사용법을 알아보도록 하자.


 일단 Enum 클래스를 임포트해야 한다.


>>> from enum import Enum


첫 번째로 다음과 같이 클래스를 Enum을 상속해서 생성할 수 있다.


>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3


이제 Color.red 또는 Color.green 과 같이 사용할 수 있다. 첫 번째 값은 보통 1부터 시작한다. (0으로 시작하면 그 필드는 False가 된다.)


 다른 방법으로 다음과 같이 더 간단히 생성할 수 있다.


>>> Color = Enum('Color', 'red green blue')


유의할 점은 인스턴스 이름과  Enum생성자의 첫 인자가 (위에서 밑줄 쳐진 두 부분) 같아야 한다. 이제 이전과 마찬가지로 Color.red 또는 Color.green 과 같이 사용할 수 있다.


enum 필드는 name과 value를 가진다. 예를 들어 Color.blue 의 name 은 'blue' value 는 3 이다.


>>> Color.blue.name
'blue'

>>> Color.blue.value
3

>>> type(Color.blue)
<enum 'Color'>



Posted by 살레시오

댓글을 달아 주세요

1. 개요

 상미분방정식(이하 상미방)의 수치 적분을 수행하기 위해서 scipy.integrate.odeint 함수의 사용법에 대해서 알아보도록 하겠다. scipy.integrate 클래스는 적분을 수행하는 다양한 함수들이 모여 있는데 그 중 연립 상미방의 수치적분을 구하는 ode, odeint, complex_ode 함수들이 마련되어 있다.

 

  • odeint             : 상미방의 수치적분

  • ode                : 상미방의 수치적분 (generic interface class)

  • complex_ode  : 복소 상미방의 수치적분

 

이중 odeint 는 내부적으로 FORTRAN 라이브러리인 odepack 의 'lsoda'를 사용하여 상미방을 풀어내는 함수이다. 이 함수는 (연립) 상미분 방정식

  • y' = f ( y, t )

의 수치해를 구해주며 stiff ode 와 non-stiff ode 모두에 적용된다.

2. 기본 문법


 일단 레퍼런스의 함수 문법은 다음과 같다.


y, infodic = scipy.integrate.odeint (
   func,# callable(y, t, ...) : 시간 t 에서 y(t)의 미분값을 구할 수 있는 함수
   y0,  # array_like : y 의 초기 조건 (벡터도 된다.)
   t,   # array_like : y(t)값을 구할 t 점들. 초기값의 시간이 이 배열의 첫 요소여야 함.
   args=()  # 튜플 : func() 에 넘길 추가적인 인수들
   Dfun=None, col_deriv=0, full_output=0, ml=None, mu=None, rtol=None, atol=None,
   tcrit=None, h0=0.0, hmax=0.0, hmin=0.0, ixpr=0, mxstep=0, mxhnil=0, mxordn=12,
   mxords=5, printmessg=0
)

 

여기서 필수적인 인수는 func, y0, t 세 개이며 각각 ODE함수, 초기값, 시간(벡터)이다. 나머지 인수들은 모두 선택적으로 지정해 줄 수 있으며 지정하지 않으면 설정값이 사용된다. 그리고 y0와 t는 리스트, 튜플, ndarray (이것들을 묶어서 array_like 라고 한다.) 등이 될 수 있다.

 이 함수를 사용하기 전에 먼저 상미방을 기술하는 함수를 먼저 정의해야 한다. 이 함수는 array_like 를 반환해야 하며 ndarray y와 시간 t 를 받아서 그 미분을 구하는 함수이다.

 odeint() 함수의 출력 y는 배열(ndarray) 이고 shape 이 ( len(t), len(y0) ) 이며 2차 배열이다. 입력으로 넘어온 t 배열의 각 점에서의 y값들을 갖는다. 두 번째 출력인 infodict 는 full_output== True 로 지정되었을 경우 추가적인 정보가 넘어오게 된다.

3. 간단한 1차 ode 예제

 다음과 같이 가장 간단한 1차 미방을 시간 구간 [0, 5] 에서 수치해를 구하는 예제를 해 보도록 하겠다.


  • y' =  -2y, y(0)=1

위 예제에서는 pylab 모듈에 numpy가 포함되어 있으므로 따로 numpy는 import하지 않았다.

4. 2차 ode : 조화진동자

  조화 진동자 방정식은 2차 상미방으로서 x''(t) = -x(t) 이다. 2차 방정식은 두 개의 1차 방정식으로 분해할 수 있다. 즉, y(t) = [ x(t) ; x'(t) ] =: [y1(t); y2(t)] 라고 정의하면

              y'(t) = [x'(t); -x(t)] = [y2(t); -y1(t)]

와 같이 기술할 수 있다. 초기값이 [0; 1] 일 경우 시간 [0, 5]에 대해서 이 상미방을 풀면 다음과 같다. 이 예제에서는 ode1()함수의 반환값도 튜플이고 odeint()함수의 초기값도 튜플로 주었다.


Posted by 살레시오

댓글을 달아 주세요

 다음 표는 산술/논리 연산에 관련된 내장 함수들이다.


[표 1] 산술/논리 연산에 관련된 내장 함수들

hex(n)

oct(n)

bin(n)

정수 n의 16진수 값을 구해서 ‘문자열’로 반환한다.

정수 n의 8진수 값을 구해서 ‘문자열’로 반환한다.

정수 n의 2진수 값을 구해서 ‘문자열’로 반환한다.

abs(n)

절대값을 구한다. 복소수의 경우 크기를 구한다.

pow(x,y[,z])

거듭제곱을 구한다. pow(x,y)은 x**y 와 같다.

divmod(a,b)

a를 b로 나눈 (몫, 나머지)를 구한다. 튜플 반환.

all(iterable)

any(iterable)

iterable 의 모든 요소가 True 일 경우 True를 반환.

iterable 의 하나 이상의  요소가 True 일 경우 True를 반환.

max(iterable)

max(arg1, arg2, …)

최대값을 구한다.

min(iterable)

min(arg1, arg2, …)

최소값을 구한다.

round()

반올림을 한다.


hex(), oct(), bin() 함수는 각각 ‘0x’, ‘0o’, ‘0b’ 로 시작하는 ‘문자열’로 결과를 반한한다. 이 문자열을 파이썬 값으로 변환하려면 eval()함수를 이용하면 된다.



Posted by 살레시오

댓글을 달아 주세요