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