|
6 | 6 |
|
7 | 7 | .. _topics-address-tools: |
8 | 8 |
|
9 | | -:mod:`address_tools` - documentation not written |
10 | | -====================================== |
| 9 | +:mod:`address_tools` |
| 10 | +==================== |
| 11 | + |
| 12 | +The problem that :mod:`address_tools` was originally designed to solve was |
| 13 | +getting the "address" of a class, and possibly shortening it to an equivalent |
| 14 | +but shorter string. But after I implemented that, I realized that this could be |
| 15 | +generalized into a pair of functions, :func:`address_tools.describe` and |
| 16 | +:func:`address_tools.resolve`, that can replace the built-in :func:`repr` and |
| 17 | +:func:`eval` functions. |
| 18 | + |
| 19 | +So, Python has two built-in functions called :func:`repr` and :func:`eval`. You |
| 20 | +can say that they are opposites of each other: :func:`repr` "describes" a |
| 21 | +Python object as a string, and :func:`eval` evaluates a string into a Python |
| 22 | +object. |
| 23 | + |
| 24 | +*When is this useful?* This is useful in various cases: For example when you |
| 25 | +have a GUI program that needs to show the user Python objects and let him |
| 26 | +manipulate them. As a more well-known example, Django uses something like |
| 27 | +:func:`eval` to let the user specify functions without importing them, both in |
| 28 | +``settings.py`` and ``urls.py``. |
| 29 | + |
| 30 | +In some easy cases, :func:`repr` and :func:`eval` are the exact converses of |
| 31 | +each other: |
| 32 | + |
| 33 | + >>> repr([1, 2, 'meow', {3: 4}]) |
| 34 | + "[1, 2, 'meow', {3: 4}]" |
| 35 | + >>> eval( |
| 36 | + ... repr( |
| 37 | + ... [1, 2, 'meow', {3: 4}] |
| 38 | + ... ) |
| 39 | + ... ) |
| 40 | + [1, 2, 'meow', {3: 4}] |
| 41 | + |
| 42 | +When you put a simple object like that in :func:`repr` and then put the |
| 43 | +resulting string in :func:`eval`, you get the original object again. That's |
| 44 | +really pretty, because then we have something like a one-to-one correspondence |
| 45 | +between objects and strings used to describe them. |
| 46 | + |
| 47 | +In a happy-sunshine world, there would indeed be a perfect one-to-one mapping |
| 48 | +between Python objects and strings that describe them. You got a Python object? |
| 49 | +You can turn it into a string so a human could easily see it, and the string |
| 50 | +will be all the human will need to create the object again. But unfortunately |
| 51 | +some objects just can't be meaningfully described as a string in a reversible |
| 52 | +way: |
| 53 | + |
| 54 | + >>> import threading |
| 55 | + >>> lock = threading.Lock() |
| 56 | + >>> repr(lock) |
| 57 | + '<thread.lock object at 0x00ABF110>' |
| 58 | + >>> eval(repr(lock)) |
| 59 | + Traceback (most recent call last): |
| 60 | + File "", line 1, in |
| 61 | + invalid syntax: , line 1, pos 1 |
| 62 | + |
| 63 | +A `lock object`_ is used for synchronization between threads. You can't really |
| 64 | +describe a lock in a string in a reversible way; a lock is a breathing, living |
| 65 | +thing that threads in your program interact with, it's not a data-type like a |
| 66 | +list or a dict. |
| 67 | + |
| 68 | +So when we call :func:`repr` on a lock object, we get something like |
| 69 | +``'<thread.lock object at 0x00ABF110>'``. Enveloping the text with pointy |
| 70 | +brackets is Python's way of saying, "you can't turn this string back into an |
| 71 | +object, sorry, but I'm still going to give you some valuable information about |
| 72 | +the object, in the hope that it'll be useful for you." This is good behavior on |
| 73 | +Python's part. We may not be able to use :func:`eval` on this string, but at |
| 74 | +least we got some info about the object, and introspection is a *very* useful |
| 75 | +ability. |
| 76 | + |
| 77 | +So some objects, like lists, dicts and strings, can be easily described by |
| 78 | +:func:`repr` in a reversible way; some objects, like locks, queues, and file |
| 79 | +objects, simply cannot by their nature; and then there are the objects in |
| 80 | +between. |
| 81 | + |
| 82 | + |
| 83 | +Classes, functions, methods, modules |
| 84 | +------------------------------------ |
| 85 | + |
| 86 | + |
| 87 | +What happens when we run :func:`repr` for a Python class? |
| 88 | + |
| 89 | + >>> import decimal |
| 90 | + >>> repr(decimal.Decimal) |
| 91 | + "<class 'decimal.Decimal'>" |
| 92 | + |
| 93 | +We get a pointy-bracketed un-\ ``eval``\ -able string. How about a function? |
| 94 | + |
| 95 | + >>> import re |
| 96 | + >>> repr(re.match) |
| 97 | + '<function match at 0x00E8B030>' |
| 98 | + |
| 99 | +Same thing. We get a string that we can't put back in :func:`eval`. Is this really necessary? Why not return ``'decimal.Decimal'`` or ``'re.match'`` so we could :func:`eval` those later and get the original objects? |
| 100 | + |
| 101 | +It *is* sometimes helpful that the :func:`repr` string ``"<class |
| 102 | +'decimal.Decimal'>"`` informs us that this is a class; but sometimes you want a |
| 103 | +string that you can turn back into an object. Although... :func:`eval` might |
| 104 | +not be able to find it, because :mod:`decimal` might not be currently imported. |
| 105 | + |
| 106 | +Enter :mod:`address_tools`: |
| 107 | + |
| 108 | + |
| 109 | +:func:`address_tools.describe` and :func:`address_tools.resolve` |
| 110 | +---------------------------------------------------------------- |
| 111 | + |
| 112 | +Let's play with :func:`address_tools.describe` and :func:`address_tools.resolve`: |
| 113 | + |
| 114 | + >>> from python_toolbox import address_tools |
| 115 | + >>> import decimal |
| 116 | + >>> address_tools.describe(decimal.Decimal) |
| 117 | + 'decimal.Decimal' |
| 118 | + |
| 119 | +That's a nice description string! We can put that back into :func:`resolve <address_tools.resolve>` and get the original class: |
| 120 | + |
| 121 | + >>> address_tools.resolve(address_tools.describe(decimal.Decimal)) is decimal.Decimal |
| 122 | + True |
| 123 | + |
| 124 | +We can use :func:`resolve <address_tools.resolve>` to get this function, without :mod:`re` being imported, and it will import :mod:`re` by itself: |
| 125 | + |
| 126 | + >>> address_tools.resolve('re.match') |
| 127 | + <function match at 0x00B5E6B0> |
| 128 | + |
| 129 | +This shtick also works on classes, functions, methods, modules, and possibly |
| 130 | +other kinds of objects. |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | +.. _lock object: http://docs.python.org/library/threading.html#lock-objects |
0 commit comments