Внутренние/вложенные классы Python путают меня. Есть ли что-то, что не может быть достигнуто без них? Если да, то что это за вещь?
Какова цель внутренних классов python?
Ответ 1
Цитата из http://www.geekinterview.com/question_details/64739:
Преимущества внутреннего класса:
- Логическая группировка классов. Если класс полезен только для одного другого класса, тогда логично встроить его в этот класс и сохранить вместе. Вложение таких "вспомогательных классов" делает их пакет более упорядоченным.
- Увеличенная инкапсуляция. Рассмотрим два класса верхнего уровня A и B, где B требуется доступ к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B в классе A, члены могут быть объявлены частными, а B - доступ к ним. Кроме того, сам B может быть скрыт от внешнего мира.
- Более читаемый, поддерживаемый код. Вложенные небольшие классы в классах верхнего уровня помещают код ближе к тому, где он используется.
Основным преимуществом является организация. Все, что может быть выполнено с помощью внутренних классов , может выполняться без них.
Ответ 2
Есть ли что-то, что не может быть достигнуто без них?
Нет. Они абсолютно эквивалентны определению класса, обычно на верхнем уровне, а затем копированию ссылки на него во внешний класс.
Я не думаю, что есть какая-либо особая причина, в которой допускаются вложенные классы, кроме того, что явно не имеет смысла явно запрещать их.
Если вы ищете класс, который существует в жизненном цикле объекта external/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)" после определения класса.)
Ответ 3
Там вам нужно обернуть голову, чтобы понять это. В большинстве языков определения классов являются директивами для компилятора. То есть класс создается до того, как программа будет запущена. В python все операторы исполняемы. Это означает, что это утверждение:
class foo(object):
pass
- это оператор, который выполняется во время выполнения, как этот:
x = y + z
Это означает, что вы не только можете создавать классы в других классах, но и создавать классы в любом месте. Рассмотрим этот код:
def foo():
class bar(object):
...
z = bar()
Таким образом, идея "внутреннего класса" на самом деле не является языковой конструкцией; это программист. У Гвидо очень хорошее представление о том, как это произошло здесь. Но по существу, основная идея заключается в том, что это упрощает грамматику языка.
Ответ 4
Вложенные классы внутри классов:
-
Вложенные классы раздувают определение класса, что затрудняет просмотр происходящего.
-
Вложенные классы могут создавать связь, что затрудняет тестирование.
-
В Python вы можете поместить более одного класса в файл/модуль, в отличие от Java, поэтому класс по-прежнему остается близким к классу верхнего уровня и может даже иметь имя класса с префиксом "_", чтобы помочь обозначить что другие не должны его использовать.
Место, где вложенные классы могут оказаться полезными, находится внутри функций
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
Класс захватывает значения из функции, позволяющие динамически создавать класс, например метапрограммирование шаблонов в С++
Ответ 5
Я понимаю аргументы против вложенных классов, но есть случай для их использования в некоторых случаях. Представьте, что я создаю двусвязный класс списка, и мне нужно создать класс node для поддержки узлов. У меня есть два варианта: создайте класс node внутри класса DoublyLinkedList или создайте класс node вне класса DoublyLinkedList. Я предпочитаю первый выбор в этом случае, потому что класс node имеет смысл только внутри класса DoublyLinkedList. Несмотря на отсутствие преимуществ скрытия/инкапсуляции, существует преимущество группировки в том, что можно сказать, что класс node является частью класса DoublyLinkedList.
Ответ 6
Я использовал внутренние классы Python для создания преднамеренно багги-подклассов в unittest-функциях (т.е. внутри def test_something():
), чтобы приблизиться к 100-процентному охвату тестирования (например, тестирование очень редко вызываемых операторов протоколирования путем переопределения некоторых методов).
В ретроспективе это похоже на ответ Эд fooobar.com/questions/75861/...
Такие внутренние классы должны выйти из сферы действия и быть готовыми к сбору мусора, как только все ссылки на них будут удалены. Например, возьмите следующий 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__()
, это может иногда загрязняться внутренними классами. В любом случае, если вы следовали за этим далеко, я думаю, что мы довольно глубоко в Python на данный момент, частные dunderscores и все.
Ответ 7
Основным вариантом использования, который я использую для этого, является предотвращение распространения небольших модулей и предотвращение загрязнения пространства имен, когда отдельные модули не нужны. Если я расширяю существующий класс, но этот существующий класс должен ссылаться на другой подкласс, который всегда должен быть связан с ним. Например, у меня может быть модуль utils.py
, в котором есть много вспомогательных классов, которые не обязательно связаны вместе, но я хочу усилить связь для некоторых из этих вспомогательных классов. Например, когда я реализую fooobar.com/info/47555/...
: 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
состоял из классов для расширяемости.
Вот еще один прецедент: я хочу использовать factory для modables в моем OuterClass без необходимости вызывать 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
, который в противном случае вы использовали бы для функции factory.