Какова цель внутренних классов python?
внутренние/вложенные классы Python путают меня. Есть то, что не может быть достигнуто без них? Если да, то что это?
7 ответов:
цитируется из http://www.geekinterview.com/question_details/64739:
преимущества внутреннего класса:
- логическая группировка классов: если класс полезен только для одного другого класса, то логично встроить его в этот класс и сохранить их вместе. Вложение таких "вспомогательных классов" делает их пакет более упорядоченным.
- повышенная заключения: рассмотрим два верхнего уровня классы A и B, где B нуждается в доступе к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B в пределах класса A члены A могут быть объявлены частными, и B может получить к ним доступ. Кроме того, сам Б может быть скрыт от внешнего мира.
- более читаемый, поддерживаемый код: вложение небольших классов в классы верхнего уровня помещает код ближе к тому месту, где он используется.
главным преимуществом является организация. Все, что может быть достигнуто с помощью внутренних классов можете свершится без них.
нет. Они абсолютно эквивалентны определению класса обычно на верхнем уровне, а затем копированию ссылки на него во внешний класс.
Я не думаю, что есть какая-то особая причина, по которой вложенные классы "разрешены", кроме того, что нет особого смысла явно "запрещать" их.
Если вы ищете класс, который существует в течение жизненного цикла объект outer / owner, и всегда имеет ссылку на экземпляр внешнего класса - внутренние классы, как это делает Java – тогда вложенные классы 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)" после определения класса.)
есть что-то, что вам нужно обернуть голову вокруг, чтобы быть в состоянии понять это. В большинстве языков определения классов являются директивами для компилятора. То есть класс создается до того, как программа будет запущена. В python все операторы являются исполняемыми. Это означает, что это утверждение:
class foo(object): pass
- это заявление, которое выполняется во время выполнения, так же, как этот:
x = y + z
это означает, что вы можете не только создавать классы в других классах, вы можете создавать классы в любом месте вы хотите. Рассмотрим этот код:
здесь. Но по сути, основная идея заключается в том, что это упрощает грамматику языка.def foo(): class bar(object): ... z = bar()
вложенность классов внутри классов:
вложенные классы раздувают определение класса, что делает его труднее увидеть, что происходит.
вложенные классы могут создать связь, которая сделает тестирование более сложным.
в Python вы можете поместить более одного класса в файл / модуль, в отличие от Java, поэтому класс по-прежнему остается близким к классу верхнего уровня и может даже иметь имя класса с префиксом"_", чтобы помочь обозначить это другие не должны использовать его.
место, где вложенные классы могут оказаться полезными в функции
def some_func(a, b, c): class SomeClass(a): def some_method(self): return b SomeClass.__doc__ = c return SomeClass
класс захватывает значения из функции, что позволяет динамически создавать класс, как шаблон метапрограммирования в C++
Я понимаю аргументы против вложенных классов, но есть случай для их использования в некоторых случаях. Представьте, что я создаю класс двусвязного списка, и мне нужно создать класс узла для обслуживания узлов. У меня есть два варианта: создать класс узла внутри класса DoublyLinkedList или создать класс узла вне класса DoublyLinkedList. Я предпочитаю первый вариант в этом случае, потому что класс Node имеет смысл только внутри класса DoublyLinkedList. Пока нет скрытие / инкапсуляция преимущества, есть группировка преимущество того, что можно сказать, что класс узла является частью класса DoublyLinkedList.
я использовал внутренние классы Python для создания намеренно ошибочных подклассов внутри unittest функций (т. е. внутри
def test_something():
) для того, чтобы приблизиться к 100% тестовому покрытию (например, тестирование очень редко запускало операторы регистрации путем переопределения некоторых методов).в ретроспективе это похоже на ответ Эда https://stackoverflow.com/a/722036/1101109
такие внутренние классы должны выходите за рамки и будьте готовы к сбору мусора один раз все ссылки на них были удалены. Например, возьмите следующее
inner.py
file: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__()
, что он может иногда загрязняются внутренними классами. В любом случае, если вы следили за этим далеко, я думаю, что мы довольно глубоко в Python на данный момент, частные dunderscores и все такое.
основной вариант использования я использую это для предотвращения распространения небольших модулей и чтобы предотвратить загрязнение пространства имен, когда отдельные модули не нужны. Если я расширяю существующий класс, но этот существующий класс должен ссылаться на другой подкласс, который всегда должен быть связан с ним. Например, у меня может быть
utils.py
модуль, который имеет много вспомогательных классов в нем, которые не обязательно связаны вместе, но я хочу усилить связь для некоторые of эти вспомогательные классы. Например, когда я реализую 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
в целом и просто переопределить значение по умолчанию ()::
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
состоять из классов для расширяемости.вот еще use-case: мне нужна фабрика для изменяемых объектов в моем внешнем классе без необходимости вызывать
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
декоратор, который вы в противном случае использовали бы для заводской функции.