Python 内部类的目的是什么?

Python 的内部/嵌套类让我感到困惑。有什么事情没有它们就不能完成吗? 如果有,那是什么事情?

68810 次浏览

引自 http://www.geekinterview.com/question_details/64739:

内部课堂的好处:

  • 类的逻辑分组 : 如果一个类只对另一个类有用,那么将它嵌入到该类中并将两者保持在一起是合乎逻辑的。嵌套这样的“助手类”可以使它们的包更加流线型。
  • 增加封装 : 考虑两个顶级类 A 和 B,其中 B 需要访问否则将被声明为私有的 A 的成员。通过在类 A 中隐藏类 B,可以将类 A 的成员声明为 private,并且 B 可以访问它们。另外 B 本身可以对外界隐藏。
  • 更易读、可维护的代码 : 在顶级类中嵌套小类会使代码更接近使用它的位置。

主要的优势是组织性。任何可以用内部类 can完成的事情都可以不用它们来完成。

在类中嵌套类:

  • 嵌套类使类定义膨胀,使得查看正在发生的事情变得更加困难。

  • 嵌套类可以创建耦合,这将使测试更加困难。

  • 在 Python 中,与 Java 不同,你可以在一个文件/模块中放入多个类,所以这个类仍然接近顶级类,甚至可以在类名前加上“ _”,以表示其他人不应该使用它。

可以证明嵌套类有用的地方是在函数内部

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

这个类捕获了函数中的值,这样你就可以动态地创建一个类,就像 C + + 中的模板超编程一样

有什么事没有他们就不能完成吗?

没有。它们绝对等价于通常在顶层定义类,然后将对它的引用复制到外部类中。

我不认为嵌套类被“允许”有什么特别的原因,除了显式地“禁止”它们也没有什么特别的意义。

如果您正在寻找一个存在于外部/所有者对象的生命周期中的类,并且总是引用外部类的一个实例ーー内部类,正如 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 (Inside)”。)

有些事你需要理解才能明白。在大多数语言中,类定义是对编译器的指令。也就是说,类是在程序运行之前创建的。在 python 中,所有语句都是可执行的。这意味着这个声明:

class foo(object):
pass

是一个在运行时执行的语句,就像下面这样:

x = y + z

这意味着您不仅可以在其他类中创建类,还可以在任何想要创建的地方创建类。考虑下面的代码:

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

因此,“内部类”的概念实际上不是一种语言构造,而是一种程序员构造。Guido 对 给你是如何产生的有一个很好的总结。但本质上,基本思想是这样简化了语言的语法。

我理解反对嵌套类的论点,但是在某些场合使用它们是有道理的。假设我正在创建一个双链表类,并且需要创建一个用于维护节点的节点类。我有两种选择,在 DoublyLinkedList 类内创建 Node 类,或者在 DoublyLinkedList 类外创建 Node 类。在这种情况下,我更喜欢第一个选择,因为 Node 类只在 DoublyLinkedList 类中有意义。虽然没有隐藏/封装的好处,但是分组的好处是可以说 Node 类是 DoublyLinkedList 类的一部分。

I have used Python's inner classes to create deliberately buggy subclasses within unittest functions (i.e. inside def test_something():) in order to get closer to 100% test coverage (e.g. testing very rarely triggered logging statements by overriding some methods).

现在回想起来,这和艾德的答案 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__(),它可以 有时候得到内部类污染。不管怎样,如果你跟了这么久,我想我们已经深入研究 Python 了,包括私人 dunderscore 等等。

我使用它的主要用例是防止小模块 还有的扩散,以防止在不需要单独的模块时造成名称空间污染。如果我正在扩展一个现有的类,但是那个现有的类必须引用另一个应该总是耦合到它的子类。例如,我可能有一个 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)

Then we can:

>>> 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}'

But sometimes just for convention, you want utils to be composed of classes for extensibility.

下面是另一个用例: 我希望在 OutterClass 中有一个用于可变量的工厂,而不必调用 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装饰器。

有什么事情没有他们就不能完成吗? 如果是这样的话, 那是什么?

有些东西。

下面是一个极简主义的例子,与 AB相关:

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'>

If B were a top-level class, you could not write self.B() in the method make_B but would simply write B(), and thus lose the 动态绑定动态绑定 to the adequate classes.

注意,在这种构造中,不应该在类 B的主体中引用类 A。这就是在类 B中引入 parent属性的动机。

当然,这种动态绑定可以重新创建 没有内部类,代价是对类进行冗长且容易出错的检测。

1. Two functionally equivalent ways

前面显示的两种方法是 功能上相同的。然而,有一些细微的差别,有些情况下,你想选择其中之一。

方法1: 嵌套类定义 < br > (= “嵌套类”)

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

方法2: 使用模块级别的内部类附加到外部类 < br > (= “引用的内部类”)

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


class MyOuter2:
Inner = _InnerClass

Underscore is used to follow PEP8 "internal interfaces (packages, modules, classes, functions, attributes or other names) should -- be prefixed with a single leading underscore."

2. 相似之处

Below code snippet demonstrates the functional similarities of the "Nested class" vs "Referenced inner class"; They would behave the same way in code checking for the type of an inner class instance. Needless to say, the m.inner.anymethod() would behave similarly with m1 and 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)


3. 差异

下面列出了“嵌套类”和“引用内部类”的区别。它们并不大,但有时你会根据这些选择其中一个。

3.1代码封装

使用“嵌套类”可以比使用“引用内部类”更好地封装代码。模块命名空间中的类是 全球性的变量。嵌套类的目的是减少模块中的混乱,并将内部类放在外部类中。

虽然没有人 * 在使用 from packagename import *,但是少量的模块级变量是很好的,例如当使用具有代码完成/智能感知的 IDE 时。

* 对吧?

3.2 Readability of code

Django 文档指示对模型元数据使用 内部类元能。指示框架用户使用内部 class Meta编写一个 class Foo(models.Model)会更清楚一些;

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


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

而不是“写一个 class _Meta,然后写一个 class Foo(models.Model)Meta = _Meta”;

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


class Ox(models.Model):
Meta = _Meta
horn_length = models.IntegerField()
  • 使用“嵌套类”方法代码 < strong > 可以读取嵌套的项目符号列表 ,但是使用“引用内部类”方法,必须向上滚动查看 _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

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


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