在我将 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++ 成员函数之间过期。
这是一个基于上述细节的完整的最小示例。在此示例中,spam
和 spam_factory
(实例化由 shared_ptr
管理的 spam
对象的工厂)被视为遗留类型.通过 weak_ptr
引用 spam
的 spam_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/