使用求值为None类型的弱指针

Using weak pointers that evaluate to a None type

本文关键字:类型 指针 None      更新时间:2023-10-16

在我将python集成到c++应用程序的实现中,我添加了对可能有效也可能无效的节点的支持。在内部,这些是作为弱指针存储的,因此我考虑使用isValid()方法,用户可以在调用公开的方法之前使用该方法。如果在无效节点上调用已暴露的方法,则会抛出异常。

然而,我想知道是否有可能比这更蟒化一点。是否有可能在调用暴露的方法之前内部检查指针是否有效,如果它没有使python对象为None?

我想要的一个例子是:

>>> my_valid_node = corelibrary.getNode("valid_node")
>>> my_valid_node.printName()
valid_node

然而,现在系统中的其他地方可能会使节点无效,但从python的角度来看,我希望节点变为None。

>>> my_valid_node.printName()
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'printName'

谁能想到一个方法来做到这一点?

当外部事件发生时,没有干净的方法使对对象的引用成为对None的引用。然而,当使用python接口时,可以:

  • 实现__nonzero__方法,允许对象在布尔上下文中计算。
  • weak_ptr锁定失败时抛出Python异常。一个简单的解决方案是访问默认构造的boost::python::object上的成员属性,因为它引用了None

请注意,属性查找自定义点,如__getattr__,将不够,因为weak_ptr所指向的对象可能在属性访问和分配到c++成员函数之间过期。


下面是基于上述细节的一个完整的最小示例。在这个例子中,spamspam_factory(一个实例化由shared_ptr管理的spam对象的工厂)被认为是遗留类型。spam_proxy辅助类通过weak_ptr和其他辅助函数引用spam,帮助使遗留类型适应Python。

#include <string>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/weak_ptr.hpp>
#include <boost/python.hpp>
/// Assume legacy APIs.
// Mockup class containing data.
class spam
{
public:
  explicit spam(const char* name)
   : name_(name)
  {}
  std::string name() { return name_; }
private:
  std::string name_;
};
// Factory for creating and destroying the mockup class.
class spam_factory
{
public:
  boost::shared_ptr<spam> create(const char* name)
  {
    instance_ = boost::make_shared<spam>(name);
    return instance_;
  }
  void destroy()
  {
    instance_.reset();
  }
private:
  boost::shared_ptr<spam> instance_;
};
/// Auxiliary classes and functions to help obtain Pythonic semantics.
// Helper function used to cause a Python AttributeError exception to
// be thrown on None.
void throw_none_has_no_attribute(const char* attr)
{
  // Attempt to extract the specified attribute on a None object.
  namespace python = boost::python;
  python::object none;
  python::extract<python::object>(none.attr(attr))();
}
// Mockup proxy that has weak-ownership.
class spam_proxy
{
public:
  explicit spam_proxy(const boost::shared_ptr<spam>& impl)
    : impl_(impl)
  {}
  std::string name() const  { return lock("name")->name(); }
  bool is_valid() const     { return !impl_.expired();     }
  boost::shared_ptr<spam> lock(const char* attr) const
  {
    // Attempt to obtain a shared pointer from the weak pointer.
    boost::shared_ptr<spam> impl = impl_.lock();
    // If the objects lifetime has ended, then throw.
    if (!impl) throw_none_has_no_attribute(attr);
    return impl;
  }
private:
  boost::weak_ptr<spam> impl_;
};
// Use a factory to create a spam instance, but wrap it in the proxy.
spam_proxy spam_factory_create(
  spam_factory& self,
  const char* name)
{
  return spam_proxy(self.create(name));
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose the proxy class as if it was the actual class.
  python::class_<spam_proxy>("Spam", python::no_init)
    .def("__nonzero__", &spam_proxy::is_valid)
    .add_property("name", &spam_proxy::name)
    ;
  python::class_<spam_factory>("SpamFactory")
    .def("create", &spam_factory_create) // expose auxiliary method
    .def("destroy", &spam_factory::destroy)
    ;
}
互动用法:

>>> import example
>>> factory = example.SpamFactory()
>>> spam = factory.create("test")
>>> assert(spam.name == "test")
>>> assert(bool(spam) == True)
>>> if spam:
...     assert(bool(spam) == True)
...     factory.destroy() # Maybe occurring from a C++ thread.
...     assert(bool(spam) == False) # Confusing semantics.
...     assert(spam.name == "test") # Confusing exception.
... 
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
AttributeError: 'NoneType' object has no attribute 'name'
>>> assert(spam is not None) # Confusing type.

