수업이나 사전을 사용해야합니까?


99

다음과 같이 필드 만 포함하고 메서드가없는 클래스가 있습니다.

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

이것은 쉽게 dict로 번역 될 수 있습니다. 이 클래스는 향후 추가를 위해 더 유연하며 __slots__. 그래서 대신 dict를 사용하면 이점이 있습니까? 딕셔너리는 클래스보다 빠를까요? 슬롯이있는 클래스보다 빠르나요?


2
저는 항상 데이터를 보관하기 위해 사전을 사용하는데, 이것은 저에게 사용 사례처럼 보입니다. 어떤 경우에는 클래스를 파생시키는 dict것이 합리적 일 수 있습니다. 깔끔한 장점 : 디버그 할 때 말하면 print(request)모든 상태 정보를 쉽게 볼 수 있습니다. 보다 고전적인 접근 방식을 사용하면 사용자 지정 __str__메서드 를 작성 해야합니다. 항상해야하는 경우에는 짜증이납니다.
흐름

서로 교환 할 수 있습니다. stackoverflow.com/questions/1305532/…
Oleksiy 2013 년

수업이 완전히 이해되고 다른 사람들에게 명확하다면 왜 안됩니까? 또한 예를 들어 공통 인터페이스를 사용하여 많은 클래스를 정의한다면 왜 안됩니까? 그러나 Python은 C ++와 같은 강력한 객체 지향 개념을 지원하지 않습니다.
MasterControlProgram 2017

3
@Ralf 파이썬이 지원하지 않는 OOP는 무엇입니까?
qwr

답변:


32

왜 이것을 사전으로 만드시겠습니까? 장점은 무엇입니까? 나중에 코드를 추가하려면 어떻게됩니까? __init__코드 는 어디에 있습니까 ?

클래스는 관련 데이터 (일반적으로 코드)를 묶는 데 사용됩니다.

사전은 키-값 관계를 저장하기위한 것으로, 일반적으로 키는 모두 동일한 유형이고 모든 값도 한 유형입니다. 경우에 따라 키 / 속성 이름이 모두 알려지지 않은 경우 데이터를 번들링하는 데 유용 할 수 있지만, 이는 종종 디자인에 문제가 있다는 신호입니다.

이 클래스를 유지하십시오.


클래스의 __init__메서드 대신 dict를 만드는 일종의 팩토리 메서드를 만들 것입니다 . 하지만 당신 말이 맞습니다 : 나는 함께 속한 것들과 분리 할 것입니다.
deamon

88
딕셔너리, 세트, ​​목록 및 튜플이 모두 관련 데이터를 번들로 제공합니다. 딕셔너리의 값이 동일한 데이터 유형이어야하거나 동일해야한다는 가정은 전혀 없습니다. 많은 목록과 집합에서 값은 동일한 유형을 갖지만 대부분 함께 열거하기를 좋아하기 때문입니다. 나는 실제로 데이터를 유지하기 위해 클래스를 널리 사용하는 것이 oop의 남용이라고 생각합니다. 직렬화 문제에 대해 생각하면 이유를 쉽게 알 수 있습니다.
흐름

4
OBJECT 지향 프로그래밍이고 클래스 지향이 아닌 이유가 있습니다. 우리는 객체를 다룹니다. 객체는 2 (3) 속성으로 특징 지어집니다. 1. 상태 (멤버) 2. 동작 (메서드) 및 3. 인스턴스는 여러 단어로 설명 될 수 있습니다. 따라서 클래스는 상태와 동작을 함께 묶기위한 것입니다.
friendzis jul.

14
가능한 경우 항상 더 간단한 데이터 구조를 기본으로해야하기 때문에 이것을 표시했습니다. 이 경우 사전은 의도 한 목적에 충분합니다. 문제 where would your __init__ code go?가 있습니다. 딕셔너리에서 init 메소드를 사용하지 않기 때문에 클래스 만 사용하도록 경험이 적은 개발자를 설득 할 수 있습니다 . 불합리한.
로이드 무어

