수업 방법의 목적은 무엇입니까?


250

나는 나 자신에게 파이썬을 가르치고 있으며 가장 최근의 교훈은 파이썬이 Java가 아니라는 것이므로 모든 클래스 메소드를 함수로 바꾸는 데 시간을 보냈습니다.

staticJava에서 메소드로 수행 할 작업에 클래스 메소드를 사용할 필요가 없다는 것을 알고 있지만 이제 언제 사용할지 잘 모르겠습니다. 파이썬 클래스 메소드에 대해 찾을 수있는 모든 조언은 나와 같은 초보자 라인을 따르고 있으며 표준 문서는 논의 할 때 가장 불투명합니다.

누구나 파이썬에서 클래스 메소드를 사용하는 좋은 예가 있습니까? 아니면 클래스 메소드를 현명하게 사용할 수있는 시점을 누군가가 말해 줄 수 있습니까?

답변:


178

클래스 메서드는 특정 인스턴스에 고유하지 않지만 여전히 어떤 방식으로 클래스를 포함하는 메서드가 필요할 때 사용됩니다. 그들에 대한 가장 흥미로운 점은 서브 클래스에 의해 재정의 될 수 있다는 것입니다. 하위 클래스는 Java의 정적 메소드 또는 Python의 모듈 레벨 함수에서는 불가능합니다.

class MyClass와 MyClass (공장, 의존성 주입 스터브 등)에서 작동하는 모듈 수준 함수가 있다면 그것을 a로 만드십시오 classmethod. 그러면 서브 클래스에서 사용할 수 있습니다.


내가 참조. 그러나 MyClass가 필요하지 않지만 다른 MyClass로 그룹화하면 여전히 어떤 방법으로 의미가있는 메소드를 분류하려는 경우 어떻습니까? 그래도 도우미 모듈로 옮길 수 있습니다.
swdev

원래 질문과 관련된 질문이 있습니다. 같은 시간에 대답 할 수 있습니까? 객체의 클래스 메소드를 호출하는 방법은 "더 나은", 또는 "더 관용적"입니다 : obj.cls_mthd(...)type(obj).cls_mthd(...)?
Alexey

63

팩토리 메소드 (대체 생성자)는 실제로 클래스 메소드의 전형적인 예입니다.

기본적으로 클래스 메소드는 클래스의 네임 스페이스에 자연스럽게 맞는 메소드를 원할 때마다 적합하지만 클래스의 특정 인스턴스와 연관되지 않습니다.

예를 들어, 우수한 유니 패스 모듈에서 :

현재 디렉토리

  • Path.cwd()
    • 실제 현재 디렉토리를 리턴하십시오. 예를 들어, Path("/tmp/my_temp_dir"). 이것은 클래스 메소드입니다.
  • .chdir()
    • self를 현재 디렉토리로 만드십시오.

현재 디렉토리가 전체 프로세스이므로 cwd메소드에는 연관시킬 특정 인스턴스가 없습니다. 그러나 cwd주어진 Path인스턴스 의 디렉토리로 변경하는 것은 실제로 인스턴스 방법이어야합니다.

흠 ... Path.cwd()실제로 Path인스턴스를 반환하는 것처럼 팩토리 메서드로 간주 될 수 있다고 생각합니다 ...


1
네 ... 공장 방법이 가장 먼저 떠 올랐습니다. getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
파이썬에서 그것들은 클래스 메소드가 아닌 정적 메소드입니다.
Jaykul

46

일반적인 방법은 디스패치의 세부 사항을 숨기는 데 유용 합니다. 객체의 클래스 또는 부모 클래스 중 하나에 의해 메서드가 구현 myobj.foo()되는지 걱정할 필요없이 입력 할 수 있습니다. 클래스 메소드는 이것과 정확히 유사하지만 클래스 객체를 대신 사용합니다. 고유 한 특수 버전이 필요하기 때문에 특별히 구현 되었는지 또는 부모 클래스가 호출을 처리 할 수 ​​있는지 여부 에 대해 걱정할 필요가 없습니다 .foo()myobjMyClass.foo()foo()MyClass

