파이썬에서 언제 메서드 대신 함수를 사용해야합니까?


118

Zen of Python은 작업을 수행 할 수있는 방법은 하나만 있어야한다고 말합니다.하지만 자주 함수를 사용할 때와 메서드를 사용할 때를 결정하는 문제에 직면합니다.

간단한 예인 ChessBoard 객체를 보겠습니다. 보드에서 사용 가능한 모든 합법적 인 King 이동을 얻을 수있는 방법이 필요하다고 가정 해 보겠습니다. ChessBoard.get_king_moves () 또는 get_king_moves (chess_board)를 작성합니까?

내가 살펴본 몇 가지 관련 질문은 다음과 같습니다.

내가 얻은 대답은 대체로 결정적이지 않았습니다.

파이썬은 왜 일부 기능 (예 : list.index ())에 메서드를 사용하고 다른 기능 (예 : len (list))에 대해 사용합니까?

주된 이유는 역사입니다. 함수는 유형 그룹에 대해 일반적이고 메서드가 전혀없는 객체 (예 : 튜플)에서도 작동하도록 의도 된 작업에 사용되었습니다. 파이썬의 기능적 기능을 사용할 때 (map (), apply () et al) 객체의 무정형 컬렉션에 쉽게 적용 할 수있는 함수를 갖는 것도 편리합니다.

실제로 len (), max (), min ()을 내장 함수로 구현하는 것은 실제로 각 유형의 메소드로 구현하는 것보다 코드가 적습니다. 개별 사례에 대해 고민 할 수는 있지만 Python의 일부이며 지금 그러한 근본적인 변경을하기에는 너무 늦었습니다. 대규모 코드 손상을 방지하려면 함수를 유지해야합니다.

흥미롭지 만, 위의 내용은 어떤 전략을 채택해야하는지에 대해 많이 언급하지 않습니다.

이것이 이유 중 하나입니다. 사용자 지정 메서드를 사용하면 개발자가 getLength (), length (), getlength () 등과 같은 다른 메서드 이름을 자유롭게 선택할 수 있습니다. Python은 공통 함수 len ()을 사용할 수 있도록 엄격한 이름 지정을 적용합니다.

약간 더 흥미 롭습니다. 내 생각은 함수가 어떤 의미에서 파이썬 버전의 인터페이스라는 것입니다.

마지막으로, 귀도에서 자신 :

능력 / 인터페이스에 대해 이야기하면서 "불량"특수 메서드 이름 중 일부를 생각하게되었습니다. Language Reference에서 "클래스는 특수한 이름으로 메서드를 정의하여 특수 구문 (예 : 산술 연산 또는 첨자 및 슬라이싱)에 의해 호출되는 특정 연산을 구현할 수 있습니다." 그러나 구문 지원보다는 내장 함수의 이점을 위해 제공되는 __len__or 와 같은 특수 이름을 가진 이러한 모든 메서드가 __unicode__있습니다. 그 때문에 아마도 인터페이스 기반의 파이썬에서, 이러한 방법은 ABC 방송에 정기적으로 명명 된 방법으로 전환 할 __len__될 것

class container:
  ...
  def len(self):
    raise NotImplemented

좀 더 생각해 보면 모든 구문 연산이 특정 ABC에 대해 일반적으로 명명 된 적절한 메서드를 호출 <object.lessthancomparable.lessthan하지 않는 이유를 알 수 없습니다. 예를 들어 " "는 아마도 " "(또는 아마도 " ")를 호출 할 것 입니다. 그래서 또 다른 이점은 파이썬을이 망가진 이름의 이상 함에서 벗어날 수 있다는 것 입니다.

흠. 동의하는지 잘 모르겠습니다 (그림 :-).

먼저 설명하고 싶은 "Python 근거"가 두 가지 있습니다.

우선 HCI 이유로 x.len () 대신 len (x)를 선택했습니다 ( def __len__()훨씬 나중에 나왔습니다). 실제로 두 가지 이유가 있습니다. 두 가지 모두 HCI입니다.

(a) 일부 연산의 경우 접두사 표기법이 접미사보다 더 잘 읽습니다. 접두사 (및 중위 어!) 연산은 수학자가 문제에 대해 생각하는 데 도움이되는 표기법을 좋아하는 수학에서 오랜 전통을 가지고 있습니다. 우리는 같은 공식을 다시있는 쉬운 비교 x*(a+b)x*a + x*b원시 OO 표기법을 사용하여 같은 일을하고의 어색함에 있습니다.

