파이썬 내부 클래스의 목적은 무엇입니까?


98

파이썬의 내부 / 중첩 클래스가 나를 혼란스럽게합니다. 그들 없이는 달성 할 수없는 것이 있습니까? 그렇다면 그게 뭔데?

답변:


85

http://www.geekinterview.com/question_details/64739 에서 인용 :

내부 클래스의 장점 :

  • 클래스의 논리적 그룹화 : 클래스가 다른 하나의 클래스에만 유용하면 해당 클래스에 포함하고 두 클래스를 함께 유지하는 것이 논리적입니다. 이러한 "도우미 클래스"를 중첩하면 패키지가 더욱 간소화됩니다.
  • 캡슐화 증가 : B가 그렇지 않으면 private으로 선언 될 A의 멤버에 액세스해야하는 두 개의 최상위 클래스 A와 B를 고려하십시오. 클래스 AA 내에서 클래스 B를 숨기면 멤버는 private으로 선언되고 B는 액세스 할 수 있습니다. 또한 B 자체는 외부 세계에서 숨길 수 있습니다.
  • 더 읽기 쉽고 관리하기 쉬운 코드 : 최상위 클래스 내에 작은 클래스를 중첩하면 코드가 사용되는 위치에 더 가깝게 배치됩니다.

가장 큰 장점은 조직입니다. 내부 클래스로 수행 할 수있는 모든 것은 그것들 없이도 수행 할 수 있습니다.


50
물론 캡슐화 인수는 Python에 적용되지 않습니다.
bobince

30
첫 번째 요점은 Python에도 적용되지 않습니다. 하나의 모듈 파일에서 원하는만큼 많은 클래스를 정의 할 수 있으므로 함께 유지하면 패키지 구성에도 영향을주지 않습니다. 마지막 요점은 매우 주관적이며 타당하다고 생각하지 않습니다. 요컨대,이 답변에서 Python의 내부 클래스 사용을 지원하는 인수를 찾지 못했습니다.
Chris Arndt

17
그럼에도 불구하고 이것이 프로그래밍에서 내부 클래스가 사용되는 이유입니다. 당신은 단지 경쟁적인 대답을 쏘려고하는 것뿐입니다. 이 친구가 여기에 준 대답은 확실합니다.
Inversus 2014

16
@Inversus : 동의하지 않습니다. 이것은 대답이 아니라 다른 언어 (즉, Java) 에 대한 다른 사람의 대답에서 확장 된 인용문입니다 . 반대 투표를했고 다른 사람들도 똑같이하기를 바랍니다.
Kevin

4
나는이 답변에 동의하고 이의에 동의하지 않습니다. 중첩 된 클래스 Java의 내부 클래스 가 아니지만 유용합니다. 중첩 클래스의 목적은 조직입니다. 효과적으로, 한 클래스를 다른 클래스의 네임 스페이스 아래에 두는 것입니다. 그렇게하는 것이 논리적 인 의미가 있습니다 때, 이것은 이다 파이썬 : "네임 스페이스는 좋은 아이디어가 경적을 울리고 하나 - 좀 더 사람들의하자!". 예를 들어 예외를 발생 DataLoader시킬 수 있는 클래스를 고려하십시오 CacheMiss. 기본 클래스 아래에 예외를 중첩하면 DataLoader.CacheMiss가져올 수 DataLoader있지만 여전히 예외를 사용할 수 있습니다 .
cbarrick

50

그들 없이는 달성 할 수없는 것이 있습니까?

아니요. 일반적으로 최상위 수준에서 클래스를 정의한 다음 이에 대한 참조를 외부 클래스에 복사하는 것과 절대적으로 동일합니다.

중첩 클래스가 '허용'되는 특별한 이유가 있다고 생각하지 않습니다. 명시 적으로 '허용'하는 것도 특별한 의미가 없습니다.

외부 / 소유자 객체의 수명주기 내에 존재하고 항상 외부 클래스의 인스턴스에 대한 참조를 갖고있는 클래스를 찾고 있다면 (자바처럼 내부 클래스) Python의 중첩 클래스는 그런 것이 아닙니다. 하지만 다음 과 같이 해킹 할 수 있습니다 .

import weakref, new