인스턴스가 존재할 때까지 인스턴스를 메소드 호출의 디스패치 지점으로 사용할 수 없기 때문에 실제 인스턴스를 작성하기 전에 설정 또는 계산을 수행 할 때 클래스 메소드가 필수적 입니다. SQLAlchemy 소스 코드에서 좋은 예를 볼 수 있습니다. dbapi()다음 링크에서 클래스 메소드를 살펴보십시오 .

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

당신은 것을 볼 수 있습니다 dbapi()그것을 실행해야하기 때문에 데이터베이스 백엔드 사용이 주문형 필요로하는 공급 업체 특정 데이터베이스 라이브러리를 가져올 수있는 방법, 수업 방법입니다 전에 생성 받기 시작 특정 데이터베이스 연결의 경우 - 그러나 그것이 수 없어 간단한 함수 또는 정적 함수입니다. 부모 클래스보다 서브 클래스에서 더 구체적으로 작성 해야하는 다른 지원 메소드를 호출 할 수 있기를 원하기 때문입니다. 그리고 함수 나 정적 클래스로 디스패치하면 초기화를 수행하는 클래스에 대한 지식을 "잊어"버립니다.


끊어진 링크, 조정하십시오
piertoni

28

최근에는 프로그래밍 방식으로 설정할 수있는 로깅 수준에 따라 다양한 양의 출력을 출력하는 매우 가벼운 로깅 클래스가 필요했습니다. 그러나 디버깅 메시지 또는 오류 또는 경고를 출력하려고 할 때마다 클래스를 인스턴스화하고 싶지 않았습니다. 그러나 나는 또한이 로깅 기능을 캡슐화하고 전역 선언없이 재사용 할 수있게하고 싶었습니다.

그래서 나는 @classmethod이것을 달성하기 위해 클래스 변수와 데코레이터를 사용했습니다.

간단한 로깅 클래스를 사용하여 다음을 수행 할 수 있습니다.

Logger._level = Logger.DEBUG

그런 다음 내 코드에서 많은 디버깅 정보를 뱉어 내려면 간단히 코딩해야했습니다.

Logger.debug( "this is some annoying message I only want to see while debugging" )

오류가 발생할 수 있습니다

Logger.error( "Wow, something really awful happened." )

"제작"환경에서 지정할 수 있습니다

Logger._level = Logger.ERROR

이제 오류 메시지 만 출력됩니다. 디버그 메시지가 인쇄되지 않습니다.

여기 내 수업이 있습니다.

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

그리고 약간 테스트하는 코드 :

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
이것은 클래스 메소드가 좋은 예제 인 것처럼 보이지 않습니다. 클래스 메소드를 모든 모듈 메소드로 만들고 클래스를 완전히 제거함으로써 덜 복잡한 방식으로 모두 수행 할 수 있기 때문에 클래스 메소드가 좋지 않습니다.
martineau

10
아, 더 중요한 것은, 파이썬은 매우 사용하기 쉬운 로깅 클래스를 제공한다는 것입니다. 덜 최적의 솔루션을 만드는 것보다 더 나쁘게 바퀴를 재발 명했습니다. 양손 때리기. 그러나 적어도 실제 사례를 제공합니다. 그래도 개념적인 수준에서 수업을 없애는 것에 동의하지 않습니다. 때로는 쉽게 재사용 할 수 있도록 코드를 캡슐화하려는 경우 로깅 방법이 재사용의 훌륭한 대상입니다.
Marvo

3
왜 정적 메서드를 사용하지 않습니까?
bdforbes


10

사용자가 내 웹 사이트에 로그인하면 사용자 이름과 비밀번호로 User () 객체가 인스턴스화됩니다.

로그인 할 사용자가없는 사용자 객체가 필요한 경우 (예 : 관리자 사용자가 다른 사용자 계정을 삭제하고 싶을 수 있으므로 해당 사용자를 인스턴스화하고 delete 메소드를 호출해야합니다) :

사용자 객체를 가져 오는 클래스 메소드가 있습니다.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

가장 명확한 대답은 AmanKow의 것입니다. 코드 구성 방법으로 요약됩니다. 모듈의 네임 스페이스에 싸여있는 모듈 레벨 함수로 모든 것을 작성할 수 있습니다.

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