(b) 내가 무언가의 길이를 요구한다는 len(x)것을 안다 는 코드를 읽을 때 . 이것은 나에게 두 가지를 알려줍니다. 결과는 정수이고 인수는 일종의 컨테이너입니다. 반대로, 내가 읽을 때 x.len(), 나는 그것이 x인터페이스를 구현하거나 표준을 가진 클래스에서 상속받는 일종의 컨테이너 라는 것을 이미 알고 있어야합니다 len(). 매핑을 구현하지 않는 클래스에 get()또는 keys() 메서드가 있거나 파일이 아닌 무언가에 메서드 가있을 때 때때로 우리가 혼란스러워하는 것을 목격하십시오 write().

같은 말을 다른 방식으로 말하면 'len'을 내장 연산으로 봅니다. 나는 그것을 잃고 싶지 않습니다. 그 뜻인지 아닌지는 확실하지 않지만 'def len (self) : ...'는 확실히 평범한 방법으로 강등시키고 싶은 것처럼 들립니다. 나는 그것에 대해 강하게 -1입니다.

제가 설명하겠다고 약속 한 두 번째 파이썬 이론적 근거는 제가 __special__단순히 special. 나는 클래스가 재정의하기를 원하는 많은 연산, 일부 표준 (예 : __add__또는 __getitem__), 일부는 그렇게 표준 __reduce__적이 지 않은 연산 (예 : 피클 은 오랫동안 C 코드에서 전혀 지원 하지 않음) 을 예상했습니다 . 이러한 특수 작업이 일반 메서드 이름을 사용하는 것을 원하지 않았습니다. 기존 클래스 또는 모든 특수 메서드에 대한 백과 사전 메모리가없는 사용자가 작성한 클래스는 구현하려는 의도가 아닌 작업을 실수로 정의 할 수 있기 때문입니다. , 비참한 결과를 초래할 수 있습니다. Ivan Krstić는 내가이 모든 것을 쓴 후에 도착한 그의 메시지에서 이것을 더 간결하게 설명했습니다.

