在我的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'
有人可以想办法吗?
答案 0 :(得分:1)
当外部事件发生时,没有 clean 方法使对象的引用成为对None
的引用。然而,在努力建立Pythonic界面时,可以:
__nonzero__
方法以允许在布尔上下文中评估对象。weak_ptr
无法锁定时抛出Python异常。一个简单的解决方案是访问默认构造的boost::python::object
上的成员属性,因为它引用了None
。请注意,属性查找自定义点(例如__getattr__
)将不够,因为weak_ptr
指向的对象可能在属性访问和调度到C ++成员函数之间到期。
以下是基于上述细节的完整最小示例。在此示例中,spam
和spam_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)
;
}
确切的模式可能不是最恐怖的,但它提供了一种干净的方式来保证对象在有限的范围内的生命。