1
@Ralf Foo 클래스는 int 및 string과 같은 단순한 유형입니다. 정수 유형 또는 정수 유형의 변수 foo에 값을 저장합니까? 미묘하지만 중요한 의미 차이. C와 같은 언어에서는 이러한 구분이 학계 밖에서는 그다지 적절하지 않습니다. 대부분의 OO 언어에는 클래스 변수 지원이있어 클래스와 객체 / 인스턴스 간의 구분이 매우 중요합니다. 데이터를 클래스 (모든 인스턴스에서 공유) 또는 특정 객체에 저장합니까? 물리지하지 마십시오
friendzis

44

클래스의 추가 메커니즘이 필요하지 않으면 사전을 사용하십시오. namedtuple하이브리드 접근 방식을 위해를 사용할 수도 있습니다 .

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

사전이 더 빠르지 않으면 놀랄 것이지만 여기서 성능 차이는 최소화됩니다.


15
"여기에 성능 차이가있을 것입니다 최소한의 사전이 아니었다면 내가 놀랄 것이라고하지만, 훨씬 더 빨리." 이것은 계산되지 않습니다. :)
mipadi

1
@mipadi : 사실입니다. 현재 수정 : p
Katriel

dict는 namedtuple보다 1.5 배 빠르며 슬롯이없는 클래스보다 두 배 빠릅니다. 이 답변에 대한 내 게시물을 확인하십시오.
alexpinho98 2013

@ alexpinho98 : 말씀하신 '포스트'를 찾으려고 노력했지만 찾을 수 없습니다! URL을 제공 할 수 있습니까? 감사!
Dan Oblinger

@ DanOblinger 나는 그가 아래에 그의 대답을 의미한다고 가정하고 있습니다.
Adam Lewis

37

파이썬의 클래스 그 밑에있는 dict입니다. 클래스 동작에 약간의 오버 헤드가 발생하지만 프로파일 러 없이는이를 알아 차릴 수 없습니다. 이 경우 다음과 같은 이유로 수업을 통해 혜택을받을 수 있습니다.

  • 모든 논리는 단일 기능에 있습니다.
  • 업데이트하기 쉽고 캡슐화 상태로 유지
  • 나중에 변경하는 경우 쉽게 인터페이스를 동일하게 유지할 수 있습니다.

모든 논리는 단일 기능에 존재하지 않습니다. 클래스는 공유 상태의 튜플이며 일반적으로 하나 이상의 메서드입니다. 클래스를 변경하면 해당 클래스의 인터페이스에 대한 보증이 제공되지 않습니다.
로이드 무어

25

나는 각각의 사용법이 너무 주관적이어서 그것에 대해 이해하지 못한다고 생각하기 때문에 나는 숫자에 충실 할 것입니다.

dict, new_style 클래스 및 슬롯이있는 new_style 클래스에서 변수를 만들고 변경하는 데 걸리는 시간을 비교했습니다.

