Skip to content

Commit 0fe4268

Browse files
committed
Merge remote-tracking branch 'origin/development'
2 parents beb52da + a7b3c7d commit 0fe4268

File tree

1 file changed

+126
-2
lines changed

1 file changed

+126
-2
lines changed

docs/topics/address-tools.txt

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,129 @@
66

77
.. _topics-address-tools:
88

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

Comments
 (0)