Skip to content

Commit 66a607f

Browse files
committed
-
1 parent beb52da commit 66a607f

File tree

1 file changed

+122
-0
lines changed

1 file changed

+122
-0
lines changed

docs/topics/address-tools.txt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,125 @@
88

99
:mod:`address_tools` - documentation not written
1010
======================================
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

Comments
 (0)