Python的内部/嵌套类使我感到困惑。有没有它们无法实现的东西?如果是这样,那是什么东西?
答案 0 :(得分:73)
引自http://www.geekinterview.com/question_details/64739:
内在阶级的优点:
- 类的逻辑分组:如果一个类只对另一个类有用,那么将它嵌入该类并将两者保持在一起是合乎逻辑的。嵌套这样的“助手类”使他们的包更加简化。
- 增加封装:考虑两个顶级类A和B,其中B需要访问A的成员,否则这些成员将被声明为私有。通过将类B隐藏在类A中,可以将A的成员声明为私有,B可以访问它们。另外B本身可以隐藏在外面的世界里。
- 更易读,易维护的代码:在顶级类中嵌套小类会使代码更接近使用它的位置。
主要优势是组织。内部类可以完成的任何事情都可以在没有它们的情况下完成。
答案 1 :(得分:47)
如果没有它们,有什么东西是无法实现的吗?
没有。它们绝对等同于通常在顶级定义类,然后将对它的引用复制到外部类中。
我认为没有任何特殊原因嵌套类是“允许的”,除了明确“禁止”它们之外没有特别意义。
如果你正在寻找一个存在于外部/所有者对象生命周期内的类,并且总是引用外部类的实例 - 内部类就像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)”。)
答案 2 :(得分:25)
为了能够理解这一点,你需要把头包起来。在大多数语言中,类定义是编译器的指令。也就是说,该类是在程序运行之前创建的。在python中,所有语句都是可执行的。这意味着这句话:
class foo(object):
pass
是一个在运行时执行的语句,就像这样:
x = y + z
这意味着您不仅可以在其他类中创建类,还可以在任何地方创建类。请考虑以下代码:
def foo():
class bar(object):
...
z = bar()
因此,“内部阶级”的概念实际上并不是一种语言结构;它是一个程序员构造。 Guido对here的结果有很好的总结。但基本上,基本思想是简化语言的语法。
答案 3 :(得分:13)
在类中嵌套类:
嵌套类会使类定义膨胀,因此很难看到最新情况。
嵌套类可以创建使测试更加困难的耦合。
在Python中,您可以在文件/模块中放置多个类,这与Java不同,因此该类仍然保持接近顶级类,甚至可以使用前缀为“_”的类名来帮助表示其他人不应该使用它。
嵌套类可以证明有用的地方是函数
def some_func(a, b, c):
class SomeClass(a):
def some_method(self):
return b
SomeClass.__doc__ = c
return SomeClass
该类捕获函数中的值,允许您在C ++中动态创建类似模板元编程的类
答案 4 :(得分:4)
我理解针对嵌套类的参数,但是在某些情况下可以使用它们。想象一下,我正在创建一个双向链表类,我需要创建一个节点类来维护节点。我有两个选择,在DoublyLinkedList类中创建Node类,或者在DoublyLinkedList类之外创建Node类。在这种情况下,我更喜欢第一个选择,因为Node类只在DoublyLinkedList类中有意义。虽然没有隐藏/封装优势,但是有一个分组优势,可以说Node类是DoublyLinkedList类的一部分。
答案 5 :(得分: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()
,从而丢失动态绑定到适当的类。
请注意,在此构造中,您永远不要在类A
的主体中引用类B
。这是在类parent
中引入B
属性的动机。
当然,可以在没有内部类的情况下重新创建此动态绑定,而无需花费繁琐且容易出错的类检测。
答案 6 :(得分: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,“内部接口(包,模块,类,函数,属性或其他名称)应-带有一个前缀的下划线。 “
下面的代码片段演示了“嵌套类”与“引用内部类”的功能相似性;它们在检查内部类实例类型的代码中的行为相同。不用说,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)
下面列出了“嵌套类”和“引用内部类”的区别。它们并不大,但有时您希望根据这些选择一个或多个。
使用“嵌套类”,可以比使用“引用内部类”更好地封装代码。模块名称空间中的类是 global 变量。嵌套类的目的是减少模块中的混乱情况,并将内部类放入外部类中。
虽然没有人使用* from packagename import *
,但是,例如,当使用具有代码完成/智能感知功能的IDE时,少量的模块级别变量会很好。
* 对吗?
Django文档指示将inner class Meta用于模型元数据。指示框架用户使用内部class Foo(models.Model)
来编写class Meta
;更清晰*
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()
使用“嵌套类”方法,可以读取代码 嵌套的项目符号点列表 ,但是使用“引用内部类”方法,则必须向上滚动以查看_Meta
的定义,以查看其“子项”(属性)。
如果代码嵌套级别增加或由于其他原因导致行很长,则“引用的内部类”方法可能更具可读性。
*当然,有味道的问题
这没什么大不了的,只是为了完整性:当访问内部类的不存在属性时,我们会看到截然不同的异常。继续第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
答案 7 :(得分:0)
我已经使用Python的内部类在unittest函数中创建故意错误的子类(即在def test_something():
内),以便接近100%的测试覆盖率(例如,通过覆盖某些方法来测试很少触发的日志记录语句)。
回想起来,它类似于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__()
,它有时会被内部类污染。无论哪种方式,如果你遵循这一点,我认为我们在这一点上非常深入Python,私人dunderscores和所有。
答案 8 :(得分:0)
我使用它的主要用例是防止小模块和的扩散,以防止在不需要单独的模块时命名空间污染。如果我正在扩展现有的类,但是现有的类必须引用应始终与其耦合的另一个子类。例如,我可能有一个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
由可扩展性类组成。
这是另一个用例:我想在我的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
装饰器。