How safe is modifying locals()?

Bengt Richter bokr at oz.net
Sun Jul 27 13:02:04 EDT 2003


On Sun, 27 Jul 2003 04:51:11 GMT, Paul Paterson <paulpaterson at users.sourceforge.net> wrote:

[...]

>To my eye, the [:] or [0] spelling of this makes the code look more 
>complex than necessary, but I think you are on to something because if 
>you spell it,
>
>def change(x, y):
>    x = 'new x'
>    y.update('new y')
>

For a general pointer, ISTM you want to be able to dereference it for both getting and setting.
The [:] or [0] syntax gets you that, and you could do it with p() for getting and p(val) for setting,
or p.getval() and p.update(), but all these get clumsy when you want to use the "pointer" on
both sides of an assignment statement, e.g.,

    p.update(p.getval()+' added text for target')

where you could write more readably (IMO),

    p.value = p.value + 'added text for target'

or
    p.v += 'added text for target'


Below is a class PNS that lets you spell as immediately above, using .value
(and .v for concise use) bound to a property that implements the accessing of
what's pointed/referred to. I gave it a somewhat informative __repr__ method also,
so the test prints better. As you will note, this is now separate from any particular
name space. Any object that supports getattr and/or setattr can be used. I also
threw in a permissions parameter to control read/write/delete. See nsother and math
in examples. Note also that a pointer may point to another pointer, allowing cascaded
dereferencing spelled p.v.v etc.

>with the relevant changes to the Ptr class then it could certainly grow 
>on me. The things I like are,
>
>- no new variable names in the 'change' function so it looks similar to 
>the original code
>- the mechanism for propogating changes to the caller scope is explicit
>- 'y' can be passed on to another function if needed and things are 
>still clear
>
>eg,
>
>def change(x, y):
>     x = 'new x'
>     change2(y)
>
>def change2(y):
>     y.update('deep change in y')
>
If you use PNS, that will be spelled

 def change(x, y):
      x = 'new x'
      change2(y)
 
 def change2(y):
      y.value = 'deep change in y'

See test() code below. I used your original class Namespace: pass as the main namespace.

>
>To do this using the original namespace approach gets a little tricky 
>because you have to merge the namespaces as you go. The pointer idea 
>flattens that structure.
>

====< pns.py >==========================================================
class PNS(object):
    """
    Pointer to Name Space
    PNS instance holds ns ref and vname for access to ns.vname
    Read, write, delete access permitted if r,w,d in perms, respectively.
    Typical: ptr_to_x = PNS(ns, 'x')
    """
    __slots__ = 'ns vname ok_r ok_w ok_d'.split()
    class PNSError(Exception):
        def __init__(self, ptr, msg):
            Exception.__init__(self, '%s for %r' %(msg, ptr))
        
    def __init__(self, ns, vname, perms='rwd'):
        self.ns=ns; self.vname=vname
        self.ok_r = 'r' in perms
        self.ok_w = 'w' in perms
        self.ok_d = 'd' in perms
    def _getv(self):
        """Typical read access: x = ptr.value (x = ptr.v works too)"""
        if self.ok_r: return getattr(self.ns, self.vname)
        raise self.PNSError(self, 'Value read prohibited')
    def _setv(self, v):
        """Typical write access: ptr.value = 'new x' (ptr.v = 'new x' works too)"""
        if self.ok_w: setattr(self.ns, self.vname, v)
        else: raise self.PNSError(self, 'Value write prohibited')
    def _delv(self):
        """Typical del access: del ptr.value (del ptr.v works too)"""
        if self.ok_d: delattr(self.ns, self.vname)
        else: raise self.PNSError(self, 'Value deletion prohibited')
    value = v = property(_getv, _setv, _delv) # .v for short
    def __repr__(self): return '<PNS ptr to %r of %r>'%(self.vname, self.ns)

class Namespace(object): pass

