How safe is modifying locals()?

Bengt Richter bokr at oz.net
Sat Jul 26 18:03:59 EDT 2003


On Sat, 26 Jul 2003 15:20:24 GMT, Paul Paterson <paulpaterson at users.sourceforge.net> wrote:

>Stefan Schwarzer wrote:
>
>> Hi Paul
>> 
>> Paul Paterson wrote:
>> 
>>> This is a very interesting (and Pythonic) approach, thanks for 
>>> suggesting it! This is certainly what I would try to do if I were 
>>> writing the code from scratch. It may be possible to construct a 
>>> "namespace" type object which gets passed to the function.
>> 
>> 
>> I think the most common way to make a namespace in Python is
>
><snip nice example of namespace class>
>
>Thanks, the bare namespace class is essentially what I am going to try.
>
>> 
>> Of course, if you have a better name for your container, that's even
>> better. Think of Ian's Point example. :-)
>
>I think you may have missed my original post; I am writing a generic VB 
>to Python converter so generating good names based on code intent would 
>be pretty tough! Perhaps in v2.0 ;)
>
>My current thinking is (for the orignal example of changing one 
>variable, 'y' but not another, 'x'),
>
>class Namespace: pas
>
># ByVal arguments passed directly, ByRef via a namespace
>def change(x, byrefs):
>     x = x + 1
>     byrefs.y = byrefs.y + 1
>
>ns = Namespace() # The local namespace
>ns.x = 0
>ns.y = 0
>change(ns.x, ns)
># Now ns.x = 0, ns.y = 1
>
This looks readable, to me ;-)

But it is not pointer semantics, since you have to know the name for y inside change.
It will be nice and fast if you can use the above, but if not, in some cases, class NSP
below will let you use it just like Namespace above, though with pointer features if
you need them.

I just wanted to throw in here that the namespace approach will also let you
use properties (there's a handy builtin to create them from accessor methods,
or you can instantiate them from your own descriptor classes) as your
byrefs "variables" if you like.

You just have to mane the NameSpace class inherit from object, to make it
a new-style class. Then you can add properties either right in the class definition
or later by setting class attributes , e.g.,

 >>> class Namespace(object): pass
 ...
 >>> ns = Namespace()
 >>> ns.x = 123
 >>> Namespace.p = property(lambda self:'Read is only access function specified here')

Note that the above was *not* setting an instance attribute with ns.p = property(...)

 >>> ns.x
 123
 >>> ns.p
 'Read is only access function specified here'
 >>> ns.x = 456
 >>> ns.p = 789
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AttributeError: can't set attribute
 >>> del ns.p
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AttributeError: can't delete attribute

So you can define a kind of constant, or have any side effect you want for setting, getting,
and deleting.

Note that normal class attributes are shadowed by instance attributes, e.g.,

 >>> ns.x # from last assignment
 456
 >>> Namespace.x = '456000'
 >>> ns.x
 456
 >>> del ns.x  # deletes ordinary instance attribute
 >>> ns.x      # and if instance attribute is absent, it will be looked for in class
 '456000'

Actually it is a little more complicated than that, since it has to be determined that
there is not a data descriptor/property in the class overriding the instance variable access.
That's why I said "normal" ;-)

A namespace for variables also gives you the potential to define methods for the name space
itself. Here is an example with a name space that has an internal pointer class and lets
you create "pointers" to the "variables" in the namespace, using ns[vname] syntax, which
goes to the __getitem__ method of the class to make a "pointer" (__slots__ hopefully just
makes pointer instances better optimized):

 >>> class NSP(object):
 ...     """NSP defines name space with "pointer" creation via p = ns[vname]"""
 ...     class Ptr(object):
 ...         """Ptr instance holds ns ref and vname for access to ns.vname"""
 ...         __slots__ = 'ns', 'vname'
 ...         def __init__(self, ns, vname): self.ns=ns; self.vname=vname
 ...         def __getitem__(self, i):
 ...             """typical: x=p[:]"""
 ...             return getattr(self.ns, self.vname)
 ...         def __setitem__(self, i, v):
 ...             """Typical: p[:] = x"""
 ...             setattr(self.ns, self.vname, v)
 ...         def __delitem__(self, i):
 ...             """Typical: del p[:] # deletes what's pointed to"""
 ...             delattr(self.ns, self.vname)
 ...     def __getitem__(self, name):
 ...         """Make "pointer." Typical: p2x = ns['x']; p2x[:] => ns.x"""
 ...         return self.Ptr(self, name)
 ...
 >>> ns = NSP()
 >>> ns.x = 123
 >>> ns.y = 456
 >>> def change(x, py):
 ...     x = 'new x?'
 ...     py[:] = 'new via py[:]'
 ...
 >>> ns.x
 123
 >>> ns.y
 456
 >>> change(ns.x, ns['y']) # on the fly &y
 >>> ns.x
 123
 >>> ns.y
 'new via py[:]'
 >>>

You can also save a "pointer" and use it later, or pass it around:

 >>> px = ns['x']
 >>> px[:]
 123

( [:] is just sugar, since the arg is ignored. [0] will run faster, since no slice object is needed)

 >>> px[0]
 123
 >>> px[0] = 'new x value'
 >>> ns.x
 'new x value'
 >>>

Establish some new values:

 >>> ns.x = 123; ns.y = 456
 >>> ns.x, ns.y
 (123, 456)

An important distinction from using explicit byref attribute names (e.g. byref.y) in change():

 >>> change('byvalue', px) # note we are passing px in the py parameter position
 >>> ns.x, ns.y
 ('new via py[:]', 456)

I.e., change was passed a "pointer" it thought of  as pointing to y (i.e., was named "py"
by the programmer to suggest the meaning), but it pointed to x, and
so x changed when py[:] was assigned to. Changes to 'byvalue' were just local to change, as expected.

We can also delete an attribute via the pointer:
 >>> del px[0]
 >>> ns.x
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 AttributeError: 'NSP' object has no attribute 'x'

which doesn't affect the "pointer" itself:
 >>> px[0]='an x again'
 >>> ns.x
 'an x again'

but of course deleting the pointer itself as opposed to px[0] will:
 >>> del px
 >>> ns.x
 'an x again'
 >>> px
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 NameError: name 'px' is not defined

i.e., the pointer got deleted, not the thing pointed to.

I'm not necessarily recommending any of the above, but I thought it might give you some ideas.
There is definitely a performance hit in px[:] (or better, px[0]) vs byref.x to consider.

Anyway, I thought properties and descriptors might also come in handy somewhere in your quest ;-)

Raymond Hettinger has written a nice "How-To Guide for Descriptors":

    http://users.rcn.com/python/download/Descriptor.htm

and for properties (follow Table Of Contents link in below page) and much more there is

    http://www.python.org/2.2/descrintro.html

HTH

Regards,
Bengt Richter




More information about the Python-list mailing list