위의 절차 적 코드는 잘 구성되어 있지 않습니다. 혼란스러워하는 3 개의 모듈 만 후에 볼 수 있듯이 각 방법은 무엇입니까? 자바와 같이 함수에 긴 설명 이름을 사용할 수 있지만 여전히 코드를 관리하기가 매우 빠릅니다.

객체 지향 방식은 코드를 관리 가능한 블록으로 분류하는 것입니다. 즉, 클래스 및 객체 및 함수는 객체 인스턴스 또는 클래스와 연관 될 수 있습니다.

클래스 함수를 사용하면 모듈 수준 함수와 비교하여 코드에서 다른 수준의 나누기를 얻을 수 있습니다. 따라서 클래스 내에서 관련 기능을 그룹화하여 해당 클래스에 할당 된 작업에보다 구체적으로 지정할 수 있습니다. 예를 들어 파일 유틸리티 클래스를 만들 수 있습니다.

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

이 방법은보다 유연하고 유지 관리가 가능하며, 기능을 함께 그룹화하고 각 기능이하는 일에 대해보다 명확합니다. 또한 이름 충돌을 방지합니다. 예를 들어 코드에서 사용하는 다른 가져온 모듈 (예 : 네트워크 복사)에 함수 복사본이있을 수 있으므로 전체 이름 FileUtil.copy ()를 사용하면 문제가 해결되고 복사 기능이 모두 나란히 사용할 수 있습니다.


1
f1 (), f2 () 등을 사용하려면 모듈 가져 오기 * 및 사용법 import *?에서 사용하지 않아야합니다.
Jblasco

@Jblasco 혼란을 제거하기 위해 import 문을 수정했습니다. 그렇습니다. 모듈을 가져올 경우 함수 앞에 모듈 이름을 붙여야합니다. 즉 수입 모듈-> module.f1 () 등
firephil

이 답변에 동의하지 않습니다. 파이썬은 자바가 아니다. 관리 할 수없는 코드 문제는 예제에서 의도적으로 잘못된 이름을 선택했기 때문에 발생합니다. 대신 FileUtil클래스 메소드를 file_util모듈의 함수로 복제하면 실제로 객체가 없을 때 OOP를 비교할 수 없으며 OOP를 남용하지 않습니다 (실제로 from file_util import FileUtil또는 다른 자세한 설명으로 끝나지 않기 때문에 바람직 합니다). import file_util대신 절차 코드에서 이름 충돌을 피할 수 있습니다 from file_util import ....
ForgottenUmbrella

7

솔직히? staticmethod 또는 classmethod를 사용한 적이 없습니다. 전역 함수 또는 인스턴스 메소드를 사용하여 수행 할 수없는 작업을 아직 보지 못했습니다.

파이썬이 Java처럼 개인 및 보호 멤버를 더 많이 사용하면 달라집니다. Java에서는 인스턴스의 개인 멤버에 액세스하여 작업을 수행 할 수있는 정적 메소드가 필요합니다. 파이썬에서는 거의 필요하지 않습니다.

일반적으로 정적 메소드와 클래스 메소드를 사용하는 사람들은 파이썬의 모듈 레벨 네임 스페이스를 더 잘 사용하는 것만 봅니다.


1
비공개 : _variable_name 및 보호 : __variable_name
Bradley Kreider

1
필수적인 테스트 중 하나는 unittest의 setUpClass와 tearDownClass입니다. 단위 테스트를 사용하고 있습니까? :)
dbn

7

호환 가능한 클래스와 함께 사용할 수있는 일반 클래스 메소드를 작성할 수 있습니다.

예를 들면 다음과 같습니다.

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

사용하지 않으면 @classmethodself 키워드로 수행 할 수 있지만 Class 인스턴스가 필요합니다.

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

나는 PHP로 일 했었고 최근에 나 자신에게 물었다.이 클래스 메소드는 어떻게 진행되고 있습니까? 파이썬 매뉴얼은 매우 기술적이고 단어가 짧기 때문에 그 기능을 이해하는 데 도움이되지 않습니다. 인터넷 검색 및 인터넷 검색을 통해 답변-> http://code.anjanesh.net/2007/12/python-classmethods.html을 찾았습니다 .