class innerclass(object):
    """Descriptor for making inner classes.

    Adds a property 'owner' to the inner class, pointing to the outer
    owner instance.
    """

    # Use a weakref dict to memoise previous results so that
    # instance.Inner() always returns the same inner classobj.
    #
    def __init__(self, inner):
        self.inner= inner
        self.instances= weakref.WeakKeyDictionary()

    # Not thread-safe - consider adding a lock.
    #
    def __get__(self, instance, _):
        if instance is None:
            return self.inner
        if instance not in self.instances:
            self.instances[instance]= new.classobj(
                self.inner.__name__, (self.inner,), {'owner': instance}
            )
        return self.instances[instance]


# Using an inner class
#
class Outer(object):
    @innerclass
    class Inner(object):
        def __repr__(self):
            return '<%s.%s inner object of %r>' % (
                self.owner.__class__.__name__,
                self.__class__.__name__,
                self.owner
            )

>>> o1= Outer()
>>> o2= Outer()
>>> i1= o1.Inner()
>>> i1
<Outer.Inner inner object of <__main__.Outer object at 0x7fb2cd62de90>>
>>> isinstance(i1, Outer.Inner)
True
>>> isinstance(i1, o1.Inner)
True
>>> isinstance(i1, o2.Inner)
False

(이것은 Python 2.6 및 3.0에서 새로 추가 된 클래스 데코레이터를 사용합니다. 그렇지 않으면 클래스 정의 뒤에 "Inner = innerclass (Inner)"라고 말해야합니다.)


5
에 대한 호출하는 사용 사례 일반적으로 내부의 내부 클래스를 정의하여 파이썬에서 해결 될 수 있습니다 (인스턴스 외부 클래스의 인스턴스와 관계를 가지고 즉, 자바 - 억양 내부 클래스) 방법 들이 표시됩니다 - 외부 클래스의이 외부의는 self필요한 추가 작업없이 (당신이 일반적으로 내부의를 둘 것입니다 다른 식별자를 사용 self, 등이 innerself, 그 통해 외부 인스턴스에 액세스 할 수 있습니다).
Evgeni Sergeev

WeakKeyDictionary이 예제에서 a 를 사용 하면 값이 해당 owner속성을 통해 각 키를 강력하게 참조하기 때문에 실제로 키가 가비지 수집되는 것을 허용하지 않습니다 .
Kritzefitz

36

이것을 이해하기 위해 머리를 감쌀 필요가 있습니다. 대부분의 언어에서 클래스 정의는 컴파일러에 대한 지시문입니다. 즉, 프로그램이 실행되기 전에 클래스가 생성됩니다. 파이썬에서는 모든 문이 실행 가능합니다. 이는 다음과 같은 진술을 의미합니다.

class foo(object):
    pass

다음과 같이 런타임에 실행되는 명령문입니다.

x = y + z

즉, 다른 클래스 내에서 클래스를 만들 수있을뿐만 아니라 원하는 곳 어디에서나 클래스를 만들 수 있습니다. 이 코드를 고려하십시오.

def foo():
    class bar(object):
        ...
    z = bar()

따라서 "내부 클래스"라는 개념은 실제로 언어 구조가 아닙니다. 그것은 프로그래머 구조입니다. Guido는 이것이 어떻게 여기에 왔는지에 대한 아주 좋은 요약을 가지고 있습니다 . 그러나 본질적으로 기본 아이디어는 이것이 언어의 문법을 단순화한다는 것입니다.


16

클래스 내 중첩 클래스 :

  • 중첩 된 클래스는 클래스 정의를 부풀리기 때문에 무슨 일이 일어나고 있는지보기가 더 어려워집니다.

  • 중첩 클래스는 테스트를 더 어렵게 만드는 결합을 만들 수 있습니다.

  • Python에서는 Java와 달리 파일 / 모듈에 둘 이상의 클래스를 넣을 수 있으므로 클래스는 여전히 최상위 클래스에 가깝게 유지되며 다른 클래스가 없어야 함을 나타 내기 위해 "_"접두사가 붙은 클래스 이름을 가질 수도 있습니다. 그것을 사용합니다.

중첩 된 클래스가 유용함을 입증 할 수있는 곳은 함수 내에 있습니다.

def some_func(a, b, c):
   class SomeClass(a):
      def some_method(self):
         return b
   SomeClass.__doc__ = c
   return SomeClass