有人可能会说,虽然接口是python的,但对象的语义不是。由于weak_ptr语义在Python中不太常见,因此通常不会期望局部变量引用的对象被析构。如果需要weak_ptr语义,那么考虑引入一种方法,允许用户通过上下文管理器协议在特定上下文中获得共享所有权。例如,下面的模式允许对象的有效性检查一次,然后在有限的范围内得到保证:

>>> with spam:                # Attempt to acquire shared ownership.
...     if spam:              # Verify ownership was obtained.
...         spam.name         # Valid within the context's scope.
...         factory.destroy() # spam is still valid.
...         spam.name         # Still valid.
...                           # spam destroyed once context's scope is exited.

下面是前面示例的完整扩展,其中spam_proxy实现了上下文管理器协议:

#include <string>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/smart_ptr/weak_ptr.hpp>
#include <boost/python.hpp>
/// Assume legacy APIs.
// Mockup class containing data.
class spam
{
public:
  explicit spam(const char* name)
   : name_(name)
  {}
  std::string name() { return name_; }
private:
  std::string name_;
};
// Factory for creating and destroying the mockup class.
class spam_factory
{
public:
  boost::shared_ptr<spam> create(const char* name)
  {
    instance_ = boost::make_shared<spam>(name);
    return instance_;
  }
  void destroy()
  {
    instance_.reset();
  }
private:
  boost::shared_ptr<spam> instance_;
};
/// Auxiliary classes and functions to help obtain Pythonic semantics.
// Helper function used to cause a Python AttributeError exception to
// be thrown on None.
void throw_none_has_no_attribute(const char* attr)
{
  // Attempt to extract the specified attribute on a None object.
  namespace python = boost::python;
  python::object none;
  python::extract<python::object>(none.attr(attr))();
}
// Mockup proxy that has weak-ownership and optional shared ownership.
class spam_proxy
{
public:
  explicit spam_proxy(const boost::shared_ptr<spam>& impl)
    : shared_impl_(),
      impl_(impl)
  {}
  std::string name() const  { return lock("name")->name(); }
  bool is_valid() const     { return !impl_.expired();     }
  boost::shared_ptr<spam> lock(const char* attr) const
  {
    // If shared ownership exists, return it.
    if (shared_impl_) return shared_impl_;
    // Attempt to obtain a shared pointer from the weak pointer.
    boost::shared_ptr<spam> impl = impl_.lock();
    // If the objects lifetime has ended, then throw.
    if (!impl) throw_none_has_no_attribute(attr);
    return impl;
  }
  void enter()
  {
    // Upon entering the runtime context, guarantee the lifetime of the
    // object remains until the runtime context exits if the object is
    // alive during this call.
    shared_impl_ = impl_.lock();
  }
  bool exit(boost::python::object type,
            boost::python::object value,
            boost::python::object traceback)
  {
    shared_impl_.reset();
    return false; // Do not suppress the exception.
  }
private:
  boost::shared_ptr<spam> shared_impl_;
  boost::weak_ptr<spam> impl_;
};
// Use a factory to create a spam instance, but wrap it in the proxy.
spam_proxy spam_factory_create(
  spam_factory& self,
  const char* name)
{
  return spam_proxy(self.create(name));
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose the proxy class as if it was the actual class.
  python::class_<spam_proxy>("Spam", python::no_init)
    .def("__nonzero__", &spam_proxy::is_valid)
    // Support context manager protocol.
    .def("__enter__", &spam_proxy::enter)
    .def("__exit__", &spam_proxy::exit)
    .add_property("name", &spam_proxy::name)
    ;
  python::class_<spam_factory>("SpamFactory")
    .def("create", &spam_factory_create) // expose auxiliary method
    .def("destroy", &spam_factory::destroy)
    ;
}
互动用法:

>>> import example
>>> factory = example.SpamFactory()
>>> spam = factory.create("test")
>>> with spam:
...     assert(bool(spam) == True)
...     if spam:
...         assert(spam.name == "test")
...         factory.destroy()
...         assert(bool(spam) == True)
...         assert(spam.name == "test")
... 
>>> assert(bool(spam) == False)

确切的模式可能不是最python化的,但它提供了一种干净的方式来保证对象在有限范围内的生存期。