Skip to content

Commit

Permalink
Use dir, and set to exclude duplicate and __x__ attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
frmdstryr committed Jan 29, 2025
1 parent ea8b0dc commit e666cd3
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 87 deletions.
157 changes: 80 additions & 77 deletions enaml/src/dynamicscope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ namespace ScopeIterator
FLocals,
FGlobals,
FBuiltins,
ParentTypeDict,
ParentInstanceDict,
Parent,
};

}
Expand All @@ -84,6 +83,7 @@ struct DynamicScopeIterator
PyObject* scope;
PyObject* parent;
PyObject* iter;
PyObject* used;
ScopeIterator::State state;

static PyType_Spec TypeObject_Spec;
Expand All @@ -96,6 +96,7 @@ namespace
{


static PyObject* dir;
static PyObject* parent_str;
static PyObject* self_str;
static PyObject* change_str;
Expand Down Expand Up @@ -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;
}

Expand All @@ -573,6 +579,7 @@ DynamicScopeIterator_clear( DynamicScopeIterator* self )
Py_CLEAR( self->scope );
Py_CLEAR( self->parent );
Py_CLEAR( self->iter );
Py_CLEAR( self->used );
}


Expand All @@ -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;
}

Expand All @@ -601,26 +609,67 @@ 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<DynamicScope*>( self->scope );
switch( self->state )
{
case ScopeIterator::FWrites:
{
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
Expand All @@ -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 );
}
}
Expand Down Expand Up @@ -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 )
{
Expand Down
17 changes: 7 additions & 10 deletions tests/core/test_dynamicscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit e666cd3

Please sign in to comment.