이 클래스는 함수에서 값을 캡처하여 C ++에서 템플릿 메타 프로그래밍과 같은 클래스를 동적으로 생성 할 수 있습니다.


7

중첩 클래스에 대한 인수를 이해하지만 경우에 따라 사용하는 경우가 있습니다. 이중 연결 목록 클래스를 만들고 노드를 유지 관리하기 위해 노드 클래스를 만들어야한다고 상상해보십시오. 두 가지 선택이 있습니다. DoublyLinkedList 클래스 내부에 Node 클래스를 만들거나 DoublyLinkedList 클래스 외부에 Node 클래스를 만듭니다. Node 클래스는 DoublyLinkedList 클래스 내에서만 의미가 있기 때문에이 경우 첫 번째 선택을 선호합니다. 숨김 / 캡슐화 이점은 없지만 Node 클래스가 DoublyLinkedList 클래스의 일부라고 말할 수있는 그룹화 이점이 있습니다.


5
동일한 Node클래스가 사용자가 만들 수도있는 다른 유형의 연결 목록 클래스에 유용하지 않다고 가정하면 사실 입니다.이 경우에는 아마도 외부에 있어야합니다.
Acumenus

그것을 넣는 또 다른 방법 : Node의 네임 스페이스 아래에 있으며 DoublyLinkedList논리적으로 의미가 있습니다. 이것은 이다 파이썬 : "네임 스페이스는 좋은 아이디어가 경적을 울리고 하나 -하자 많은 사람들의 해!"
cbarrick

@cbarrick : "더 많이"를 수행하면 중첩에 대해 아무 의미가 없습니다.
Ethan Furman

4

그들 없이는 달성 할 수없는 것이 있습니까? 그렇다면 그게 뭔데?

뭔가가 쉽게없이 할 수 없습니다 : 관련 클래스의 상속 .

다음은 관련 클래스 A와 함께 미니멀 한 예입니다 B.

class A(object):
    class B(object):
        def __init__(self, parent):
            self.parent = parent

    def make_B(self):
        return self.B(self)


class AA(A):  # Inheritance
    class B(A.B):  # Inheritance, same class name
        pass

이 코드는 매우 합리적이고 예측 가능한 동작으로 이어집니다.

>>> type(A().make_B())
<class '__main__.A.B'>
>>> type(A().make_B().parent)
<class '__main__.A'>
>>> type(AA().make_B())
<class '__main__.AA.B'>
>>> type(AA().make_B().parent)
<class '__main__.AA'>

경우 B최상위 클래스했다, 당신은 쓸 수있는 self.B()방법에 make_B단순히 쓸 것 B(), 따라서 잃을 동적 바인딩 적절한 클래스를.

이 구성에서는 class A본문에있는 class 를 참조해서는 안됩니다 B. 이것이 parent클래스에 속성 을 도입 한 동기입니다 B.

물론이 동적 바인딩은 지루하고 오류가 발생하기 쉬운 클래스 계측 비용으로 내부 클래스 없이 다시 만들 수 있습니다 .


1

내가 이것을 사용하는 주요 사용 사례는 작은 모듈의 확산 을 방지 하고 별도의 모듈이 필요하지 않을 때 네임 스페이스 오염을 방지하는 것입니다. 기존 클래스를 확장하지만 해당 기존 클래스가 항상 연결되어야하는 다른 하위 클래스를 참조해야하는 경우. 예를 들어, utils.py여러 도우미 클래스 가 포함 된 모듈이 있는데 반드시 함께 연결되지는 않지만 일부 도우미 클래스에 대한 연결을 강화하고 싶습니다 . 예를 들어 https://stackoverflow.com/a/8274307/2718295를 구현할 때

: utils.py:

import json, decimal

class Helper1(object):
    pass

class Helper2(object):
    pass

# Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats
class DecimalJSONEncoder(json.JSONEncoder):

    class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched
        def __init__(self, obj):
            self._obj = obj
        def __repr__(self):
            return '{:f}'.format(self._obj)

    def default(self, obj): # override JSONEncoder.default
        if isinstance(obj, decimal.Decimal):
            return self._repr_decimal(obj)
        # else
        super(self.__class__, self).default(obj)
        # could also have inherited from object and used return json.JSONEncoder.default(self, obj) 

