2008-01-29
An annoyance in Python's attribute access rules
When looking up names on objects, I really wish that Python had a special attribute accessor for looking up attributes it was about to call, instead of only running them through the regular attribute lookup chain.
While this uniformity probably has little effects on most
code, it complicates the life of things such as proxy objects that
trace and monitor access to objects, which sometimes want to behave
differently for 'var = you.foo' than for 'var = you.foo()'. You can
fake this by returning a proxied foo object that has a __call__
method, but things can rapidly become complicated; for example, what
objects do you proxy this way and what objects do you return as is?
Unfortunately, changing this is one of those simple looking things
that strike to the heart of a language's conceptual model. Unless
you change Python's conceptual model of attribute access, a special
__getmethod__ accessor can only be a hack; it triggers only for
certain syntactic patterns of method calls, whereas the __call__
approach is fully general.
Sidebar: implementation complications
Adding such an attribute accessor would also have implementation
complications in the Python bytecode compiler and interpreter. Right
now, all object attributes are retrieved with a single bytecode
instruction, LOAD_ATTR, and the LOAD_ATTR that gets the function
object may be some distance from the actual calling of the function,
because the bytecode is stack based and puts the function arguments on
the top of the stack. The easiest way out would probably be to add a new
'load attribute to be called' instruction and make the compiler generate
it in the appropriate situation.
2008-01-28
One reason I like Python
One reason that I like Python is that I find it what I call a very 'computer science' language and I am a product of a CS environment. By this I mean that Python is mostly built from a small number of core concepts which are uniformly expanded through the language, and those core concepts are by and large familiar to me from my CS background.
(It also has a quite traditional Algol-style syntax, apart from the whitespace issue.)
Since its core concepts click for me, I find Python very regular and predictable; as I sometimes put it, Python thinks like I do. Things that I think should work generally do, and it winds up being easy to write most Python code, even code that does moderately odd things. And I found learning Python fairly easy, as once I understood a few core ideas things more or less just fell into place and I could extrapolate outwards.
(I suspect that this is one reason that the Python tutorial seems to work so well for people who like Python. If you have the CS style background and think like Python does, the tutorial is less teaching you things than showing you how Python's bits fit into what you already know. A longer, more verbose tutorial wouldn't just be unnecessary, it would be belabouring the point.)
2008-01-14
A Python pattern: Mutating Proxies
Over the course of the Python code that I've written, I've noticed certain patterns that seem to reoccur relatively frequently. One of my common ones is a pattern that I'll call the Mutating Proxy.
Python doesn't have much use for plain proxy objects, objects that just relay things to another one, since duck typing means that you can usually just use the original object instead. A mutating proxy is a proxy that's used in order to change the behavior of the underlying object, adding things or changing the behavior of existing things.
(For example, I have used a mutating proxy to give sockets a total
connect() to close() timeout, instead of just their timeout on
individual operations.)
Mutating proxies are sometimes but not always implemented as subclasses of the original object class. The classical reason not to subclass is if you don't generate the objects you want to proxy, so you have no good way to get them created as instances of your subclass. If you want to mutate the behavior of SSL sockets, for example, you will be wrapping them since they get handed to you by the underlying C code in the socket module.
(Another reason not to subclass is if you're only doing a partial implementation of your mutation. Since a real proxy passes only the bits you've implemented, attempts to use unsupported stuff will fail right away instead of sort of working.)