다음은 테스트에 사용한 코드입니다 (약간 지저분하지만 작업을 수행합니다.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

그리고 여기에 출력이 있습니다 ...

만드는 중 ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

변수 변경 ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

따라서 변수를 저장하는 경우 속도가 필요하고 많은 계산을 수행 할 필요가 없습니다. dict를 사용하는 것이 좋습니다 (항상 메서드처럼 보이는 함수를 만들 수 있음). 그러나 정말로 클래스가 필요한 경우 항상 __ 슬롯 __을 사용하십시오 .

노트 :

나는 new_style 및 old_style 클래스 모두 에서 'Class'를 테스트했습니다 . old_style 클래스는 생성 속도가 빠르지 만 수정 속도가 느립니다 (단단한 루프에서 많은 클래스를 생성하는 경우에는 많지는 않지만 중요합니다 (팁 : 잘못하고 있음)).

또한 내 것이 오래되고 느리기 때문에 변수를 만들고 변경하는 시간은 컴퓨터에서 다를 수 있습니다. '실제'결과를 보려면 직접 테스트해야합니다.

편집하다:

나중에 namedtuple을 테스트했습니다. 수정할 수는 없지만 10000 개의 샘플 (또는 이와 유사한 것)을 만드는 데 1.4 초가 걸렸으므로 사전이 실제로 가장 빠릅니다.

난 경우 딕셔너리 기능을 변경 그것을 만들 때 키와 값을 포함하고 상기 딕셔너리를 포함하는 변수 대신 딕셔너리를 반환 나야 제공 0.65 대신 0.8 초가.

class Foo(dict):
    pass

생성은 슬롯이있는 클래스와 같으며 변수 변경이 가장 느리므로 (0.17 초) 이러한 클래스를 사용하지 마십시오 . dict (속도) 또는 객체에서 파생 된 클래스 ( '구문 캔디')로 이동


의 하위 클래스에 대한 숫자를보고 싶습니다 dict(재정의 된 메서드가없는 것 같 습니까?). 처음부터 작성된 새로운 스타일의 클래스와 동일한 방식으로 수행됩니까?
Benjamin Hodgson

12

@adw에 동의합니다. 나는 사전으로 "객체"(OO의 의미에서)를 표현하지 않을 것입니다. 사전은 이름 / 값 쌍을 집계합니다. 클래스는 객체를 나타냅니다. 나는 객체가 사전으로 표현되는 코드를 보았고 사물의 실제 모양이 무엇인지 명확하지 않습니다. 특정 이름 / 값이 없으면 어떻게됩니까? 클라이언트가 아무것도 넣지 못하도록 제한하는 것. 또는 모든 것을 꺼내려고 시도하는 것. 사물의 모양은 항상 명확하게 정의되어야합니다.

Python을 사용할 때 언어는 저자가 자신의 발을 쏠 수있는 다양한 방법을 허용하기 때문에 규율을 가지고 구축하는 것이 중요합니다.


9
SO에 대한 답변은 투표별로 정렬되며 동일한 투표 수의 답변은 무작위로 정렬됩니다. 그러니 "마지막 포스터"가 누구를 의미하는지 명확히 해주세요.
Mike DeSimone

4
그리고 필드 만 있고 기능이없는 것이 OO 의미에서 "객체"라는 것을 어떻게 알 수 있습니까?
Muhammad Alkarouri

1
내 리트머스 테스트는 "이 데이터의 구조가 고정되어 있습니까?"입니다. 그렇다면 객체를 사용하고 그렇지 않으면 dict를 사용하십시오. 이것에서 벗어나는 것은 혼란을 야기 할뿐입니다.
weberc2

해결하려는 문제의 맥락을 분석해야합니다. 그 풍경에 익숙해지면 사물은 상대적으로 분명해야합니다. 개인적으로 반환하는 것이 이름 / 값 쌍의 매핑이어야하지 않는 한 사전을 반환하는 것이 마지막 수단이어야한다고 생각합니다. 메서드에서 전달되고 전달 된 모든 것이 사전 일 뿐인 조잡한 코드를 너무 많이 보았습니다. 게으르다. 동적으로 입력되는 언어를 좋아하지만 코드가 명확하고 논리적 인 것도 좋아합니다. 모든 것을 사전에 밀어 넣는 것은 의미를 숨기는 편리함이 될 수 있습니다.
jaydel

5

요청과 관련된 모든 종류의 정보이므로 수업을 추천합니다. 사전을 사용하는 사람이라면 저장된 데이터가 본질적으로 훨씬 더 유사 할 것으로 예상합니다. 내가 따르는 경향이있는 지침은 전체 키-> 값 쌍 세트를 반복하고 무언가를 수행하려면 사전을 사용한다는 것입니다. 그렇지 않으면 데이터가 기본 키-> 값 매핑보다 훨씬 더 많은 구조를 가지므로 클래스가 더 나은 대안이 될 수 있습니다.

따라서 수업에 충실하십시오.


2
나는 완전히 동의하지 않는다. 반복 할 대상으로 사전 사용을 제한 할 이유가 없습니다. 매핑을 유지하기위한 것입니다.
Katriel

1
좀 더 자세히 읽어주세요. 나는 말했다 돌이 할 수 있습니다 하지 않습니다 . 나에게 사전을 사용한다는 것은 키와 값 사이에 큰 기능적 유사성이 있음을 의미합니다. 예를 들어, 나이가있는 이름. 클래스를 사용하면 다양한 '키'가 매우 다른 의미를 가진 값을 갖게됩니다. 예를 들어 PErson 수업을 들으십시오. 여기서 'name'은 문자열이고 'friends'는 목록, 사전 또는 기타 적절한 개체 일 수 있습니다. 이 클래스의 정상적인 사용의 일부로 이러한 모든 속성을 반복하지 않습니다.
Stigma

1
파이썬에서는 클래스와 딕셔너리의 구분이 불분명하다고 생각합니다. 전자가 후자를 사용하여 구현된다는 사실 때문입니다 ( '슬롯'은 견딜 수 없습니다). 나는 처음 언어를 배울 때 약간 혼란 스러웠다는 것을 알고 있습니다 (클래스는 객체이므로 신비한 메타 클래스의 인스턴스라는 사실과 함께).
martineau

4

달성하려는 모든 것이. obj.bla = 5대신에 같은 구문 사탕 obj['bla'] = 5이면, 특히 많이 반복해야하는 경우 martineaus 제안에서와 같이 일반 컨테이너 클래스를 사용하는 것이 좋습니다. 그럼에도 불구하고 코드는 상당히 부풀고 불필요하게 느립니다. 다음과 같이 간단하게 유지할 수 있습니다.

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

namedtuples 또는 클래스 로 전환하는 또 다른 이유는 __slots__메모리 사용량 일 수 있습니다. 사전은 목록 유형보다 훨씬 더 많은 메모리를 필요로하므로 고려할 사항이 될 수 있습니다.

어쨌든, 특정 경우에는 현재 구현에서 전환하려는 동기가없는 것 같습니다. 수백만 개의 이러한 개체를 유지하지 않는 것 같으므로 목록 파생 유형이 필요하지 않습니다. 그리고 실제로 내부에 일부 기능적 논리가 포함 __init__되어 있으므로 AttrDict.


types.SimpleNamespace(Python 3.3부터 사용 가능) 사용자 지정 AttrDict의 대안입니다.
Cristian Ciupitu

4

케이크를 먹고 먹을 수도 있습니다. 즉, 클래스 및 사전 인스턴스의 기능을 모두 제공하는 무언가를 만들 수 있습니다. ActiveState의 Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss 레시피와 그 방법에 대한 의견을 참조하세요 .

하위 클래스가 아닌 일반 클래스를 사용하기로 결정한 경우 Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ"ᴄʟᴀss 조리법 (Alex Martelli의)이 매우 유연하고 유용하다는 것을 발견했습니다. 당신이하고있는 것처럼 보입니다 (즉, 상대적으로 간단한 정보 수집기 생성). 클래스이기 때문에 메서드를 추가하여 기능을 쉽게 확장 할 수 있습니다.

마지막으로 클래스 멤버의 이름은 합법적 인 Python 식별자 여야하지만 사전 키는 그렇지 않습니다. 따라서 키는 해시 할 수있는 모든 것이 될 수 있기 때문에 사전이 더 큰 자유를 제공합니다 (문자열이 아니더라도).

최신 정보

object(하나가없는 __dict__) 이름이 지정된 클래스 (가없는 SimpleNamespace)가 typesPython 3.3 모듈 에 추가되었으며 또 다른 대안입니다.


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