---Guido van Rossum (홈페이지 : http://www.python.org/~guido/ )

내 이해는 어떤 경우에는 접두사 표기법이 더 의미가 있다는 것입니다 (즉, Duck.quack이 언어 적 관점에서 quack (Duck)보다 더 의미가 있습니다). 다시 말하지만 함수는 "인터페이스"를 허용합니다.

이 경우 Guido의 첫 번째 요점만을 기반으로 get_king_moves를 구현하는 것으로 생각합니다. 그러나 비슷한 푸시 및 팝 메서드로 스택 및 큐 클래스를 구현하는 것과 관련하여 여전히 많은 열린 질문을 남깁니다. 함수 또는 메서드 여야합니까? (여기에서는 푸시-팝 인터페이스를 알리고 싶기 때문에 함수를 추측합니다.)

TLDR : 누군가 함수와 메서드를 사용할시기를 결정하는 전략이 무엇인지 설명 할 수 있습니까?


2
Meh, 나는 항상 그것을 완전히 임의적이라고 생각했습니다. 당신이 있는지 여부를 암시 "인터페이스"는 많은 차이가되지 않습니다에 대한 오리 입력은 허용 X.frob또는 X.__frob__무료 - 서 frob.
Cat Plus Plus

2
나는 대부분 당신에게 동의하지만 원칙적으로 당신의 대답은 Pythonic이 아닙니다. "모호함에도 불구하고 추측하려는 유혹을 거부하십시오." (물론 마감일이 변경 될 수 있지만 재미 / 자기 개선을 위해이 작업을 수행하고 있습니다.)
Ceasar Bautista 2011

이것은 내가 파이썬에 대해 좋아하지 않는 한 가지입니다. 나는 당신이 int와 같은 타이핑을 문자열에 강제로 캐스트한다면 그것을 메소드로 만드십시오. 그것을 괄호로 묶어야하고 시간이 많이 걸리는 것은 성가신 일입니다.
Matt

1
이것이 제가 파이썬을 좋아하지 않는 가장 중요한 이유입니다. 무언가를 얻고 싶을 때 함수 나 메소드를 찾아야하는지 알 수 없습니다. 또한 벡터 또는 데이터 프레임과 같은 새로운 데이터 유형이있는 추가 라이브러리를 사용할 때 더욱 복잡해집니다.
vonjd

1
"Zen of Python은 일을 할 수있는 한 가지 방법 만 있어야한다고 말합니다 ."
생성

답변:


84

내 일반적인 규칙은 이것이다- 작업이 개체에 대해 수행됩니까 아니면 개체에 의해 수행됩니까?

개체에 의해 수행되는 경우 구성원 작업이어야합니다. 그것이 다른 것들에도 적용될 수 있거나 객체에 대해 다른 것에 의해 수행된다면 그것은 함수 (또는 아마도 다른 것의 구성원) 여야합니다.

프로그래밍을 도입 할 때 자동차와 같은 실제 객체의 관점에서 객체를 설명하는 것은 전통적입니다 (구현이 잘못되었지만). 당신은 오리를 언급 했으니, 그것으로 가자.

class duck: 
    def __init__(self):pass
    def eat(self, o): pass 
    def crap(self) : pass
    def die(self)
    ....

"객체는 실제적인 것"비유의 맥락에서 객체가 할 수있는 모든 것에 대한 클래스 메소드를 추가하는 것은 "올 바릅니다". 그래서 내가 오리를 죽이고 싶다고 말하면 오리에 .kill ()을 추가합니까? 아니 ... 내가 아는 한 동물은 자살하지 않는다. 따라서 오리를 죽이고 싶다면 이렇게해야합니다.

def kill(o):
    if isinstance(o, duck):
        o.die()
    elif isinstance(o, dog):
        print "WHY????"
        o.die()
    elif isinstance(o, nyancat):
        raise Exception("NYAN "*9001)
    else:
       print "can't kill it."

이 비유에서 벗어나 메서드와 클래스를 사용하는 이유는 무엇입니까? 데이터를 포함하고 향후 재사용 및 확장이 가능한 방식으로 코드를 구조화하기를 원하기 때문입니다. 이것은 우리에게 OO 디자인에 매우 중요한 캡슐화의 개념을 가져옵니다.

캡슐화 원칙은 실제로 이것이 내려지는 것입니다. 디자이너는 구현 및 클래스 내부에 대한 모든 것을 숨겨야합니다. 이는 사용자 나 다른 개발자가 반드시 액세스 할 필요는 없습니다. 우리는 클래스의 인스턴스를 다루기 때문에 " 이 인스턴스에서 중요한 작업"으로 축소됩니다 . 작업이 인스턴스와 관련이없는 경우 멤버 함수가 아니어야합니다.

TL; DR : @Bryan이 말한 것. 인스턴스에서 작동하고 클래스 인스턴스 내부의 데이터에 액세스해야하는 경우 멤버 함수 여야합니다.


요컨대, 멤버가 아닌 함수는 불변 객체에서 작동하고, 가변 객체는 멤버 함수를 사용합니까? (아니면 이것은 너무 엄격한 일반화입니까? 이것은 불변 유형에 상태가 없기 때문에 특정 작동합니다.)
Ceasar Bautista 2011

1
엄격한 OOP 관점에서 나는 그것이 공정하다고 생각합니다. Python은 공용 및 개인용 변수 (__로 시작하는 이름의 변수)를 모두 가지고 있고 Java와 달리 보장 된 액세스 보호를 제공하지 않기 때문에 허용 언어에 대해 토론하고 있기 때문에 절대 값이 없습니다. 그러나 Java와 같이 덜 관대 한 언어에서는 getFoo () ad setFoo () 함수가 표준이므로 불변성이 절대적이지 않습니다. 클라이언트 코드는 구성원에게 할당 할 수 없습니다.
arrdem

1
@Ceasar 사실이 아닙니다. 불변 객체에는 상태가 있습니다. 그렇지 않으면 정수와 다른 정수를 구분할 수 없습니다. 불변 객체는 상태를 변경 하지 않습니다 . 일반적으로 이것은 모든 주가 공개되는 것이 훨씬 덜 문제가됩니다. 그리고이 설정에서는 함수를 사용하여 불변의 모든 공용 객체를 의미있게 조작하는 것이 훨씬 쉽습니다. 메소드가되는 "특권"이 없습니다.
Ben

1
@CeasarBautista 예, Ben은 요점이 있습니다. 코드 설계에는 1) 설계되지 않음, 2) OOP 및 3) 기능에 대한 세 가지 "주요"학교가 있습니다. 기능적 스타일에서는 상태가 전혀 없습니다. 이것이 내가 설계 한 대부분의 파이썬 코드를 보는 방식이며, 입력을 받아 부작용이 거의없는 출력을 생성합니다. OOP 의 요점 은 모든 것이 상태를 가지고 있다는 것입니다. 클래스는 상태에 대한 컨테이너이므로 "멤버"함수는 호출 될 때마다 클래스에 정의 된 상태를로드하는 상태 종속적이고 부작용 기반 코드입니다. 파이썬은 기능적인 경향이 있으므로 비 구성원 코드를 선호합니다.
arrdem

