evan's original examples...

---

<div tal:define="foo string:*foo*;                                   
                 seq python:('arg', 3.4);                            
                 map python:{'*foo*': 'yes', 'keys': 'skeleton'}">   
  <div tal:content="seq/item:0"></div>                               
  <div tal:content="seq/item:1/fmt:%.2f"></div>                      
  <div tal:content="template/attr:title"></div>                      
  <div tal:content="here/key:test3/title"></div>                     
  <div tal:content="structure foo/fmt:structured_text"></div>        
  <div tal:content="map/key:keys"></div>                             
  <div tal:content="map/var:foo"></div>                              
</div>                                           


These first examples are pretty trivial, and can mostly be done today 
as python expressions with no difficulty.
For the fmt: and var: examples, i'll just hand-wave, I think what I mean 
is pretty clear...

<div tal:define="python:foo '*foo*';                                   
                 seq python:('arg', 3.4);                            
                 map python:{'*foo*': 'yes', 'keys': 'skeleton'}">   
  <div tal:content="python:seq[0]"></div>                               
  <div tal:content="python:'%.2f' % seq[0]"></div>                      
  <div tal:content="python:template.title"></div>                      
  <div tal:content="python:here['test3'].title"></div>                     
  <div tal:content="structure python:zope.fmt.structured_text(foo)"></div>
  <div tal:content="python:map['keys']"></div>                             
  <div tal:content="python:zope.var(map, 'foo')"></div>      
</div>                                           


Now for Evan's tricky examples:

 options/foo/item:0 | request/foo/item:0 | default
 data/stat/fmt:thousands_commas | string:No data. 


My solution using Python expressions:
First of all, provide a namespace that provides access to
API stuff. We can argue later about how this can be named & organized.
This namespace is always available in ZPT, and can be imported 
in Python Scripts.

Next, we provide a function that behaves a little bit like the
existing path() function -  
given some names, for each successive pair it tries to find 
foo[bar] and getattr(foo, bar) and if both fail, we don't raise the 
AttributeError or KeyError - we just return false (more on this below).  
This allows you to chain function calls with "or".  TALES has to be 
modified a bit for this to work as I intend, as described below.  

This path lookup function should just take, as arguments, any number
of ids to look up.  You can pass a sequence using the *args 
notation.  Note that since it just tries __getitem__ and __getattr__ 
at each step, it works fine with mappings (e.g. the request object).
I can't think of a good name so i'm calling it zget.


Examples:

 original: 
   options/foo/item:0 | request/foo/item:0 | default
 mine: 
   zget('options','foo')[0] or zget('request','foo')[0] or default

 original:
   data/stat/fmt:thousands_commas | string:No data.
 mine:
   zope.fmt.thousands_commas(zget('data','stat')) or 'No data'

Note a significant difference in this latter example: in Evan's version,
the thousands_commas call can be short-circuited if data/stat
does not exist, while in mine it cannot be avoided (it is
merely called on an empty string).

How this works: the "false" value returned by zget is a 
bit special - it's a lightweight "null object". 
e.g. __len__ returns 0, while __getitem__, __getattr__, and __call__ 
return None ...  and __str__ (and __repr__?) returns ''.  It would also 
keep an _exception attribute which would be the exception that 
led to its creation (e.g. the AttributeError).

TALES would then have to be modified such that if the end result of
a TALES expression is one of these special null objects, 
we re-raise its _exception. That way you still find out what the 
missing item was.