我有一个用PyBind11
Package 的c++
类。问题是:当Python
脚本结束时,c++
destructor
没有被自动调用。这会导致一个不整洁的退出,因为网络资源需要被析构函数释放。
作为一种解决办法,有必要显式删除Python
对象,但我不明白为什么!
请有人解释一下这里出了什么问题,以及如何在Python
对象被垃圾收集时自动调用destructor
?
Pybind 11绑定代码:
py::class_<pcs::Listener>(m, "listener")
.def(py::init<const py::object &, const std::string &, const std::string &, const std::string &, const std::string &, const std::set<std::string> &, const std::string & , const bool & , const bool & >(), R"pbdoc(
Monitors network traffic.
When a desired data source is detected a client instance is connected to consume the data stream.
Reconstructs data on receipt, like a jigsaw. Makes requests to fill any gaps. Verifies the data as sequential.
Data is output by callback to Python. Using the method specified in the constructor, which must accept a string argument.
)pbdoc");
在Python中:
#Function to callback
def print_string(str):
print("Python; " + str)
lstnr = listener(print_string, 'tcp://127.0.0.1:9001', clientCertPath, serverCertPath, proxyCertPath, desiredSources, 'time_series_data', enableCurve, enableVerbose)
#Run for a minute
cnt = 0
while cnt < 60:
cnt += 1
time.sleep(1)
#Need to call the destructor explicity for some reason
del lstnr
3条答案
按热度按时间qacovj5a1#
正如在注解中提到的,这种行为的直接原因是Python垃圾收集器:当一个对象的引用计数器为零时,垃圾收集器 * 可能 * 销毁该对象(从而调用c++析构函数),但它 * 不必在那个时候 * 这么做。
这个想法在下面的回答中得到了更充分的阐述:
https://stackoverflow.com/a/38238013/790979
正如在上面的链接中提到的,如果你在Python中的对象生命周期结束时需要清理,一个不错的解决方案是context management,你可以在对象的 Package 器中定义
__enter__
和__exit__
(在pybind11或Python本身中),让__exit__
释放网络资源,然后,在Python客户端代码中,类似于:nzk0hqpo2#
所以几年后,我通过在PyBind11代码中添加
__enter__
和__exit__
方法处理来启用Python上下文管理器with
支持,从而修复了这个问题:在C++类中添加了相应的函数,如下所示:
注
1.从
enter()
返回的指针值对于with
....as
语句中的as
功能非常重要。1.传递给
exit(py::handle type, py::handle value, py::handle traceback)
的参数是有用的调试信息。Python用法:
Python上下文管理器现在调用C++对象上的析构函数,从而顺利地释放网络资源。
2exbekwf3#
上面GoFaster的解决方案是有帮助和正确的方法,但我只是想澄清和纠正他们的Assert,
Python上下文管理器现在调用C对象上的析构函数,从而顺利释放网络资源
这是不正确的。上下文管理器只保证
__exit__
将被调用,而不保证任何析构函数将被调用。让我来演示一下-这是一个用C实现的托管资源:Python绑定:
和一些python测试代码(注意上面的代码 * 还没有 * 释放
__exit__
中的资源):其产生:
因此,资源被构造,获取内存,并在上下文管理器中被访问。到目前为止,一切都很好。然而,内存仍然可以在上下文管理器之外被访问(在调用destuctor之前,这是由python运行时决定的,不在我们的控制范围内,除非我们用
del
强制它,这完全破坏了上下文管理器的作用。但是我们实际上并没有释放
__exit__
中的资源。这一次,当您在上下文管理器外部调用
get()
时,ManagedResource
对象本身仍然没有被析构,但是它内部的资源已经被释放,而且还有更大的危险:如果在
with
块外创建ManagedResource
,则会泄漏资源,因为__exit__
永远不会被调用。要解决此问题,需要将从构造函数获取资源的操作推迟到__enter__
方法,并检查get
中是否存在该资源。简而言之,这个故事的寓意是:
__enter__
方法中获取资源,而不是在构造函数中__exit__
方法中释放资源,而不是析构函数上下文管理的对象本身不是RAII资源,而是RAII资源的 Package 器。