당신이 그것을 클릭 게으른 경우. 내 설명은 더 짧고 아래입니다. :)

PHP에서 (모두 PHP를 아는 것은 아니지만이 언어는 너무 간단하여 누구나 내가 말하는 것을 이해해야합니다) 우리는 다음과 같은 정적 변수를 가지고 있습니다 :


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

결과는 두 경우 모두 20입니다.

그러나 파이썬에서는 @classmethod 데코레이터를 추가 할 수 있으므로 각각 10과 20을 출력 할 수 있습니다. 예:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

똑똑하지 않습니까?


파이썬 예제에서 한 가지 잠재적 인 문제는이 예제가 B.setInnerVar(20)생략 된 경우, 10두 번째 echoInnerBar () 호출에서 오류가 발생하지 않고 두 번 인쇄 된다는 것 inner_var입니다.
martineau

5

클래스 메소드는 "의미 설탕"(이 용어가 널리 사용되는지 알지 못함) 또는 "의미 편의"를 제공합니다.

예 : 객체를 나타내는 클래스 세트가 있습니다. 당신은 클래스 메소드가 할 수 있습니다 all()또는 find()쓰기 User.all()또는 User.find(firstname='Guido'). 물론 모듈 수준의 기능을 사용하여 수행 할 수 있습니다 ...


물론 예에서는 클래스가 모든 인스턴스를 추적하고 생성 후 자동으로 수행되지 않는 인스턴스에 액세스 할 수 있다고 가정합니다.
martineau

1
"의미 설탕"은 "구문 설탕"과 잘 어울립니다.
XTL

3

무엇 단지 루비에서 오는 나를 때리는 것은, 소위이다 수업 방법 및 소위 인스턴스 방법은 경우에 자동으로 전달되는 첫 번째 매개 변수에 적용 의미 론적 의미를 단지 함수 기능을 하는 방법으로 호출 의 객체 (예 obj.meth()).

일반적으로 해당 객체 는 인스턴스 여야하지만 @classmethod 메소드 데코레이터 는 클래스를 전달하도록 규칙을 변경합니다. 인스턴스에서 클래스 메소드를 호출 할 수 있습니다 (단지 함수 일뿐입니다). 첫 번째 인수는 클래스입니다.

함수일 뿐이므로 주어진 범위 (즉, class정의) 에서 한 번만 선언 할 수 있습니다 . 따라서 Rubyist에게 놀랍게도 같은 이름의 클래스 메소드와 인스턴스 메소드를 가질 수 없습니다 .

이걸 고려하세요:

class Foo():
  def foo(x):
    print(x)

foo인스턴스를 호출 할 수 있습니다

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

그러나 수업이 아닙니다.

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

이제 추가하십시오 @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

인스턴스를 호출하면 이제 해당 클래스를 전달합니다.

Foo().foo()
__main__.Foo

수업을 부르는 것처럼 :

Foo.foo()
__main__.Foo

self인스턴스 메소드와 cls클래스 메소드에서 첫 번째 인수에 사용하도록 지시하는 것은 컨벤션뿐입니다 . 나는 단지 그것이 단지 논쟁이라는 것을 설명하기 위해 여기를 사용하지 않았다. 루비에서, selfA는 키워드.

루비와 대조 :

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

Python 클래스 메소드는 단지 꾸며진 함수 이며 동일한 기술을 사용하여 자신 만의 데코레이터작성할 수 있습니다 . 데코 레이팅 된 메소드는 실제 메소드를 랩핑합니다 ( @classmethod추가 클래스 인수를 전달하는 경우 ). 기본 방법은 아직 숨겨져 있지만 여전히 액세스 할 수 있습니다 .


각주 : 클래스와 인스턴스 메소드 사이의 이름 충돌로 호기심이 생겼습니다. 나는 파이썬 전문가와 거리가 멀며, 이것 중 하나라도 잘못되면 의견을 원합니다.


