Emulating !ActionScript's "with" statement in Python
If you haven't had much to do with ActionScript or Visual Basic, right now you might be thinking, as I did, "huh?" Basically the with statement in both of these languages allows one to access the attributes of an object as if they were in the local scope.
I know, I know. Why would you want to do this? I have no idea, but I thought it was cool that it was even possible in Python. However, there are some fairly major limitations:
- It only works in a global scope (I neglected to mention this originally, apologies - see #74 for details). One can s/f_locals/f_globals/g but this introduces its own problems.
- It only works for objects with a mutable __dict__.
I also thought the approach of injecting symbols into the context could be useful for providing DSL-like features. eg.
with query('a', 'b', 'c') as result: SELECT(a.foo, b.bar, c.baz) WHERE(AND(a.foo == 10, b.bar == 20)) ORDER_BY(a.foo) GROUP_BY(b.bar) for row in result: print row.foo, row.bar, row.baz
etc.
But enough speculation, here is an actual example. It requires at least Python 2.5.
And finally, here's the actual code.
from __future__ import with_statement import inspect class scope(object): """A context that maps an objects attributes into the current lexical scope, only for the duration of the context. NB. Properties will NOT work. >>> import math >>> with scope(math): ... print sqrt(9) 3.0 >>> class Vector: ... def __init__(self, x, y): ... self.x = x ... self.y = y ... def length(self): ... return math.sqrt(self.x ** 2 + self.y ** 2) >>> v = Vector(3, 3) >>> with scope(v): ... print x, '%0.2f' % length() ... x = 4 ... print x, '%0.2f' % length() 3 4.24 4 5.00 >>> print v.x, v.y, v.length() 4 3 5.0 >>> print x Traceback (most recent call last): ... NameError: name 'x' is not defined """ def __init__(self, obj): self.members = dict(inspect.getmembers(obj)) self.obj = obj def __enter__(self): parent = inspect.currentframe().f_back # Preserve parent locals() and the objects dictionaries self.old_locals = dict(parent.f_locals) self.old_dict = self.obj.__dict__ # Update locals with object members parent.f_locals.update(self.members) # If possible, replace the object's __dict__ with our updated locals. # Functions on the object that operate on the attributes will then # continue to work as expected. The downside is that all other objects # in the parents scope will be included as well. try: self.obj.__dict__ = parent.f_locals self.fake_dict = True except (TypeError, AttributeError): self.fake_dict = False def __exit__(self, type, value, traceback): parent = inspect.currentframe().f_back if self.fake_dict: object_dict = self.old_dict else: object_dict = self.obj.__dict__ # Replicate member state from locals to object's __dict__. for key in self.members: if key in parent.f_locals: object_dict[key] = parent.f_locals[key] if key not in self.old_locals: del parent.f_locals[key] else: del object_dict[key] if self.fake_dict: self.obj.__dict__ = object_dict del self.old_locals del self.old_dict return False if __name__ == '__main__': import doctest doctest.testmod()
Type masquerading using dynamic base classes
Python allows us to construct new classes on-the-fly with type(). By (ab)using this feature we can construct a class that preserves its own interface while assuming all of the behaviour of an existing object. Useful for wrapping compound types such as dictionaries, lists, sets, etc. without having to proxy __getitem__, __iter__ and so on, or write a custom __getattr__.
One example of when this could be useful is a JSON-specific HTTP client object that assumes the type of the JSON data, while maintaining response-specific attributes such as headers and status:
import simplejson class Response(object): """A response object that masquerades as the decoded content type.""" def __new__(cls, content=None, headers=None, status=None): if content is not None: content = simplejson.loads(content) assumed_type = type(content) bases = (Response, assumed_type) name = assumed_type.__name__.title() + 'Response' cls = type(name, bases, {}) self = assumed_type.__new__(cls, content) self.assumed_type = assumed_type else: self = object.__new__(cls) return self def __init__(self, content=None, headers=None, status=None): if content is not None: super(Response, self).__init__(content) self.headers = headers self.status = status json_data = [ '{"foo": 1, "bar": 2}', '123', '123.5', '["foo", "bar"]', ] for data in json_data: response = Response(data, headers=[('Content-Type', 'application/json')], status=200) decoded = simplejson.loads(data) print response print ' Same type?', isinstance(response, type(decoded)) try: print ' Iteration:', print [i for i in response] except TypeError: print '(type does not support iteration)' print ' Headers:', response.headers print ' Status:', response.status print
Outputs this:
{u'foo': 1, u'bar': 2}
Same type? True
Iteration: [u'foo', u'bar']
Headers: [('Content-Type', 'application/json')]
Status: 200
123
Same type? True
Iteration: (type does not support iteration)
Headers: [('Content-Type', 'application/json')]
Status: 200
123.5
Same type? True
Iteration: (type does not support iteration)
Headers: [('Content-Type', 'application/json')]
Status: 200
[u'foo', u'bar']
Same type? True
Iteration: [u'foo', u'bar']
Headers: [('Content-Type', 'application/json')]
Status: 200

rss