relation class

Aaron Brady castironpi at gmail.com
Fri Apr 24 08:18:29 CEST 2009


On Apr 22, 11:34 pm, Aaron Brady <castiro... at gmail.com> wrote:
> On Apr 22, 11:52 am, Aaron Brady <castiro... at gmail.com> wrote:
>
> > On Apr 22, 12:09 am, Chris Rebert <c... at rebertia.com> wrote:
>
> > > On Tue, Apr 21, 2009 at 5:51 PM, Aaron Brady <castiro... at gmail.com> wrote:
> > > > Hi all,
>
> > > > I think Python should have a relation class in the standard library.
> > > > Fat chance.
>
> > > Perhaps I'm not understanding "relation" correctly, but are you not
> > > aware ofhttp://docs.python.org/library/sqlite3.html?
>
> > > Cheers,
> > > Chris
> > > --
> > > I have a blog:http://blog.rebertia.com
> snip
> > It only supports numbers and strings.
>
> snip
>
> My point is that in undirected relations, it's redundant to maintain
> reciprocal membership.
snip
> Here is another sample:
>
> Tree= Relation( ( "parent", "child", "direction" ) )
> nodes= [ object( ) for _ in range( 10 ) ]
> Tree( ( nodes[ 0 ], nodes[ 1 ], "left" ) )
> Tree( ( nodes[ 0 ], nodes[ 2 ], "right" ) )
snip
> 'select' would need the context
> of its caller, which isn't available.
snip

Or, pass 'locals()'.  Actually, I discovered an interesting catch to
'locals()'.  When you don't use a variable that's in an outer scope in
a function, it doesn't appear in 'locals()'.  However, if you just use
it in a blank statement, it magically appears.

>>> def f( ):
...     x= []
...     def g( ):
...             print( locals( ) )
...     g( )
...
>>> f( )
{}
>>> def f( ):
...     x= []
...     def g( ):
...             x # empty use of 'x'
...             print( locals( ) )
...     g( )
...
>>> f( ) # changes the contents of 'locals()'
{'x': []}

Here is what the docs have to say about *that*:

"Free variables are returned by locals() when it is called in a
function block."

Since 'x' doesn't appear in the function, 'g' in this case, it isn't
included in 'g's free variables.  'eval' actually therefore doesn't
return exactly the result of evaluating a string where it's used.

>>> def f( ):
...     x= []
...     def g( ):
...             print( eval( 'x' ) )
...     g( )
...
>>> f( )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in f
  File "<stdin>", line 4, in g
  File "<string>", line 1, in <module>
NameError: name 'x' is not defined

The 'eval' function docs do assert otherwise though:

"...the expression is executed in the environment where eval() is
called."

'eval' doesn't obviously keep its contract as shown.

> I think some kind of markers would have to replace any identifiers it
> would use:
>
> recordset= Tree.select( [ "child" ],
>     "parent is %var and direction=='left'", nodes[0] )
snip

The 'sqlite3' module just uses a question mark.  Despite the fact that
variables are separated from exactly where they are used, it has the
advantage, as S. D'Aprano pointed out nearby, of being able to use
queries independently of them, as well as of being legal Python.

If anyone is still reading, there is a further nifty-cool construct
that the 'Relation' class can offer.  Given the earlier definition of
'Tree' as an instance of 'Relation', a new class can combine property
descriptors with the select statement.

class TreeNode:
    relation= Tree
    left= Relation.getter(
        "child", "parent is ? and direction=='left'" )

nodes= [ TreeNode( ) for _ in range( 10 ) ]
record= nodes[ 0 ].left

Getting the 'left' member of an instance of this class is just
syntactic sugar for a long-hand query.  It only returns one field of
an arbitrary element of the recordset.  It only allows one '?' in the
definition, since getter functions only take one argument: self!
Here's the definition of 'getter':

    @staticmethod
    def getter( field, where ):
        def _getter( self ):
            return getattr( next(
                self.relation.select( '*', where, self ) ), field )
        return property( fget= _getter )

It could be an attribute of the module, not specifically of the
Relation class... which might even be advisable.  You could even
combine the 'relation' member into the call to 'getter', so 'getter'
wouldn't have to retrieve it as 'self.relation'.  That has the
disadvantage of increasing repetition of the relation in the class
statement, but enables a class to participate in multiple relations.
Come to think of it, you could write it as 'Tree.getter', and
compromise.  Then 'getter' becomes:

    def getter( self, field, where ):
        def _getter( ob ):
            return getattr( next(
                self.select( '*', where, ob ) ), field )
        return property( fget= _getter )

and 'TreeNode' becomes:

class TreeNode:
    left= Tree.getter(
        "child", "parent is ? and direction=='left'" )

You might want 'getter', 'getiter', and 'getone' methods, which return
a set of, an iterator over, and just the field of an arbitrary one of
the matching records respectively.

Proof-of-concept isn't even complete; but nonetheless, pretty cool,
eh?



More information about the Python-list mailing list