我一直在尝试在Python中找到RAII。 资源分配是初始化是C ++中的一种模式 对象在创建时初始化。如果失败,则抛出 一个例外。通过这种方式,程序员知道这一点 对象永远不会处于半构造状态。蟒蛇 可以做这么多。
但RAII也适用于C ++的范围规则 确保迅速销毁对象。一旦变量 弹出堆栈它被摧毁。这可能发生在Python中,但仅限于此 如果没有外部或循环引用。
更重要的是,对象的名称在函数之前仍然存在 在退出(有时更长)。模块级别的变量将 坚持模块的生命周期。
如果我这样做,我想得到一个错误:
for x in some_list:
...
... 100 lines later ...
for i in x:
# Oops! Forgot to define x first, but... where's my error?
...
我可以在使用它后手动删除它们, 但这很难看,我需要付出努力。
我希望在这种情况下做什么 - 我意味着什么:
for x in some_list:
surface = x.getSurface()
new_points = []
for x,y,z in surface.points:
... # Do something with the points
new_points.append( (x,y,z) )
surface.points = new_points
x.setSurface(surface)
Python做了一些作用域,但不是在缩进级别,只是在 功能水平。要求我创建一个新功能似乎很愚蠢 只是为了确定变量的范围,以便重用名称。
Python 2.5有"with" statement
但这需要我明确地加入__enter__
和__exit__
函数
并且通常似乎更倾向于清理像文件这样的资源
和mutex锁定无论退出向量。它对范围界定没有帮助。
或者我错过了什么?
我搜索过“Python RAII”和“Python范围”,但我找不到任何东西 直接和权威地解决了这个问题。 我查看了所有的PEP。这个概念似乎没有得到解决 在Python中。
我是一个坏人,因为我想在Python中使用范围变量? 这是不是Pythonic?
我不是喜欢它吗?
也许我正试图消除语言动态方面的好处。 有时希望范围得到执行是否自私?
我是否因为想要编译器/解释器而懒惰 抓住我的疏忽变量重用错误?嗯,是的,当然我很懒, 但我是不是很懒?
答案 0 :(得分:28)
tl; dr RAII是不可能的,你将它与一般的范围混合起来,当你错过那些额外的范围时,你可能会编写错误的代码。
也许我没有得到你的问题,或者你没有得到关于Python的一些非常重要的东西......首先,与范围相关的确定性对象破坏是不可能的垃圾收集语言。 Python中的变量仅仅是引用。一旦指向它的指针超出范围,你就不希望malloc
'的内存块为free
',是吗?如果您碰巧使用引用计数,某些情况下的实际例外 - 但没有语言足够疯狂地设置确切的实现。
并且即使你有引用计数,就像在CPython中一样,它是一个实现细节。通常,在Python中包含使用引用计数的各种实现 not ,您应该编写代码,好像每个对象都会挂起,直到内存耗尽。
对于函数调用的其余部分存在的名称:可以通过del
语句从当前或全局范围中删除名称。但是,这与手动内存管理无关。它只是删除了引用。可能会或可能不会触发引用的对象为GC'd,而不是练习的重点。
你是对的,with
与范围界定无关,只是确定性清理(因此它与末端的RAII重叠,但不在手段中)。
也许我正试图消除语言动态方面的好处。有时希望范围得到执行是否自私?
没有。体面的词汇范围是一个独立于动态/静态的优点。不可否认,Python(2 - 3几乎已经解决了这个问题)在这方面存在缺陷,尽管它们更多地属于闭包领域。
但是要解释“为什么”:Python 必须保守其开始新范围的地方因为没有声明,否则,对名称的赋值使其成为最内层/当前范围的本地。所以例如如果for循环有自己的范围,则无法轻易修改循环外的变量。
我是否因为希望编译器/解释器能够捕获我的疏忽变量重用错误而懒惰?嗯,是的,当然我很懒,但我是不是很懒?
再一次,我认为名义上的重复使用(以一种引入错误或陷阱的方式)是罕见的,无论如何都很小。
编辑:尽可能清楚地说明:
with
语句进行确定性清理。是的,它没有引入新的范围(见下文),因为这不是它的用途。它删除托管对象绑定的名称并不重要 - 尽管如此,清理仍然是“不要碰我,我无法使用”对象(例如关闭的文件流)。 / LI>
答案 1 :(得分:13)
你对with
是对的 - 它与变量范围完全无关。
如果您认为全局变量存在问题,请避免使用全局变量。这包括模块级变量。
在Python中隐藏状态的主要工具是类。
生成器表达式(以及Python 3中的列表推导)也有自己的范围。
如果你的函数足够长,你可能无法跟踪局部变量,你可能应该重构你的代码。
答案 2 :(得分:8)
但RAII也适用范围界定 C ++规则确保提示 破坏物体。
这在GC语言中被认为是不重要的,这些语言基于内存为fungible的想法。只要在其他地方有足够的内存来分配新对象,就没有迫切需要回收对象的内存。诸如文件句柄,套接字和互斥体之类的不可替换资源被认为是特殊处理的特殊情况(例如,with
)。这与C ++的模型形成对比,后者将所有资源都视为相同。
一旦变量弹出 堆栈被摧毁。
Python没有堆栈变量。在C ++术语中,所有是shared_ptr
。
Python做了一些范围,但没有 缩进级别,只是在 功能水平。这看起来很傻 要求我做一个新功能 只是为了尽可能地调整变量的范围 重用一个名字。
它也确定了生成器理解级别(在3.x中,所有理解中)。
如果您不想破坏for
循环变量,请不要使用这么多for
个循环。特别是,在循环中使用append
是非Pythonic。而不是:
new_points = []
for x,y,z in surface.points:
... # Do something with the points
new_points.append( (x,y,z) )
写:
new_points = [do_something_with(x, y, z) for (x, y, z) in surface.points]
或
# Can be used in Python 2.4-2.7 to reduce scope of variables.
new_points = list(do_something_with(x, y, z) for (x, y, z) in surface.points)
答案 3 :(得分:2)
基本上你可能使用了错误的语言。如果你想要理智的范围规则和可靠的破坏,那么坚持使用C ++或尝试Perl。关于何时释放内存的GC辩论似乎忽略了这一点。它是关于释放其他资源,如互斥锁和文件句柄。我相信C#在引用计数变为零时调用的析构函数与它决定回收内存时的区别。人们不关心内存回收,但一旦不再引用就想知道。遗憾的是,Python作为一种语言具有真正的潜力。但它是非传统的范围和不可靠的析构函数(或者至少是依赖于实现的析构函数)意味着一个人被C ++和Perl所获得的权力所剥夺。
有趣的是,如果可以使用新内存而不是在GC中重新使用新内存,则会发表评论。这不仅仅是一种说它泄漏记忆的奇特方式: - )
答案 4 :(得分:2)
在多年的C ++之后切换到Python时,我发现依靠__del__
模仿RAII类型行为很有诱惑力,例如:关闭文件或连接。但是,有些情况(例如由Rx实现的观察者模式),被观察的东西保持对您的对象的引用,使其保持活力!因此,如果您想在源被终止之前关闭连接,那么在__del__
尝试执行此操作时,您将无法到达任何地方。
UI编程出现以下情况:
class MyComponent(UiComponent):
def add_view(self, model):
view = TheView(model) # observes model
self.children.append(view)
def remove_view(self, index):
del self.children[index] # model keeps the child alive
所以,这里是获取RAII类型行为的方法:创建一个带有添加和删除钩子的容器:
import collections
class ScopedList(collections.abc.MutableSequence):
def __init__(self, iterable=list(), add_hook=lambda i: None, del_hook=lambda i: None):
self._items = list()
self._add_hook = add_hook
self._del_hook = del_hook
self += iterable
def __del__(self):
del self[:]
def __getitem__(self, index):
return self._items[index]
def __setitem__(self, index, item):
self._del_hook(self._items[index])
self._add_hook(item)
self._items[index] = item
def __delitem__(self, index):
if isinstance(index, slice):
for item in self._items[index]:
self._del_hook(item)
else:
self._del_hook(self._items[index])
del self._items[index]
def __len__(self):
return len(self._items)
def __repr__(self):
return "ScopedList({})".format(self._items)
def insert(self, index, item):
self._add_hook(item)
self._items.insert(index, item)
如果UiComponent.children
是一个ScopedList
,它会调用孩子们的acquire
和dispose
方法,那么您可以获得与确定资源获取和处置相同的保证在C ++中。