파이썬 튜토리얼

하악하악... 공식 튜토리얼 기준.

하악하악... 이런 철지난 자료 하악하악...



= Python Quickstart =

    * 확장적인 문법은 설명하지 않을게요. 이런 부분은 관심 있는 분들이 찾아보시길 ^^;
        * function decorator
        * list comprehension
        * generator/iterator

    * http://docs.python.org/tut/tut.html <-- 요 튜토리얼의 순서를 따라서 정리를 해봤어요.












=== 설치와 시작, 실행, 소스파일 ===

    * 설치는 당연히 python.org에서 인스톨러를 받아서 설치하시면 됩니다. ^^
        * 설치가 끝나면 python.exe가 있는 디렉토리를 PATH에 추가만 해주시면 설정이 끝나요.
    * "*.py", "*.pyw"으로 저장된 파일은 파이썬 소스파일이며, 이를 탐색기에서 더블클릭하거나 명령줄에서 바로 지정하면 실행되요. (java.exe, javaw.exe 생각하시면 딱 맞아요.)
    * 실행은 python.exe을 명령줄에서 실행하시면 파이썬 쉘을 실행하셔서 한줄씩 대화식으로 사용하실 수 있습니다. (대화식, incremental, iterative)







=== 제일 기본적인 자료표현, 그리고 기타 알아두면 편리한. ===

    * '#' 으로 시작하는 라인은 주석으로 인식하여 무시합니다.

    * 'pass'문은 아무것도 하지 않는 문장입니다. (어셈블리에서 'nop'처럼)(파이썬은 블럭구조를 들여쓰기를 해서 표현해야 하므로 빈공간을 그냥 둘 수 없어서 pass가 필요해요^^)

    * 'print x'은 x라는 변수의 내용을 화면에 뿌려줍니다.

    * 변수는 단순히 CamelCase나 under_line을 사용해서 명명해주시면 됩니다. (보통 클래스 이름은 CamelCase, 나머지는 under_line을 사용하는게 파이썬 컨벤션)

    * dir(obj) 이렇게하셔서 해당 객체에 포함된 메서드와 속성을 바로 살펴보실 수 있어요.

    * help(obj) 요렇게하셔서 해당타입이나 모듈에 대한 문서를 바로 확인해 읽으실 수 있어요. (help(dir) 해보세요^^)

    * 전역적으로 사용하는 help(), dir() 같은 함수들은 사실 __builtins__라는 모듈(패키지'라고 생각하실 수 있죠)에 포함된 것들이에요
        * 그러니까 dir(__builtins__)하면 전역함수들이 뭐뭐 있는지 바로 아실 수 있는거지용^^

    * 'foo', "foo", """foo"""  모두 같은 표현식으로 문자열을 표현하는데 따옴표를 사용합니다. 쌍따옴표 3개를 겹쳐서 사용하시면 여러줄의 문자열을 사용하실 수 있습니다.

    * 어떤 문자열의 각 문자의 인덱스는 다음처럼 나타냅니다.

      +---+---+---+---+---+
       | H | e | l | p | A |
       +---+---+---+---+---+
       0   1   2   3   4   5
      -5  -4  -3  -2  -1

        * s = "HelpA"라는 문자열일때 인덱스들이 할당이 됩니다. 앞에서부터 0, 뒤에서부터 -1임을 알 수 있죠?
        * 슬라이싱 -- 문자열의 부분문자열을 취할때도 적용이 가능하지만 그 이외에 sequence형들에 모두 적용이 가능합니다.
            * s[0:] == "HelpA"
            * s[1:] == "elpA"
            * s[1:2] == "e"
            * s[1:3] == "el"
            * s[:3] == "Hel"
            * s[:-2] == "Help"
        * s.upper() # 이런식으로 문자열에도 메서드를 적용할 수 있겠죠.

    * 일반적인 integer, float등의 표현.
        * 4, -3, 42 # 정수형
        * 4.3, 3.14, -3 # float
        * 397831789L # long
        * 기타 복소수라든지 뭐 그런건 일단 생략.

    * 그리고 일단 '리스트list'에 대해서 설명할게요. <-- 배열, 목록.
        * l = [1, 2, "foo"]         # ...이런식으로 [...]을 사용해서 리스트를 표현할 수 있구요...
        * l[0], l[:-1]              #...이렇게 문자열에 적용하는식으로 슬라이싱 할 수 있어요.
        * len(s)                    # 리스트의 길이나 문자열의 길이를 구하는데 사용할 수 있어요. 함수? 연산자?







=== 흐름제어 ===



===== 레이아웃? 공백문자? ======

    * 파이썬은 블럭({ ... }, begin ... end등)을 특정한 문자를 사용하여 표시하지 않고 중첩된 블럭은 상위 블럭보다 더 들여써서 표시합니다.
   
    <code>
 
        // Java
        if (foo == true) {
            n = 99;
        } else {
            ;
        }

        # Python
        if foo == True:
            n = 99
        else:
            pass

    </code>


    * 한 파일에서 들여쓰기는 탭이나 스페이스 모두 허용되지만, 하나의 규격으로 통일해야 합니다.

    * 일반적으로 '스페이스 4칸'(소프트탭으로 설정하시면 되겠죠?)이 표준입니다.


===== if-elif-else =====

    <code>

        x = int(raw_input("숫자를 넣어주세요: "))               # raw_input으로 입력(문자열)을 얻어 int을 통해 정수로 변환.
        if x < 0:
            x = 0
            print '음수'
        elif x == 0:
            print '0!'
        else:
            print '양수'

    </code>

    * 'else if'가 아니라 'elif'임에 주목.

    * 불리언 연산에서 같지 않음은 '!='이 아니라 '<>'입니다. (자바: a != b --> a <> b)

    * true -> True, false -> False 임에 주의해주세요. ^^

    * null 대신에 None입니다. ^^

    * True, False, None은 모두 대문자이며 자체로 타입이며, 인스턴스입니다.

    * 불리언 부정을 위한 not 연산자가 따로 있어요. (자바 !false -> not False)

    * 재미있게도 c/c++의 삼항연산자(cond?when_true:when_false)가 지원되요. ^^;

    <code>

        x = int(raw_input("숫자를 넣어주세요: "))               # raw_input으로 입력(문자열)을 얻어 int을 통해 정수로 변환.
        print (x<0)?"음수":"0이나 양수"

    </code>



===== while =====

    * while은 다른 언어와 크게 다를 것이 없습니다.

    <code>

        // Java
        int n = 99;
        while (n > 0) {
            out.println(n);
            n --;
        }

        # Python
        n = 99
        while n > 0 :
            print n
            n = n - 1

    </code>





===== for =====

    * 이전에 설명한 리스트, 문자열과 같은 시퀀스(sequence, 순서에 따라 접근이 가능한 자료)은 for의 대상일 수 있습니다.

    <code>

        seq = [1, 2, 3]
        for i in seq:
            print i

    </code>

    * 'seq'의 각 요소를 하나씩 처음부터 끝까지 i로 참조하면서 중첩된 블럭을 실행하는 코드겠죠?  다른 언어에서 foreach와 같다고 생각하시면 됩니다.





===== range(), xrange() =====

    * range(), xrange()은 범위에 해당하는 시퀀스를 생성해주는 것들입니다. 이를 이용하여 c/c++/java의 for문을 흉내낼 수 있겠죠.

    <code>

        // java
        for (int n = 0 ; n < 99 ; n ++ ) {
            out.println(n);
        }

        # Python
        for n in range(0, 100):
            print n
    
    </code>


    * range은 range(begin, end, step)의 식으로 증가치가 1이 아닌 경우나 작아지는 경우도 지정할 수 있습니다.




===== break, continue, else =====

    * 다른 언어들에서처럼 루프안에서 break, continue을 똑같이 사용합니다.
    * while, for에 뒤에도 else절이 따를 수 있는데 이때는 해당 루프가 종료될때 이것이 실행됩니다. ^^





=== 함수 ===
      


===== 함수선언하기 =====

    <code>

        // Java
        public boolean add(int a, int b) {
            return a + b;
        }

        # Python
        def add(a, b):
            return a + b

    </code>

    * 'def' 키워드를 사용함을 주목

    * 되돌림값의 타입을 표기할 필요가 없음에 주목.

    * 인자의 목록을 적을때도 마찬가지로 바인딩 변수의 이름만 적고 타입은 필요없음.



===== 함수도 값이랍니다 =====

    <code>

        add(1, 3)     # 괄호가 있으므로 함수호출
        v = add       # 괄호가 없으므로 함수에 대한 참조입니다.
        v(1, 3) == add(1,3)   # 값으로 변수에 대입하거나 다른 함수에 인자로 넘기거나 자유롭게 취급할 수 있음.

    </code>

 


 
===== 함수인자 (디폴트, 키워드) =====

    <code>

        def list_of(a, b='B', c='C'):
            return [a, b, c]


        ################################

        >> list_of('a')
        ['a', 'B', 'C']

        >> list_of('a', 'b')
        ['a', 'b', 'C']

        >> list_of(1, c=3, b=2)
        [1, 2, 3]

    </code>





===== 가변인자 (리스트, 키워드) =====

    <code>

        ## 가변길이인자목록
        def how_many_args(*args):
            return len(args)

        ################################

        >> how_many_args(1,2,3)
        3

        >> how_many_args()
        0


        ## 키워드인자목록
        def kwargs(**kw):
            if kw.has_key('foo'):
                print "ok"
            else:
                print "no"

        ################################

        >> kwargs(bar=1, zoo=2)
        no

        >> kwargs(foo=0)
        ok

    </code>


    * 인자목록에 지정하지 않은 가변적인 길이의 추가적인 인자를 받고자 할때 *args와 같이 인자의 이름 앞에 *을 붙여주면 가변길이 인자로서 인자의 목록을 순서에 따라서 리스트로 만들어서 전달해줍니다.

    * '**'을 붙여 키워드 인자셋으로 만든다면 'foo(k=1, v=2)'와 같이 하였을때 해당 인자이름으로 해쉬를 만들어서 전달해줍니다.

    * ...지금까지 정리한 순서를 모두 적용하려고 한다면 반드시 다음과 같은 코드가 되어야 합니다. (중간에 생략은 가능합니다.)

    <code>

        def some_func1(a, b, c=1, *args, **kw):
            pass

        def some_func2(a, **kw):
            pass

    </code>





===== 인자목록을 함수에 적용하기 =====

    리스트나 해쉬를 바로 함수에 적용하고자 할때 일일이 그 요소, 요소를 하나씩 인자로 매핑해주기 귀찮을때!

    <code>

        def list_of(a, b, c):
            return [a, b, c]

        ################################

        >> list_of(*[1,2,3])
        [1, 2, 3]

        >> list_of(**{'c':3, 'a':1, 'b':2})
        [1, 2, 3]

    </code>


    특히 인자가 해쉬등으로 넘어다닐때 편리하겠죠? ^^



===== 이름 없는 함수, 람다 =====

    함수를 인자로 요구하는 함수들이 종종 있습니다. (또는 만들수도 있겠죠) 이런 경우에 함수를 전부 작성하고 하기 귀찮은 경우에 주로 사용합니다.

    이렇게 함수를 인자로 전달하거나 함수의 결과가 함수인 경우의 형식을 '함수형 프로그래밍 (functional programming)'이라고 합니다.


    <code>

        def add(a, b):
            return a + b

        >> reduce(add, [1, 2, 3])
        6

    </code>

    ...라고 작성하기 보다는...

    <code>
        >> reduce(lambda a, b : a + b, [1, 2, 3])
        6
    </code>

    인라인으로 함수를 적어주고자 할 때 사용합니다. (실제로 연산자와 같은 부분들에 대해서 이미 정의되어 있는 operator 모듈을 이용하는게 바람직합니다. ^^;)


    <code>
        >> import operator
        >> reduce(operator.add, [1, 2, 3])
        6
    </code>






===== 문서화문자열 =====

    <code>

        def some_func1(a, b, c):
            "this is some function!"
            pass

    </code>


    이렇게 함수의 맨 앞에 문자열을 두면 help(some_func1)와 같이 도움말을 보거나 할때 자동으로 이를 보여줍니다.

    일반적으로 함수의 설명이나 예제를 여기에 적어둡니다.

    * 힌트:

    >> 명령줄
    해당명령줄의 결과
    ...

    
        * 요렇게 문서화문자열을 적어주면 doctest 모듈을 이용해서 별다른 테스트케이스를 작성하지 않아도 그 예제와 같이 작동하는지 실제로 실행해보는 테스트를 문서와 통합할 수 있습니다. -_-b

        * 이후에 설명들의 예제를 기준을 들을때는 이렇게 적용하겠습니다.

    




=== 복합자료구조 ===

  ### del
  >> l = [1, 2, 3]          # 리스트를 만들고
  >> del l[1]               # 리스트의 일부를 del 연산자로 삭제
  >> l
  [1, 3]

  * del 연산자는 슬라이싱 연산으로 적용할 수 있는 범위를 자연스럽게 적용하실 수 있어요. ^^

  >> v = 1
  >> v
  1
  >> del v                  # 변수를 del연산적용
  >> v                      # 변수 바인딩 자체가 사라졌음!
  NameError

  * del 연산자를 이용하여 리스트의 부분열을 제외할수도 있지만 특정 변수 자체를 지우는데 사용이 가능!

  >> t = (1, 2, 3)          # 리스트와 유사하지만 (...)은 튜플.
  >> del t[1]               # 튜플에 del연산적용... 실패.
  TypeError

  * 튜플(tuple)은 리스트(list)와 유사하지만 immutable하므로 삭제를 적용할 수 없습니다. ^^;



===== 리스트에 대한 일반적인 연산들 =====

  ### append
  >> l = [1, 2]
  >> l.append(3)            # 리스트의 마지막에 요소를 추가
  >> l
  [1, 2, 3]

  ### insert
  >> l = [1, 3]
  >> l.insert(1, 2)         # 인덱스 1의 위치에 2를 삽입
  >> l
  [1, 2, 3]

  ### remove
  >> l = ['a', 'c', 'a']
  >> l.remove('c')          # 첫번째 'c'인 요소를 제거
  >> l
  ['a', 'a']

  ### index
  >> l = ['a', 'c', 'a']
  >> l.index('c')           # 첫번째 'c'인 요소의 인덱스?
  1

  >> l.index('b')           # 존재하지 않는 요소에 대해 검색하면...
  ValueError

  >> 'b' in l               # in 연산자를 이용해 포함여부를 판별가능.
  False

  >> 'c' in l
  True

  ### count
  >> l = ['a', 'b', 'a']
  >> l.count('a')           # 'a'인 요소의 갯수를 파악
  2
  >> l.count('c')
  0

  ### sort
  >> l = [3, 4, 1, 'a']
  >> l
  [3, 4, 1, 'a']
  >> l.sort()               # 정렬!
  >> l
  [1, 3, 4, 'a']

  ### reverse
  >> l = [1, 2, 3]
  >> l.reverse()            # 거꾸로 뒤집기!
  >> l
  [3, 2, 1]


  ### stack: append, pop
  >> l = []
  >> l.append(1)
  >> l.append(2)
  >> l
  [1, 2]
  >> l.pop()                # pop()으로 맨 마지막 요소를 꺼내기
  2
  >> l.pop()
  1
  >> l
  []
  >> l.pop()
  IndexError

  ### queue: append, pop
  >> l = []
  >> l.append(1)
  >> l.append(2)
  >> l.pop(0)               # pop(0)으로 맨 앞에서 하나 꺼내기
  1
  >> l.pop(0)
  2
  ...

  ### len(l)
  >> l = range(1, 10, 2)
  >> l
  [1, 3, 5, 7, 9]
  >> len(l)                 # 시퀀스의 길이를.
  5




===== 힌트 =====

    * sets : set은 리스트와 유사하지만 '집합'의 개념에 더 가깝게 중복된 요소를 허용하지 않습니다.

    * dict : { ... } 의 꼴로 키-값 짝을 나열하여 해쉬(혹은 Map)을 사용할 수 있습니다.

    * zip, filter, reduce, map : 흔히 lisp등에서 자주 쓰이는 이런 함수들을 이용해 더 쉽게 리스트 작업을 처리할 수 있습니다.

    * 리스트 표현식 : 특정한 조건에 맞춰 리스트를 나열할 수 있습니다. (조건제시법 <=> 원소나열법)









=== 모듈 (패키지?) ===

    * 파이썬에서 각각의 소스파일은 모듈입니다.

    * 모듈은 다른 모듈을 포함할 수 있습니다.

    * 모듈은 변수나 함수, 혹은 클래스를 포함할 수 있습니다. (자바와 달리 여러개)




===== 평면 모듈 =====

    가장 기본적인 모듈로서 파일 하나로 이루어진 모듈입니다.

    <code>

        # rect.py

        def size(w, h):
            return w * h

    </code>



===== 모듈 포함/사용 =====

    위와 같이 저장한 rect.py을 사용할 수 있습니다.

    >> import rect
    >> rect.size(3, 9)
    27

    import문은 모듈 경로를 생략하여 모듈 내용을 포함하도록 할 수 있습니다.

    >> from rect import *
    ...혹은 몇가지만 포함하고자 한다면
    >> from rect import size
    ...혹은...
    >> from rect import size, somefunc

    >> size(3, 9)
    27



===== 모듈을 스크립트로? =====

    또한 다음처럼 파일의 끝에 추가하여 모듈이 다른 스크립트에서 포함되지 않고 바로 실행될때 테스트(유닛테스팅)하도록 하기도 합니다.

    <code>

        # rect.py의 아래쪽에 추가...
        if __name__ == '__main__':
            assert(size(3, 9) == 27)
            # ...

    </code>

    이렇게 저장한 모듈은 명령줄에서 "python rect.py"와 같이 실행하여 단위테스팅을 하거나 할 수 있습니다.



===== 모듈 검색경로 =====

    * 기본적으로 현재 실행되는 스크립트의 하위 디렉토리에 위치한 모듈들을 검색합니다.

    * 그렇지 않다면 파이썬이 설치된 디렉토리의 lib/등을 찾아봅니다.

    * 새로운 모듈 경로를 추가하려면 sys모듈의 path변수에 이를 추가하면 됩니다.

    >> import sys
    >> sys.path.append('/some/path/here/')


    * 시스템의 PYTHONPATH을 이용합니다.



===== 모듈 계층 =====

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

모듈은 자바에서 패키지처럼 디렉토리를 통해서 구분될 수 있습니다.

한가지 주의할 점은 각 패키지 디렉토리마다 __init__.py을 작성해주어야 한다는점입니다.

일반적으로 빈 파일만 위치하면 별 문제가 없습니다.

하지만 해당 모듈에 (위의 그림에서는 sound쯤?) 전역변수나 함수를 추가하고자 한다면(예를 들어 sound.play()라는 함수를 추가하려면) 이들의 내용을 해당 모듈의 __init__.py에 적어주면 됩니다.


    





=== 입출력 ===

===== 포매팅 =====


    * 문자열에 대해 % 연산자를 적용하면 printf와 같이 사용이 가능합니다.

    >> print "%d times" % 3
    3 times

    >> print "'%s' is '%s'" % ('foo', 'bar')
    'foo' is 'bar'


    * 문자열 % 연산의 결과는 문자열이므로 이를 얼마든지 재사용할 수 있습니다.

    >> s = "%d/%f" % (1, 1.1)
    >> print s
    1/1.1


    * 기타 zfill등의 함수등도 있으며 다양한 포매팅 모듈들이 존재합니다.


===== 파일 =====

    * file()함수등을 이용하여 파일을 열고 읽고 쓸수있음.

    * 기타 입출력(소켓, 디렉토리)등에 대한 모듈들이 항상 존재함.









=== 에러와 예외 ===

    * 다른 일반적인 언어들에서 하는 것처럼 예외처리를 지원

    * 예외는 사실 Exception을 상속하는 객체임.

    * 대부분의 예외를 새로 정의하기 보다는 기존의 존재하는 예외 객체들을 이용하여 메시지를 바꾸거나 하여 이를 던지는 것이 일반적임.


===== 예외 던지기 =====

    >> raise NameError("my name is not FOOBAR!")
    NameError

    

===== 예외 받기 =====

    <code>

        # 가장 일반적으로 모든 에러를 잡는 방법.
        try:
            do_something()
        except:
            print "exception caught"


        # 특정 타입의 에러를 잡는 방법 #1
        try:
            do_something()
        except ValueError:
            print "VE!"
        except NameError:
            print "NE!"
        except:
            print "???"

        # 특정 타입의 에러를 잡는 방법 #2
        try:
            do_something()
        except (ValueError, NameError):
            print "VE or NE!"

        # 예외의 인스턴스를 얻는 방법
        try:
            do_something()
        except MyError, e:
            print e         # e로 인스턴스가 바인딩

    </code>



===== else, finally문 =====

    * 다른 제어구문과 마찬가지로 else절을 try에 붙일 수 있는데 이는 try 블럭을 실행하는 동안 예외가 없을때만 실행됩니다.

    * finally절도 붙일 수 있는데 이는 예외가 있건 없건 관계없이 그 블럭이 종료될때 항상 실행됩니다.

    <code>

        try:
            do_something()
        except:
            pass
        else:
            print "성공!"
        finally:
            print "종료 (무조건 실행)"

    </code>






===== 사용자 예외정의하기 =====

    * Exception 클래스를 상속하는 클래스를 작성하여 이를 처리합니다.
    (이후 클래스에 관해서 설명하고)



===== with문 =====

    아래와 같은 코드를...
    <code>

        for line in file("my.txt", "r"):
            print line

    </code>

    다음처럼 하는것이 바람직합니다.
    <code>
       with file("my.txt", "r") as f:
            for line in f:
                print line
    </code>









=== 클래스 ===

    * 인터페이스의 개념이 없음

    * 단일상속이 아니라 다중상속


    <code>

        // Java

        public class Account {

            private int balance = 0;

            public int getBalance() {
                return balance;
            }

            public int deposit(int n) {
                this.balance = this.getBalance() + n;
                return this.getBalance();
            }

            public int withdraw(int n) {
                this.balance = this.getBalance() - n;
                return this.getBalance();
            }

        }

    </code>

    이렇게 작성할 수 있겠죠...

    <code>

        class Account:
            def __init__(self):
                self.__balance = 0

            def balance(self):
                return self.__balance

            def deposit(self, n):
                self.__balance = self.balance() + n
                return self.balance()

            def withdraw(self, n):
                self.__balance = self.balance() - n
                return self.balance()

    </code>

    * class문을 사용하여 클래스를 정의한다.

    * private인 필드/메서드는 이름 앞에 '__'을 붙여 표시한다.

    * 인스턴스 메서드들은 항상 명시적으로 첫번째 인자로 'self'을 받아 이를 통해 객체 인스턴스에 접근한다.

    * 특별한 메서드들이나 이름은 앞뒤로 '__'을 붙여 표기한다.

    <code>

        >> a = Account()            # new 연산자 없이 클래스이름을 '호출'하여 생성.
        >> a.deposit(100)
        100
        >> a.withdraw(50)
        50
        >> a.deposit(10)
        60
        >> a.balance()
        60

    </code>


===== 상속 =====

    <code>

        class Animal:
            def say(self):
                print "Animal!!!"

        class Mammal(Animal):
            def say(self):
                print "Mammmmm!"

        class Bird(Animal):
            def say(self):
                print "Peeeeee!"

        class Unicorn(Mammal, Bird):
            def say(self):
                print "UniUni!!"
                print Mammal.say(self)  # super 메서드를 어떻게 선택할지...
                print Bird.say(self)

    </code>

    다중상속의 다이아몬드문제를 대략 해결...


===== 클래스 범위 =====

    * 클래스 필드는 self에 붙이는 것이 아니라 그 클래스에 그대로 클래스와 연관 없이 붙이시면 됩니다.


    * @staticmethod, @classmethod을 사용하여 다른 언어에서의 그것들을 구현할 수 있습니다.


    

2008-03-25 14:40:07 ageldama

by 아겔 | 2008/03/25 17:15 | 삽질+돈되는짓 | 트랙백 | 덧글(2)

트랙백 주소 : http://ageldama.egloos.com/tb/3675546
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 홍민희 at 2008/03/25 17:36
삼항연산자는 C/C++ 문법을 쓰지 않습니다.

print "음수" if x < 0 else "0이나 양수"

가 맞습니다.
Commented by 아겔 at 2008/03/25 17:37
생유~
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.

◀ 이전 페이지          다음 페이지 ▶