Unifying Attributes and Names (was: Re: Death to tuples!)

Christopher Subich csubich.spam.block at spam.subich.block.com
Tue Nov 29 11:23:32 EST 2005


Bengt Richter wrote:

> If we had a way to effect an override of a specific instance's attribute accesses
> to make certain attribute names act as if they were defined in type(instance), and
> if we could do this with function instances, and if function local accesses would
> check if names were one of the ones specified for the function instance being called,
> then we could define locally named constants etc like properties.
> 
> The general mechanism would be that instance.__classvars__ if present would make

Nah... you're not nearly going far enough with this.  I'd suggest a full 
unification of "names" and "attributes."  This would also enhance 
lexical scoping and allow an "outer" keyword to set values in an outer 
namespace without doing royally weird stuff.

In general, all lexical blocks which currently have a local namespace 
(right now, modules and functions) would have a __namespace__ variable, 
containing the current namespace object.  Operations to get/set/delete 
names would be exactly translated to getattr/setattr/delattrs.  Getattrs 
on a namespace that does not contain the relevant name recurse up the 
chain of nested namespaces, to the global (module) namespace, which will 
raise an AttributeError if not found.

This allows exact replication of current behaviour, with a couple 
interesting twists:
1) i = i+1 with "i" in only an outer scope acutally works now; it uses 
the outer scope "i" and creates a local "i" binding.
2) global variables are easily defined by a descriptor:
    def global_var(name):
       return property(
        lambda self: getattr(self.global,name),
        lambda (self, v): setattr(self.global,name,v),
        lambda self: delattr(self.global,name),
        "Global variable %s" % name)
3) "outer variables" under write access (outer x, x = 1) are also 
well-defined by descriptor (exercise left for reader).  No more weird 
machinations involving a list in order to build an accumulator function, 
for example.  Indeed, this is probably the primary benefit.
4) Generally, descriptor-based names become possible, allowing some 
rather interesting features[*]:
    i) "True" constants, which cannot be rebound (mutable objects aside)
   ii) Aliases, such that 'a' and 'b' actually reference the same bit, 
so a = 1 -> b == 1
  iii) "Deep references", such that 'a' could be a reference to my_list[4].
   iv) Dynamic variables, such as a "now_time" that implicitly expands 
to some function.
5) With redefinition of the __namespace__ object, interesting run-time 
manipulations become possible, such as redefining a variable used by a 
function to be local/global/outer.  Very dangerous, of course, but 
potentially very flexible.  One case that comes to mind is a "profiling" 
namespace, which tracks how often variables are accessed -- 
over-frequented variables might lead to better-optimized code, and 
unaccessed variables might indicate dead code.

[*] -- I'm not saying that any of these examples are particularly good 
ideas; indeed, abuse of them would be incredibly ugly.  It's just that 
these are the first things that come to mind, because they're also so 
related to the obvious use-cases of properties.

The first reaction to this is going to be a definite "ewwwww," and I'd 
agree; this would make Python names be non-absolute [then again, the 
__classvars__ suggestion goes nearly as far anyway].  But this 
unification does bring all the power of "instance.attribute" down to the 
level of "local_name".

The single biggest practical benefit is an easy definiton of an "outer" 
keyword: lexical closures in Python would then become truly on-par with 
use of global variables.  The accumulator example would become:
def make_accum(init):
    i = init
    def adder(j):
       outer i #[1]
       i += j
       return i
    return adder

[1] -- note, this 'outer' check will have to require that 'i' be defined 
in an outer namespace -at the time the definition is compiled-. 
Otherwise, the variable might have to be created at runtime (as can be 
done now with 'global'), but there's no obvious choice on which 
namespace to create it in: global, or the immediately-outer one?  This 
implies the following peculiar behaviour (but I think it's for the best):

 >>> # no i exists
 >>> def f(): # will error on definition
       outer i
       print i
 >>> def g(): # won't error
       print i
 >>> i = 1
 >>> f()
 >>> g()

Definitely a Py3K proposal, though.



More information about the Python-list mailing list