def test():
    ns = Namespace()
    ns.x = 'x value'
    ns.y = 'y value' 
    print 'Before change:'
    print 'ns.x=%r, ns.y=%r' %(ns.x, ns.y)
    print 'Making pointer py point to ns.y ...'
    py = PNS(ns, 'y') # prefixing pointer names with 'p' is not mandatory, just mnemonic
    print 'ptr py=%r' % py
    
    def change(x, y): # prefixing pointer names with 'p' is not mandatory, just mnemonic
        x = 'new x'
        y.value = 'new y'

    print 'Calling change(ns.x, py) ...'
    change(ns.x, py)
    print 'After change:'
    print 'ns.x=%r, ns.y=%r' %(ns.x, ns.y)
    
    def change1(x, y):
        x = 'new x'
        change2(y)
    
    def change2(y):
        y.v = 'deep change in y'

    print 'Before change1/change2:'
    print 'ns.x=%r, ns.y=%r' %(ns.x, ns.y)
    change1(ns.x, py)
    print 'After calling change1(ns.x, py):'
    print 'ns.x=%r, ns.y=%r' %(ns.x, ns.y)
    
    pz = PNS(ns, 'z')
    print '\nNew pointer to non-existent ns.z:\n%r' % pz
    print 'Trying to access as yet nonexistent ns.z ...'
    try: ns.z
    except Exception, e: print '%s: %s'% (e.__class__.__name__,e)
    else: print 'ns.z accessed ok'
   
    print 'Passing pz to change(ns.x, pz)...'
    change(ns.x, pz)
    print 'Result: ns.x=%r, ns.z=%r' %(ns.x, ns.z)
    print '\nBefore deleting ns.y via py.v:'
    print 'ns.y=%r, py.v=%r' %(ns.y, py.v)
   
    print '\nDeleting ns.y by del py.value ...'
    del py.value
    print 'Trying to access ns.y ...'
    try: ns.y
    except Exception, e: print '%s: %s'% (e.__class__.__name__,e)
    else: print 'ns.y accessed ok'
   
    print '\nCreating nsother name space to put pz in as a value ...'
    nsother = type('AnotherNS',(),{})()
    print nsother
    nsother.pz = pz
    print 'Creating pointer ppz pointing to nsother.pz'
    ppz = PNS(nsother,'pz')
    print 'ppz = %r'% ppz
    print 'ppz.value = %r'% ppz.value
    print 'ppz.value.value = %r'% ppz.value.value
    print 'ppz.v.v= %r'% ppz.v.v
   
    print '\nDemo read-only pointer to pi in namespace math (the module) ...'
    import math
    ppi = PNS(math,'pi', 'r') # read only
    print 'math = %r' % math
    print 'ppi = %r' % ppi
    print 'ppi.v = %s' % ppi.v

    print '\nAttempting to set math.pi via ppi.v=3.14 (will work via math.pi, BTW !)'
    try: ppi.v = 3.14
    except Exception, e: print '%s: %s'% (e.__class__.__name__,e)
    else: print 'ppi.v set ok: %r' % ppi.v

if __name__ == '__main__':
    test()
========================================================================

Result of run:

[10:03] C:\pywk\clp>pns.py
Before change:
ns.x='x value', ns.y='y value'
Making pointer py point to ns.y ...
ptr py=<PNS ptr to 'y' of <__main__.Namespace object at 0x007F9EC0>>
Calling change(ns.x, py) ...
After change:
ns.x='x value', ns.y='new y'
Before change1/change2:
ns.x='x value', ns.y='new y'
After calling change1(ns.x, py):
ns.x='x value', ns.y='deep change in y'

New pointer to non-existent ns.z:
<PNS ptr to 'z' of <__main__.Namespace object at 0x007F9EC0>>
Trying to access as yet nonexistent ns.z ...
AttributeError: 'Namespace' object has no attribute 'z'
Passing pz to change(ns.x, pz)...
Result: ns.x='x value', ns.z='new y'

Before deleting ns.y via py.v:
ns.y='deep change in y', py.v='deep change in y'

Deleting ns.y by del py.value ...
Trying to access ns.y ...
AttributeError: 'Namespace' object has no attribute 'y'

Creating nsother name space to put pz in as a value ...
<__main__.AnotherNS object at 0x007F92C0>
Creating pointer ppz pointing to nsother.pz
ppz = <PNS ptr to 'pz' of <__main__.AnotherNS object at 0x007F92C0>>
ppz.value = <PNS ptr to 'z' of <__main__.Namespace object at 0x007F9EC0>>
ppz.value.value = 'new y'
ppz.v.v= 'new y'

Demo read-only pointer to pi in namespace math (the module) ...
math = <module 'math' (built-in)>
ppi = <PNS ptr to 'pi' of <module 'math' (built-in)>>
ppi.v = 3.14159265359

Attempting to set math.pi via ppi.v=3.14 (will work via math.pi, BTW !)
PNSError: Value write prohibited for <PNS ptr to 'pi' of <module 'math' (built-in)>>

[10:03] C:\pywk\clp>

>
>Thanks for these thoughts and the time it took to post them, they really 
>made me think! (I mean that in a good way, of course ;) )
>
You're welcome. Hope this adds another useful angle.

Regards,
Bengt Richter




More information about the Python-list mailing list