python内部类的目的是什么?


Answers:


85

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

内部阶层的优势:

  • 类的逻辑分组:如果一个类仅对另一个类有用,那么将其嵌入该类并将二者保持在一起是合乎逻辑的。嵌套此类“帮助程序类”可使它们的程序包更加简化。
  • 增加封装:考虑两个顶级类A和B,其中B需要访问A的成员,否则将其声明为私有。通过将B类隐藏在AA类中,可以将其成员声明为私有,B可以访问它们。另外,B本身可以对外界隐藏。
  • 更具可读性和可维护性的代码:在顶级类中嵌套小类会使代码更靠近使用位置。

主要优势是组织。可以用内部类实现什么没有他们来完成。


50
当然,封装参数不适用于Python。
bobince

30
第一点也不适用于Python。您可以根据需要在一个模块文件中定义尽可能多的类,从而将它们保持在一起,并且包的组织也不受影响。最后一点是非常主观的,我认为这是不正确的。简而言之,在这个答案中,我找不到任何支持在Python中使用内部类的参数。
克里斯·阿恩特

17
尽管如此,这些都是在编程中使用内部类的原因。您只是想确定一个竞争性答案。这个家伙在这里给出的这个答案是可靠的。
Inversus 2014年

16
@Inversus:我不同意。这不是答案,而是对其他人关于另一种语言(即Java)的回答的扩展引文。投票不足,我希望其他人也能这样做。
凯文

4
我同意这个答案,不同意反对意见。虽然嵌套类不是 Java的内部类,但它们很有用。嵌套类的目的是组织。实际上,您正在将一个类放在另一个类的名称空间下。从逻辑上讲,这样做 Pythonic的:“命名空间是一个很棒的主意,让我们做更多的事!”。例如,考虑一个DataLoader可能引发CacheMiss异常的类。将异常嵌套在主类下DataLoader.CacheMiss意味着您可以只导入DataLoader但仍使用该异常。
cbarrick '17

50

没有他们,有什么事情是无法完成的吗?

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

我认为“允许”嵌套类没有任何特殊原因,除了明确禁止“禁止”嵌套类没有特殊意义。

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


5
该用例想到把那个(即Java的式的内部类,它的实例与外部类的实例的关系)通常可以在Python定义里面的内部类解决方法外部类的-他们会看到外部对象,self而无需任何额外的工作(只需在通常将内部对象的位置放进去的其他标识符self;例如innerself),就能通过它访问外部实例。
Evgeni Sergeev

WeakKeyDictionary在此示例中使用a 实际上并不允许垃圾回收键,因为这些值通过其owner属性强烈引用了它们各自的键。
克里策费兹19'Apr

36

您需要包裹一些东西才能理解这一点。在大多数语言中,类定义是对编译器的指令。也就是说,该类是在程序运行之前创建的。在python中,所有语句都是可执行的。这意味着该语句:

class foo(object):
    pass

是一条在运行时执行的语句,如下所示:

x = y + z

这意味着您不仅可以在其他类中创建类,还可以在任意位置创建类。考虑以下代码:

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

因此,“内部类”的想法实际上不是一种语言构造;这是一个程序员构造。圭多拥有这是怎么围绕很好的总结在这里。但本质上,基本思想是简化了语言的语法。


16

在类中嵌套类:

  • 嵌套类使类定义变得肿,这使得很难看到发生了什么。

  • 嵌套类会创建耦合,从而使测试更加困难。

  • 在Python中,您可以在文件/模块中放置一个以上的类,这与Java不同,因此该类仍保持与顶级类的距离,甚至可以在类名前加上“ _”,以帮助表示不应将其他类使用它。

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

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

该类从函数中捕获值,使您可以动态创建一个类,例如C ++中的模板元编程


7

我了解反对嵌套类的参数,但是在某些情况下有使用它们的情况。想象一下,我正在创建一个双向链接列表类,并且需要创建一个节点类来维护节点。我有两个选择,在DoublyLinkedList类内部创建Node类,或在DoublyLinkedList类外部创建Node类。在这种情况下,我首选第一种选择,因为Node类仅在DoublyLinkedList类内部有意义。虽然没有隐藏/封装的好处,但是可以说Node类是DoublyLinkedList类的一部分,这有一个分组的好处。


5
假设相同的Node类对您可能还创建的其他类型的链表类没有用处,那么这是正确的,在这种情况下,它可能应该位于外部。
Acumenus 2014年

放置它的另一种方法:Node在的命名空间下DoublyLinkedList,这样做是合乎逻辑的。这 Pythonic:“命名空间是一个很棒的主意-让我们做更多的事情!”
cbarrick '17

@cbarrick:“做更多的事情”并没有说明嵌套它们。
伊森·弗曼

3

没有他们,有什么事情是无法完成的吗?如果是这样,那是什么东西?

有以下一些事情是不容易完成的相关类的继承

这是相关类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

当然,可以在没有内部类的情况下重新创建此动态绑定,而这会浪费乏味且易于出错的类。


1

我使用它的主要用例是防止小模块的扩散,在不需要单独的模块时防止命名空间污染。如果要扩展现有的类,但是该现有的类必须引用另一个应该始终与其耦合的子类。例如,我可能有一个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不是原本用于工厂功能的装饰器。


1

1.两种功能等效的方式

前面显示的两种方法在功能上是相同的。但是,有一些细微的差异,并且在某些情况下您想选择一个而不是另一个。

方式1:嵌套类定义
(=“ Nested class”)

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

方式2:将模块级别的内部类附加到外部类
(=“ Referenced内部类”)

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

class MyOuter2:
    Inner = _InnerClass

下划线用于遵循PEP8: “内部接口(包,模块,类,函数,属性或其他名称)应-带有一个前导下划线作为前缀”。

2.相似之处

下面的代码片段演示了“嵌套类”与“引用内部类”的功能相似性;它们在检查内部类实例类型的代码中的行为方式相同。不用说,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)

3.差异

下面列出了“嵌套类”和“引用内部类”的区别。它们并不大,但是有时您希望基于这些选择一个或另一个。

3.1代码封装

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

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

* 对吗?

3.2代码的可读性

Django文档指示将内部类Meta用于模型元数据。指示框架用户class Foo(models.Model)使用inner 编写一个更清晰的* 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以查看其“子项”(属性)。

  • 如果您的代码嵌套级别增加或由于其他原因导致行很长,则“引用的内部类”方法可能更具可读性。

*当然是口味问题

3.3略有不同的错误消息

这没什么大不了的,只是为了完整性:当访问内部类的不存在属性时,我们会看到截然不同的异常。继续第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

0

我已经使用Python的内部类在unittest函数(即内部def test_something():)中故意创建了错误的子类,以便接近100%的测试覆盖率(例如,通过覆盖某些方法来测试很少触发的日志语句)。

回想起来,它类似于埃德的答案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以及所有内容都非常了解。


3
这不是全部关于子类而不是内部类吗?A
克拉斯2015年

在上面的案例中,Buggy继承了A。另请参见内置函数issubclass()
klaas

感谢@klaas,我想可以更清楚.__subclasses__()地了解到,当事情超出Python范围时,我只是用来了解内部类如何与垃圾回收器交互。从视觉上看,该职位似乎占据主导地位,因此前1-3段应该进行更多扩展。
pzrq 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.