파이썬의 내부 / 중첩 클래스가 나를 혼란스럽게합니다. 그들 없이는 달성 할 수없는 것이 있습니까? 그렇다면 그게 뭔데?
답변:
http://www.geekinterview.com/question_details/64739 에서 인용 :
내부 클래스의 장점 :
- 클래스의 논리적 그룹화 : 클래스가 다른 하나의 클래스에만 유용하면 해당 클래스에 포함하고 두 클래스를 함께 유지하는 것이 논리적입니다. 이러한 "도우미 클래스"를 중첩하면 패키지가 더욱 간소화됩니다.
- 캡슐화 증가 : B가 그렇지 않으면 private으로 선언 될 A의 멤버에 액세스해야하는 두 개의 최상위 클래스 A와 B를 고려하십시오. 클래스 AA 내에서 클래스 B를 숨기면 멤버는 private으로 선언되고 B는 액세스 할 수 있습니다. 또한 B 자체는 외부 세계에서 숨길 수 있습니다.
- 더 읽기 쉽고 관리하기 쉬운 코드 : 최상위 클래스 내에 작은 클래스를 중첩하면 코드가 사용되는 위치에 더 가깝게 배치됩니다.
가장 큰 장점은 조직입니다. 내부 클래스로 수행 할 수있는 모든 것은 그것들 없이도 수행 할 수 있습니다.
DataLoader
시킬 수 있는 클래스를 고려하십시오 CacheMiss
. 기본 클래스 아래에 예외를 중첩하면 DataLoader.CacheMiss
가져올 수 DataLoader
있지만 여전히 예외를 사용할 수 있습니다 .
그들 없이는 달성 할 수없는 것이 있습니까?
아니요. 일반적으로 최상위 수준에서 클래스를 정의한 다음 이에 대한 참조를 외부 클래스에 복사하는 것과 절대적으로 동일합니다.
중첩 클래스가 '허용'되는 특별한 이유가 있다고 생각하지 않습니다. 명시 적으로 '허용'하는 것도 특별한 의미가 없습니다.
외부 / 소유자 객체의 수명주기 내에 존재하고 항상 외부 클래스의 인스턴스에 대한 참조를 갖고있는 클래스를 찾고 있다면 (자바처럼 내부 클래스) 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)"라고 말해야합니다.)
self
필요한 추가 작업없이 (당신이 일반적으로 내부의를 둘 것입니다 다른 식별자를 사용 self
, 등이 innerself
, 그 통해 외부 인스턴스에 액세스 할 수 있습니다).
WeakKeyDictionary
이 예제에서 a 를 사용 하면 값이 해당 owner
속성을 통해 각 키를 강력하게 참조하기 때문에 실제로 키가 가비지 수집되는 것을 허용하지 않습니다 .
이것을 이해하기 위해 머리를 감쌀 필요가 있습니다. 대부분의 언어에서 클래스 정의는 컴파일러에 대한 지시문입니다. 즉, 프로그램이 실행되기 전에 클래스가 생성됩니다. 파이썬에서는 모든 문이 실행 가능합니다. 이는 다음과 같은 진술을 의미합니다.
class foo(object):
pass
다음과 같이 런타임에 실행되는 명령문입니다.
x = y + z
즉, 다른 클래스 내에서 클래스를 만들 수있을뿐만 아니라 원하는 곳 어디에서나 클래스를 만들 수 있습니다. 이 코드를 고려하십시오.
def foo():
class bar(object):
...
z = bar()
따라서 "내부 클래스"라는 개념은 실제로 언어 구조가 아닙니다. 그것은 프로그래머 구조입니다. Guido는 이것이 어떻게 여기에 왔는지에 대한 아주 좋은 요약을 가지고 있습니다 . 그러나 본질적으로 기본 아이디어는 이것이 언어의 문법을 단순화한다는 것입니다.
클래스 내 중첩 클래스 :
중첩 된 클래스는 클래스 정의를 부풀리기 때문에 무슨 일이 일어나고 있는지보기가 더 어려워집니다.
중첩 클래스는 테스트를 더 어렵게 만드는 결합을 만들 수 있습니다.
Python에서는 Java와 달리 파일 / 모듈에 둘 이상의 클래스를 넣을 수 있으므로 클래스는 여전히 최상위 클래스에 가깝게 유지되며 다른 클래스가 없어야 함을 나타 내기 위해 "_"접두사가 붙은 클래스 이름을 가질 수도 있습니다. 그것을 사용합니다.
중첩 된 클래스가 유용함을 입증 할 수있는 곳은 함수 내에 있습니다.
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
이 클래스는 함수에서 값을 캡처하여 C ++에서 템플릿 메타 프로그래밍과 같은 클래스를 동적으로 생성 할 수 있습니다.
중첩 클래스에 대한 인수를 이해하지만 경우에 따라 사용하는 경우가 있습니다. 이중 연결 목록 클래스를 만들고 노드를 유지 관리하기 위해 노드 클래스를 만들어야한다고 상상해보십시오. 두 가지 선택이 있습니다. DoublyLinkedList 클래스 내부에 Node 클래스를 만들거나 DoublyLinkedList 클래스 외부에 Node 클래스를 만듭니다. Node 클래스는 DoublyLinkedList 클래스 내에서만 의미가 있기 때문에이 경우 첫 번째 선택을 선호합니다. 숨김 / 캡슐화 이점은 없지만 Node 클래스가 DoublyLinkedList 클래스의 일부라고 말할 수있는 그룹화 이점이 있습니다.
Node
클래스가 사용자가 만들 수도있는 다른 유형의 연결 목록 클래스에 유용하지 않다고 가정하면 사실 입니다.이 경우에는 아마도 외부에 있어야합니다.
Node
의 네임 스페이스 아래에 있으며 DoublyLinkedList
논리적으로 의미가 있습니다. 이것은 이다 파이썬 : "네임 스페이스는 좋은 아이디어가 경적을 울리고 하나 -하자 많은 사람들의 해!"
그들 없이는 달성 할 수없는 것이 있습니까? 그렇다면 그게 뭔데?
뭔가가 쉽게없이 할 수 없습니다 : 관련 클래스의 상속 .
다음은 관련 클래스 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
.
물론이 동적 바인딩은 지루하고 오류가 발생하기 쉬운 클래스 계측 비용으로 내부 클래스 없이 다시 만들 수 있습니다 .
내가 이것을 사용하는 주요 사용 사례는 작은 모듈의 확산 을 방지 하고 별도의 모듈이 필요하지 않을 때 네임 스페이스 오염을 방지하는 것입니다. 기존 클래스를 확장하지만 해당 기존 클래스가 항상 연결되어야하는 다른 하위 클래스를 참조해야하는 경우. 예를 들어, 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 : 중첩 된 클래스 정의
(= "중첩 된 클래스")
class MyOuter1:
class Inner:
def show(self, msg):
print(msg)
방법 2 : 모듈 수준 내부 클래스가 외부 클래스에 연결됨
(= "참조 된 내부 클래스")
class _InnerClass:
def show(self, msg):
print(msg)
class MyOuter2:
Inner = _InnerClass
밑줄은 PEP8 을 따르는 데 사용됩니다. "내부 인터페이스 (패키지, 모듈, 클래스, 함수, 속성 또는 기타 이름)는 단일 선행 밑줄로 시작해야합니다."
아래 코드 스 니펫은 "중첩 된 클래스"와 "참조 된 내부 클래스"의 기능적 유사성을 보여줍니다. 내부 클래스 인스턴스의 유형을 확인하는 코드에서 동일한 방식으로 작동합니다. 물론이 (가), 말 m.inner.anymethod()
과 유사하게 동작 할 m1
및m2
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)
"중첩 된 클래스"와 "참조 된 내부 클래스"의 차이점은 다음과 같습니다. 그것들은 크지는 않지만 때로는 이것들을 기반으로 하나 또는 다른 것을 선택하고 싶을 때가 있습니다.
"중첩 된 클래스"를 사용하면 "참조 된 내부 클래스"보다 코드를 더 잘 캡슐화 할 수 있습니다. 모듈 네임 스페이스의 클래스는 전역 변수입니다. 중첩 클래스의 목적은 모듈의 혼란을 줄이고 내부 클래스를 외부 클래스에 넣는 것입니다.
아무도 *를 사용하지 않지만 from packagename import *
코드 완성 / 인텔리 센스와 함께 IDE를 사용할 때 모듈 수준 변수의 양이 적 으면 좋을 수 있습니다.
* 맞죠?
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
를 사용하면 "하위 항목"(속성) 의 정의를 보려면 위로 스크롤해야합니다 .
코드 중첩 수준이 증가하거나 다른 이유로 인해 행이 긴 경우 "참조 된 내부 클래스"메서드를 더 쉽게 읽을 수 있습니다.
* 물론 취향의 문제
이것은 큰 문제는 아니지만 완전성을위한 것입니다. 내부 클래스에 대해 존재하지 않는 속성에 액세스 할 때 약간 다른 예외가 있습니다. 섹션 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
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__()
그것은 수, 때로는 내부 클래스에 의해 오염 얻을. 어느 쪽이든 당신이 여기까지 갔다면, 나는 우리가이 시점에서 파이썬에 꽤 깊이 빠져 있다고 생각합니다.
.__subclasses__()
. Python에서 범위를 벗어날 때 내부 클래스가 가비지 수집기와 상호 작용하는 방식을 이해하기 위해 사용하고 있다는 것이 더 명확해질 수 있다고 생각합니다 . 그것은 시각적으로 게시물을 지배하는 것처럼 보이므로 처음 1-3 단락은 약간 더 확장 할 가치가 있습니다.