diff --git a/enaml/src/dynamicscope.cpp b/enaml/src/dynamicscope.cpp index 2ff7363c0..5a80f6160 100644 --- a/enaml/src/dynamicscope.cpp +++ b/enaml/src/dynamicscope.cpp @@ -72,8 +72,7 @@ namespace ScopeIterator FLocals, FGlobals, FBuiltins, - ParentTypeDict, - ParentInstanceDict, + Parent, }; } @@ -84,6 +83,7 @@ struct DynamicScopeIterator PyObject* scope; PyObject* parent; PyObject* iter; + PyObject* used; ScopeIterator::State state; static PyType_Spec TypeObject_Spec; @@ -96,6 +96,7 @@ namespace { +static PyObject* dir; static PyObject* parent_str; static PyObject* self_str; static PyObject* change_str; @@ -564,6 +565,11 @@ DynamicScopeIterator_new( PyTypeObject* type, PyObject* args, PyObject* kwargs ) iter->scope = cppy::incref( scope ); iter->state = ScopeIterator::FWrites; iter->iter = 0; + iter->used = PySet_New(0); + if ( !iter->used ) + return 0; + if ( PySet_Add(iter->used, self_str) < 0 || PySet_Add(iter->used, change_str) < 0) + return 0; return self; } @@ -573,6 +579,7 @@ DynamicScopeIterator_clear( DynamicScopeIterator* self ) Py_CLEAR( self->scope ); Py_CLEAR( self->parent ); Py_CLEAR( self->iter ); + Py_CLEAR( self->used ); } @@ -582,6 +589,7 @@ DynamicScopeIterator_traverse( DynamicScopeIterator* self, visitproc visit, void Py_VISIT( self->scope ); Py_VISIT( self->parent ); Py_VISIT( self->iter ); + Py_VISIT( self->used ); return 0; } @@ -601,9 +609,56 @@ DynamicScopeIterator_iter( DynamicScopeIterator* self ) return cppy::incref( pyobject_cast(self) ); } +static bool ignore_key( PyObject* str ) +{ + if ( !PyUnicode_CheckExact(str) ) + return true; + return ( + PyUnicode_GET_LENGTH( str ) > 4 // __x__ + && strncmp(PyUnicode_AsUTF8( str ), "__", 2) == 0 + ); +} + +// Return false if error. Item will be null if iterator is exhasuted. +bool DynamicScopeIterator_next_from( DynamicScopeIterator* self, PyObject* source, cppy::ptr &item ) +{ + if ( !self->iter ) + { + if ( source == self->parent ) + { + cppy::ptr items( PyObject_CallOneArg( dir, source ) ); + if ( !items ) + return 0; + self->iter = PyObject_GetIter( items.get() ); + } + else + { + self->iter = PyObject_GetIter( source ); + } + if ( !self->iter ) + return false; + } + while ( (item = PyIter_Next( self->iter )) ) + { + if ( ignore_key( item.get() ) ) + continue; + int r = PySet_Contains( self->used, item.get() ); + if ( r == 0 ) // new item + return PySet_Add( self->used, item.get() ) == 0; + if ( r < 0 ) + return false; // error + // else already used, try next + } + // item is null + Py_CLEAR( self->iter ); + return true; // no more results + +} + PyObject* DynamicScopeIterator_next( DynamicScopeIterator* self ) { + cppy::ptr item; DynamicScope* scope = reinterpret_cast( self->scope ); switch( self->state ) { @@ -611,16 +666,10 @@ DynamicScopeIterator_next( DynamicScopeIterator* self ) { if ( scope->f_writes ) { - if ( !self->iter ) - { - self->iter = PyObject_GetIter( scope->f_writes ); - if ( !self->iter ) - return 0; - } - cppy::ptr item( PyIter_Next( self->iter ) ); + if ( !DynamicScopeIterator_next_from( self, scope->f_writes, item ) ) + return 0; if ( item ) return item.release(); - Py_CLEAR( self->iter ); } self->state = ScopeIterator::Self; // Fall through @@ -640,96 +689,41 @@ DynamicScopeIterator_next( DynamicScopeIterator* self ) // TODO: Nonlocals ? case ScopeIterator::FLocals: { - if ( !self->iter ) - { - self->iter = PyObject_GetIter( scope->f_locals ); - if ( !self->iter ) - return 0; - } - cppy::ptr item( PyIter_Next( self->iter ) ); + if ( !DynamicScopeIterator_next_from( self, scope->f_locals, item ) ) + return 0; if ( item ) return item.release(); - Py_CLEAR( self->iter ); self->state = ScopeIterator::FGlobals; // Fall through } case ScopeIterator::FGlobals: { - if ( !self->iter ) - { - self->iter = PyObject_GetIter( scope->f_globals ); - if ( !self->iter ) - return 0; - } - cppy::ptr item( PyIter_Next( self->iter ) ); + if ( !DynamicScopeIterator_next_from( self, scope->f_globals, item ) ) + return 0; if ( item ) return item.release(); - Py_CLEAR( self->iter ); self->state = ScopeIterator::FBuiltins; // Fall through } case ScopeIterator::FBuiltins: { - if ( !self->iter ) - { - self->iter = PyObject_GetIter( scope->f_builtins ); - if ( !self->iter ) - return 0; - } - cppy::ptr item( PyIter_Next( self->iter ) ); + if ( !DynamicScopeIterator_next_from( self, scope->f_builtins, item ) ) + return 0; if ( item ) return item.release(); - Py_CLEAR( self->iter ); - self->state = ScopeIterator::ParentTypeDict; + self->state = ScopeIterator::Parent; self->parent = cppy::incref( scope->owner ); // Fall through } - case ScopeIterator::ParentTypeDict: - { - if ( !self->iter ) - { - if ( !self->parent ) - break; - cppy::ptr dict( PyType_GetDict( Py_TYPE( self->parent ) ) ); - if ( !dict ) - return 0; - self->iter = PyObject_GetIter( dict.get() ); - if ( !self->iter ) - return 0; - } - - cppy::ptr item( PyIter_Next( self->iter ) ); - if ( item ) - return item.release(); - Py_CLEAR( self->iter ); - self->state = ScopeIterator::ParentInstanceDict; - // Fall through - } - case ScopeIterator::ParentInstanceDict: + case ScopeIterator::Parent: { if ( !self->parent ) break; - if ( !self->iter ) - { - PyObject**dictptr = _PyObject_GetDictPtr( self->parent ); - if ( dictptr && *dictptr ) { - self->iter = PyObject_GetIter( *dictptr ); - if ( !self->iter ) - return 0; - } // else no instance dict - } - - if ( self->iter ) - { - cppy::ptr item( PyIter_Next( self->iter ) ); - if ( item ) - return item.release(); - Py_CLEAR( self->iter ); - } - - // Walk up to parent + if ( !DynamicScopeIterator_next_from( self, self->parent, item ) ) + return 0; + if ( item ) + return item.release(); Py_SETREF( self->parent, PyObject_GetAttr(self->parent, parent_str) ); - self->state = ScopeIterator::ParentInstanceDict; return DynamicScopeIterator_next( self ); } } @@ -1212,13 +1206,22 @@ dynamicscope_modexec( PyObject *mod ) { return -1; } + self_str = PyUnicode_FromString( "self" ); if( !self_str ) return -1; + change_str = PyUnicode_FromString( "change" ); if( !change_str ) return -1; + cppy::ptr builtins( PyImport_ImportModule("builtins") ); + if( !builtins ) + return -1; + dir = builtins.getattr("dir"); + if( !dir ) + return -1; + UserKeyError = PyErr_NewException( "dynamicscope.UserKeyError", 0, 0 ); if( !UserKeyError ) { diff --git a/tests/core/test_dynamicscope.py b/tests/core/test_dynamicscope.py index 58b1b3578..5032132fc 100644 --- a/tests/core/test_dynamicscope.py +++ b/tests/core/test_dynamicscope.py @@ -52,6 +52,7 @@ def __init__(self): self._parent = None self.attribute1 = 1 self._prop2 = 0 + self._top = 0 self.should_raise = True owner = NonDataDescriptor() @@ -255,13 +256,6 @@ def test_dynamicscope_mapping(dynamicscope): assert "attribute1" in list(dynamicscope) keys = { - "__dict__", - "__doc__", - "__firstlineno__", - "__init__", - "__module__", - "__static_attributes__", - "__weakref__", "_parent", "_prop2", "a", @@ -279,8 +273,13 @@ def test_dynamicscope_mapping(dynamicscope): "prop2", "write_only", "should_raise", + "top" } - assert set(dynamicscope.keys()) == keys + # There is a bunch of __...__ we don't care about' + assert not keys.difference(set(dynamicscope.keys())) + all_keys = list(dynamicscope) + print(all_keys) + assert not keys.difference(set(all_keys)) # These cause errors... owner.should_raise = False @@ -299,8 +298,6 @@ def test_dynamicscope_mapping(dynamicscope): dynamicscope.update({1: 2}) # invalid key type keys.add("x") - assert {k for k in dynamicscope} == keys - assert dict(dynamicscope.items())["x"] == "y" @pytest.fixture