그러면 다음을 수행 할 수 있습니다.

>>> from utils import DecimalJSONEncoder
>>> import json, decimal
>>> json.dumps({'key1': decimal.Decimal('1.12345678901234'), 
... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder)
{"key2": "key2_value", "key_1": 1.12345678901234}

물론 우리는 상속을 json.JSONEnocder모두 피하고 default ()를 재정의 할 수 있습니다 .

:

import decimal, json

class Helper1(object):
    pass

def json_encoder_decimal(obj):
    class _repr_decimal(float):
        ...

    if isinstance(obj, decimal.Decimal):
        return _repr_decimal(obj)

    return json.JSONEncoder(obj)


>>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder)
'{"key1": 1.12345678901234}'

그러나 때로는 관례를 utils위해 확장 성을 위해 클래스로 구성 되기를 원합니다 .

여기 또 다른 사용 사례가 있습니다 copy.

class OuterClass(object):

    class DTemplate(dict):
        def __init__(self):
            self.update({'key1': [1,2,3],
                'key2': {'subkey': [4,5,6]})


    def __init__(self):
        self.outerclass_dict = {
            'outerkey1': self.DTemplate(),
            'outerkey2': self.DTemplate()}



obj = OuterClass()
obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4)
assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6]

@staticmethod팩토리 기능에 사용 하는 데코레이터보다이 패턴을 선호 합니다.


1

1. 기능적으로 동일한 두 가지 방법

이전에 표시된 두 가지 방법은 기능적으로 동일합니다. 그러나 약간의 미묘한 차이가 있으며 서로를 선택하고 싶은 상황이 있습니다.

방법 1 : 중첩 된 클래스 정의
(= "중첩 된 클래스")

class MyOuter1:
    class Inner:
        def show(self, msg):
            print(msg)

방법 2 : 모듈 수준 내부 클래스가 외부 클래스에 연결됨
(= "참조 된 내부 클래스")

class _InnerClass:
    def show(self, msg):
        print(msg)

class MyOuter2:
    Inner = _InnerClass

밑줄은 PEP8 을 따르는 데 사용됩니다. "내부 인터페이스 (패키지, 모듈, 클래스, 함수, 속성 또는 기타 이름)는 단일 선행 밑줄로 시작해야합니다."

2. 유사점

아래 코드 스 니펫은 "중첩 된 클래스"와 "참조 된 내부 클래스"의 기능적 유사성을 보여줍니다. 내부 클래스 인스턴스의 유형을 확인하는 코드에서 동일한 방식으로 작동합니다. 물론이 (가), 말 m.inner.anymethod()과 유사하게 동작 할 m1m2

m1 = MyOuter1()
m2 = MyOuter2()

innercls1 = getattr(m1, 'Inner', None)
innercls2 = getattr(m2, 'Inner', None)

isinstance(innercls1(), MyOuter1.Inner)
# True

isinstance(innercls2(), MyOuter2.Inner)
# True

type(innercls1()) == mypackage.outer1.MyOuter1.Inner
# True (when part of mypackage)

type(innercls2()) == mypackage.outer2.MyOuter2.Inner
# True (when part of mypackage)

3. 차이점

"중첩 된 클래스"와 "참조 된 내부 클래스"의 차이점은 다음과 같습니다. 그것들은 크지는 않지만 때로는 이것들을 기반으로 하나 또는 다른 것을 선택하고 싶을 때가 있습니다.

3.1 코드 캡슐화

"중첩 된 클래스"를 사용하면 "참조 된 내부 클래스"보다 코드를 더 잘 캡슐화 할 수 있습니다. 모듈 네임 스페이스의 클래스는 전역 변수입니다. 중첩 클래스의 목적은 모듈의 혼란을 줄이고 내부 클래스를 외부 클래스에 넣는 것입니다.

아무도 *를 사용하지 않지만 from packagename import *코드 완성 / 인텔리 센스와 함께 IDE를 사용할 때 모듈 수준 변수의 양이 적 으면 좋을 수 있습니다.

* 맞죠?

3.2 코드의 가독성

Django 문서 는 모델 메타 데이터에 내부 클래스 Meta 를 사용하도록 지시합니다 . 프레임 워크 사용자에게 class Foo(models.Model)with inner 를 작성하도록 지시하는 것이 좀 더 명확합니다 * class Meta.

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

