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

标签 c++ boost-python

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

但是,我想知道是否有可能比这更 pythonic。是否可以在调用公开方法之前在内部检查指针是否有效,以及是否不使 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 的引用。然而,在开发 Pythonic 界面时,可以:

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

注意属性查找自定义点,比如__getattr__ , 还不够,因为 weak_ptr 指向的对象可能会在属性访问和分派(dispatch)到 C++ 成员函数之间过期。


这是一个基于上述细节的完整的最小示例。在此示例中,spamspam_factory(实例化由 shared_ptr 管理的 spam 对象的工厂)被视为遗留类型.通过 weak_ptr 引用 spamspam_proxy 辅助类和其他辅助函数有助于使遗留类型适应 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.

有人可能会争辩说,虽然接口(interface)是 Pythonic 的,但对象的语义却不是。由于 weak_ptr 语义在 Python 中不太常见,人们通常不希望局部变量引用的对象被破坏。如果 weak_ptr 语义是必要的,那么考虑引入一种方法允许用户通过 context manager protocol 在特定上下文中获得共享所有权。 .例如,以下模式允许检查一次对象的有效性,然后在有限范围内保证:

>>> 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 实现了上下文管理器协议(protocol):

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

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

关于c++ - 使用评估为 None 类型的弱指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25424100/

相关文章:

c++ - 使用自定义分配器引用字符串的 std::basic_string 特化作为 std::string 的常量对象而无需开销?

c++ - 多线程 c++11-ish 队列在 Windows 上失败

c++ - 使用 boost.python 从 UTF-8 编码的 char* 返回 python unicode 实例

python - python和C++之间的通信

c++ - 如何为 python 迭代器编写 c++ 包装器?

python - 使用 Boost Python 在构造函数中使用包含嵌套私有(private)类的构造函数公开类

c++ - 如果找到给定的单词,则保存下一个单词 (C++)

c++ - 使用 remove_if 从 vector 中删除元素

c++ - 如何使用 istream_iterator 从 ifstream 读取带空格的字符串?

c++ - 如何在 Python 中实现 C++ 类,由 C++ 调用?