使用评估为None类型的弱指针

时间:2014-08-21 10:32:54

标签: c++ boost-python

在我的python集成到c ++应用程序的实现中,我正在添加对可能有效或可能无效的节点的支持。在内部这些存储为弱指针,所以我想有一个用户可以在调用公开方法之前使用的isValid()方法。如果他们在无效节点上调用一个公开的方法,它将抛出异常。

然而,我想知道是否有可能比这更加pythonic。是否可以在调用公开方法之前在内部检查指针是否有效,是否可以使python对象为无?

我想要的一个例子是:

>>> 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'

有人可以想办法吗?

1 个答案:

答案 0 :(得分:1)

当外部事件发生时,没有 clean 方法使对象的引用成为对None的引用。然而,在努力建立Pythonic界面时,可以:

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

请注意,属性查找自定义点(例如__getattr__)将不够,因为weak_ptr指向的对象可能在属性访问和调度到C ++成员函数之间到期。


以下是基于上述细节的完整最小示例。在此示例中,spamspam_factory,实例化由spam管理的shared_ptr个对象的工厂被视为遗留类型。通过spam_proxy引用spam的{​​{1}}辅助类以及辅助函数有助于将遗留类型调整为Python。

weak_ptr

交互式使用:

#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)
    ;
}

有人可能会争辩说,虽然界面是Pythonic,但对象的语义却不是。由于>>> 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语义,那么考虑引入一种方法,允许用户通过context manager protocol获取特定上下文中的共享所有权。例如,以下模式允许对象的有效性进行一次检查,然后在有限的范围内得到保证:

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)
    ;
}

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