대신 "write a class _Meta, then write a class Foo(models.Model)with Meta = _Meta";

class _Meta:
    ordering = ["horn_length"]
    verbose_name_plural = "oxen"

class Ox(models.Model):
    Meta = _Meta
    horn_length = models.IntegerField()
  • "중첩 된 클래스"접근 방식을 사용하면 코드 가 중첩 된 글 머리 기호 목록을 읽을 수 있지만 "참조 된 내부 클래스"메서드 _Meta를 사용하면 "하위 항목"(속성) 의 정의를 보려면 위로 스크롤해야합니다 .

  • 코드 중첩 수준이 증가하거나 다른 이유로 인해 행이 긴 경우 "참조 된 내부 클래스"메서드를 더 쉽게 읽을 수 있습니다.

* 물론 취향의 문제

3.3 약간 다른 오류 메시지

이것은 큰 문제는 아니지만 완전성을위한 것입니다. 내부 클래스에 대해 존재하지 않는 속성에 액세스 할 때 약간 다른 예외가 있습니다. 섹션 2에 제공된 예를 계속합니다.

innercls1.foo()
# AttributeError: type object 'Inner' has no attribute 'foo'

innercls2.foo()
# AttributeError: type object '_InnerClass' has no attribute 'foo'

이것은 type내부 클래스 의 s가

type(innercls1())
#mypackage.outer1.MyOuter1.Inner

type(innercls2())
#mypackage.outer2._InnerClass

0

def test_something():100 % 테스트 커버리지에 가까워지기 위해 (예 : 일부 메서드를 재정 의하여 매우 드물게 트리거되는 로깅 문 테스트) Python의 내부 클래스를 사용하여 unittest 함수 내에서 의도적으로 버그가있는 하위 클래스를 만들었습니다 (예 : inside ).

돌이켜 보면 Ed의 답변 https://stackoverflow.com/a/722036/1101109 와 유사합니다.

이러한 내부 클래스 범위를 벗어나고 해당 클래스에 대한 모든 참조가 제거되면 가비지 수집 준비가되어 있어야합니다. 예를 들어 다음 inner.py파일을 가져옵니다 .

class A(object):
    pass

def scope():
    class Buggy(A):
        """Do tests or something"""
    assert isinstance(Buggy(), A)

OSX Python 2.7.6에서 다음과 같은 흥미로운 결과를 얻습니다.

>>> from inner import A, scope
>>> A.__subclasses__()
[]
>>> scope()
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A, scope
>>> from inner import A
>>> A.__subclasses__()
[<class 'inner.Buggy'>]
>>> del A
>>> import gc
>>> gc.collect()
0
>>> gc.collect()  # Yes I needed to call the gc twice, seems reproducible
3
>>> from inner import A
>>> A.__subclasses__()
[]

힌트-내 버그가있는 클래스에 대한 다른 (캐시 된?) 참조를 유지하는 것처럼 보였던 Django 모델로 계속해서이 작업을 시도하지 마십시오.

따라서 일반적으로 100 % 테스트 범위를 평가하고 다른 방법을 사용할 수없는 경우가 아니면 이러한 종류의 목적으로 내부 클래스를 사용하지 않는 것이 좋습니다. 나는 그것의 좋은 당신이 사용하는 경우 알고이라고 생각하지만 __subclasses__()그것은 수, 때로는 내부 클래스에 의해 오염 얻을. 어느 쪽이든 당신이 여기까지 갔다면, 나는 우리가이 시점에서 파이썬에 꽤 깊이 빠져 있다고 생각합니다.


3
이것은 내부 클래스가 아닌 하위 클래스에 관한 것 입니까? A
klaas

위의 사례에서 Buggy는 A에서 물려 받았습니다. 그래서 sublass그것을 보여줍니다. 내장 함수 issubclass () 참조
klaas

@klaas에게 감사드립니다 .__subclasses__(). Python에서 범위를 벗어날 때 내부 클래스가 가비지 수집기와 상호 작용하는 방식을 이해하기 위해 사용하고 있다는 것이 더 명확해질 수 있다고 생각합니다 . 그것은 시각적으로 게시물을 지배하는 것처럼 보이므로 처음 1-3 단락은 약간 더 확장 할 가치가 있습니다.
pzrq
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.