1
먹기 (자기), 쓰레기 (자기), 죽기 (자기). 하하
와피 티

27

다음과 같은 경우 수업을 사용하세요.

1) 구현 세부 사항에서 호출 코드를 분리 합니다 . 추상화캡슐화 를 활용 합니다 .

2) 다형성 을 활용하여 다른 물체를 대체하고 싶을 때 .

3) 유사한 객체에 대해 코드를 재사용하려는 경우- 상속 을 활용 합니다.

다양한 객체 유형에서 의미가있는 호출에 함수를 사용합니다. 예를 들어 내장 lenrepr 함수는 여러 유형의 객체에 적용됩니다.

즉, 선택은 때때로 취향의 문제로 내려갑니다. 일반적인 통화에서 가장 편리하고 읽기 쉬운 항목을 고려하십시오. 예를 들어, 어떤 더 나은 것 (x.sin()**2 + y.cos()**2).sqrt()sqrt(sin(x)**2 + cos(y)**2)?


8

다음은 간단한 경험 법칙입니다. 코드가 객체의 단일 인스턴스에 대해 작동하는 경우 메서드를 사용합니다. 더 좋은 점은 함수로 작성해야하는 설득력있는 이유가없는 한 메서드를 사용하는 것입니다.

특정 예에서 다음과 같이 보이기를 원합니다.

chessboard = Chessboard()
...
chessboard.get_king_moves()

너무 생각하지 마십시오. "이것을 방법으로 만드는 것은 이치에 맞지 않습니다"라고 스스로에게 말할 때까지 항상 방법을 사용하십시오.이 경우 기능을 만들 수 있습니다.


2
함수보다 메소드를 기본값으로 사용하는 이유를 설명해 주시겠습니까? (그리고 스택 및 큐 / 팝 및 푸시 방법의 경우에도이 규칙이 여전히 의미가 있는지 설명 할 수 있습니까?)
Ceasar Bautista 2011

11
그 규칙은 말이되지 않습니다. 표준 라이브러리 자체는 클래스와 함수를 사용하는시기에 대한 가이드가 될 수 있습니다. heapqmath 는 일반적인 파이썬 객체 (부동 및 목록)에서 작동 하고 임의 모듈 처럼 복잡한 상태를 유지할 필요가 없기 때문에 함수 입니다.
Raymond Hettinger

1
표준 라이브러리가 규칙을 위반하기 때문에 규칙이 의미가 없다는 말입니까? 나는 그 결론이 말이되지 않는다고 생각합니다. 우선 경험의 규칙은 그저 절대적인 규칙이 아닙니다. 또한, 표준 라이브러리의 대부분은 않습니다 엄지 손가락의 규칙을 따릅니다. OP는 몇 가지 간단한 지침을 찾고 있었으며 내 조언은 막 시작하는 사람에게 완벽하다고 생각합니다. 그러나 나는 당신의 관점에 감사드립니다.
Bryan Oakley 2011

STL에는 그렇게해야 할 몇 가지 좋은 이유가 있습니다. 수학 및 공동의 경우에는 상태가 없기 때문에 클래스를 갖는 것은 분명히 쓸모가 없습니다 (다른 언어에서는 정적 함수 만있는 클래스입니다). 여러 다른 컨테이너에서 작동하는 것들의 len()경우 디자인이 합리적이라고 주장 할 수도 있지만, 개인적으로 함수도 나쁘지 않을 것이라고 생각할 수 있습니다 len(). 정수 (그러나 backcomp의 모든 추가 문제와 함께 나는 파이썬에 대해서도 그것을 옹호하지 않을 것입니다)
Voo

이 답변은 완전히 임의대로 -1 : 당신은 기본적으로 X에 의해 더 나은 Y보다 것을 증명하려고하는 가정 X가 Y를보다 낫다는 것을
gented

7

나는 보통 사람과 같은 물건을 생각합니다.

속성 은 사람의 이름, 키, 신발 크기 등입니다.

방법기능 은 사람이 수행 할 수있는 작업입니다.