"함수가 객체의 메소드로 호출 될 때 자동으로 전달됩니다"-이것이 정확하지 않다고 생각합니다. AFICT, 파이썬에서 "자동으로 전달되는 것"은 없습니다. IMO, Python은 이런 의미에서 Ruby보다 훨씬 더 합리적입니다 (제외 super()). "마법"은 속성을 설정하거나 읽을 때 발생합니다. obj.meth(arg)루비와 달리 파이썬에서 의 호출 은 간단히 의미 (obj.meth)(arg)합니다. 아무 곳이나 조용히 전달되는 것은 없으며 obj.meth생성 된 함수보다 하나의 인수를 덜받는 호출 가능한 객체 일뿐입니다.
Alexey

obj.meth차례로, 단순히 의미 getattr(obj, "meth")합니다.
Alexey

다른 공용 언어를 사용하는 사람들을위한 답변을 제공하는 것이 좋습니다. 그러나이 대화에서 누락 된 한 가지 내용파이썬 설명 자라는 개념입니다 . 함수는 descriptors 이며, 함수가 클래스의 속성 일 때 첫 번째 인수 "automagically"가 인스턴스를 전달하는 방식입니다. 또한 클래스 메소드가 구현되는 방법이며, 순수 파이썬 구현이 링크 된 하우투에서 제공됩니다. 그것은 장식이 또한 사실은 일종의 보조입니다
juanpa.arrivillaga

2

이것은 흥미로운 주제입니다. 파이썬 클래스 메소드는 팩토리 (단일 인스턴스를 반환하는)가 아닌 단일 톤처럼 작동한다는 것입니다. 싱글 톤의 이유는 클래스에 대해 한 번만 생성되지만 모든 인스턴스가 공유하는 공통 오브젝트 (사전)가 있기 때문입니다.

여기에 이것을 설명하는 예가 있습니다. 모든 인스턴스에는 단일 사전에 대한 참조가 있습니다. 내가 이해할 때 이것은 공장 패턴이 아닙니다. 이것은 아마도 파이썬에게 매우 독창적입니다.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

나는 몇 번이나 같은 질문을했다. 그리고 여기에있는 사람들이 그것을 설명하려고 열심히 노력했지만 IMHO가 찾은 가장 좋은 대답 (그리고 가장 간단한 대답) 은 Python Documentation의 Class 메소드에 대한 설명 입니다.

정적 메소드에 대한 참조도 있습니다. 누군가 누군가 이미 인스턴스 메소드를 알고있는 경우 (이 가정),이 답변은 모두 함께하는 마지막 조각 일 수 있습니다 ...

이 주제에 대한보다 자세한 설명은 다음 문서에서도 찾을 수 있습니다 . 표준 유형 계층 구조 ( 인스턴스 메소드 섹션으로 스크롤 )


1

@classmethod외부 리소스에서 해당 클래스의 객체를 쉽게 인스턴스화하는 데 유용 할 수 있습니다. 다음을 고려하세요:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

그런 다음 다른 파일에서 :

from some_package import SomeClass

inst = SomeClass.from_settings()

inst.x에 액세스하면 설정 [ 'x']과 동일한 값이 제공됩니다.


0

물론 클래스는 인스턴스 집합을 정의합니다. 그리고 클래스의 메소드는 개별 인스턴스에서 작동합니다. 클래스 메소드 (및 변수)는 인스턴스 세트와 관련된 다른 정보를 매달 수있는 장소입니다.

예를 들어, 수업에서 학생 세트를 정의하는 경우 학생이 소속 될 수있는 학년 세트와 같은 것을 정의하는 클래스 변수 또는 방법을 원할 수 있습니다.

클래스 메소드를 사용하여 전체 세트 작업을위한 도구를 정의 할 수도 있습니다. 예를 들어 Student.all_of_em ()은 알려진 모든 학생을 반환 할 수 있습니다. 분명히 인스턴스 집합이 집합보다 많은 구조를 가지고 있다면 해당 구조에 대해 알 수있는 클래스 메서드를 제공 할 수 있습니다. Students.all_of_em (grade = 'juniors')

이와 같은 기술은 인스턴스 세트의 멤버를 클래스 변수에 기반한 데이터 구조에 저장하는 경향이 있습니다. 가비지 수집을 방해하지 않도록주의해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.