이 특정 사람에게 고유 한 것을 요구하지 않고 (그리고이 특정 사람에 대해 아무것도 변경하지 않고) 모든 사람이 작업을 수행 할 수 있다면 그것은 기능입니다. 이며 그렇게 작성되어야합니다.

수술이 그 사람에게 작용하거나 (예 : 식사, 걷기 등) 참여하기 위해이 사람에게 고유 한 것이 필요 하다면 (예 : 춤, 책 쓰기 등) 방법 이되어야합니다 .

물론 이것을 작업중인 특정 개체로 변환하는 것이 항상 사소한 것은 아니지만 생각하기에 좋은 방법이라고 생각합니다.


그것이 있어야 그 논리에 의해 있도록하지만, 오래된 사람의 높이를 측정 할 수있다 height(person), 없다 person.height?
endolith 2014 년

@endolith 물론입니다.하지만 키를 검색하기 위해 멋진 작업을 수행 할 필요가 없기 때문에 높이가 속성으로 더 낫다고 말하고 싶습니다. 숫자를 검색하는 함수를 작성하는 것은 건너 뛰기위한 불필요한 후프처럼 보입니다.
Thriveth

@endolith 반면에 그 사람이 키가 커지고 키가 변하는 아이라면 함수가 아닌 메서드가 그것을 처리하도록하는 것이 분명합니다.
Thriveth

이것은 사실이 아닙니다. 만약 당신이 가지고 있다면 are_married(person1, person2)? 이 쿼리는 매우 일반적이므로 메서드가 아니라 함수 여야합니다.
Pithikos 2014 년

@Pithikos 물론입니다. 그러나 그것은 하나의 개체 (사람)에 대해 수행되는 속성이나 동작 이상의 것을 포함하지만 오히려 두 개체 간의 관계를 포함합니다.
Thriveth 2014 년

4

일반적으로 나는 일부 기능의 논리적 세트를 구현하는 클래스를 사용하는 일을 그렇게 내 프로그램의 나머지 부분에서 나는 추론 할 수 있다는 것은 구현을 구성하는 모든 작은 문제에 대해 걱정할 필요 없습니다.

의 핵심 추상화의 일부 뭐든지 "당신이 함께 할 수있는 "보통 방법이어야한다. 이것은 일반적으로 변경할 수있는 모든 것을 포함하고 일을 내부 데이터 상태는 일반적으로 당신이 수행 할 수있는 작업 "의 논리적 인 생각의 일부 개인 간주되지 않기 때문에, 일을 입니다.

더 높은 수준의 작업에 올 때, 특히 여러 작업을 포함하는 경우 가지 경우 , 내부에 대한 특별한 액세스없이 사물 의 공개 추상화로 구축 할 수 있다면 일반적으로 함수로 가장 자연스럽게 표현됩니다. 다른 개체의 메서드). 이것은 내 작업 방식 의 내부를 완전히 다시 작성하기로 결정할 때 (인터페이스를 변경하지 않고) 재 작성할 작은 핵심 메서드 집합이 있고, 그런 다음 해당 메서드와 관련하여 작성된 모든 외부 함수가 있다는 큰 이점이 있습니다. 그냥 작동합니다. 클래스 X와 관련된 모든 작업이 클래스 X의 메서드라고 주장하면 클래스가 지나치게 복잡해집니다.

그래도 작성중인 코드에 따라 다릅니다. 일부 프로그램의 경우 상호 작용이 프로그램의 동작을 유발하는 개체 모음으로 모델링합니다. 여기서 가장 중요한 기능은 단일 객체에 밀접하게 결합되어 있으므로 유틸리티 함수가 분산되어있는 메서드에서 구현됩니다. 다른 프로그램의 경우 가장 중요한 것은 데이터를 조작하는 함수 집합이며 클래스는 함수에 의해 조작되는 자연스러운 "덕 유형"을 구현하는 데만 사용됩니다.


0

"모호함에 직면하여 추측하려는 유혹을 거부하라"고 말할 수 있습니다.

그러나 그것은 추측조차 아닙니다. 두 접근법의 결과는 문제를 해결한다는 점에서 동일하다는 것을 절대적으로 확신합니다.

목표를 달성하기 위해 여러 가지 방법을 갖는 것은 좋은 일이라고 생각합니다. 다른 사용자가 이미했던 것처럼 언어 측면에서 "맛이 더 좋은"/ 더 직관적 인 느낌을 사용하라고 겸손하게 말씀 드리고 싶습니다 .

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