Factory functions: synthesizing top-level names

Creating Top-Level names root0, root1, root2... rootN Setting the stage: We've come across an ethnicity, in our voyages of the starship whatever, that doesn't share our fascination with squares and cubes. For example, when multiplying n * n, it never occurred to them to picture n * n squares in a checkerboard pattern. Rather, they have a more Chinese Checkers bias towards triangles and hexagons, and their picture of n * n is an equilateral triangle subdivided into n ** 2 similar sub-triangles. Likewise, their picture of n * n * n, or n**3 is a tetrahedron, though here the subdividing process results in a matrix known to Earthlings ("terra-ists") as the FCC and/or CCP and/or... whatever Earthling terminology. The Challenge: The anthropologist-astronauts we've left behind to work and study with these people (to be collected on a next pass a year from now, unless they plan on staying longer), are using Python, and find that saying 'sqrt' just gets in the way (keeps them habituated to patterns of thought they're trying to snap out of). What they'd like to do is this: for 2nd, 3rd, 4th, 5th etc. root of a number, have the functions root2, root3, root4... up to root10 routinely available, perhaps in a module called roots or radicals.
import radicals as r r.root3 ( 2.0 / 3.0 ) 0.8735804647362989
So how might we generate root0... root10 in a factory function, such that these function names become top level within their generating and/or defining module? True, we could individually define these functions per the example below, but what is a clever way of generating rootN in principle, such as root2012, taking 2012 as an argument. Hints: For root2, you might take an obvious shortcut: from math import sqrt as root2 For root3, consider the following: def root3 (x): return pow( x, 1.0/3.0 ) In general: def rootN (x): return pow( x, 1.0/N) Rationale for this exercise: Students often want a way to factory-produce new names at the top level, from within a contained scope (such as from within a function). These top level names may be of data, functions or whatever arbitrary objects. The production process often involves synthesizing new names on the fly, as strings. Strings are "right side" objects, usually assigned names, but not themselves names of anything. By what magic might we turn a string object into a top-level name: that is the question this lesson addresses.

By what magic might we turn a string object into a top-level name: that is the question this lesson addresses.
I do have an answer in the queue, my proposed solution. However I'd be happy to see others toss in some ideas, just to test whether my lesson plan is clear enough to get across what the challenge is. Maybe not. On another front, I'm wanting to tell the story of free and open source software to a next generation of teen, and wondered if FOSS veterans here would have any feedback regarding the following thumbnail: <LORE> GNU = GNU is not Unix and was the original inspiration for the free software movement, centered on what become known as the GNU Public License (GPL). The GPL was a way for copyright holders to insist that their hard work not be used only selfishly by others, that the fruits of their labors achieve maximum positive benefit for omni-humanity, to put a Global U spin on it. Once the GPL was out there getting the work done (e.g. Linux), a safe-enough ecosystem was developed for a bevy of alternative licensing schemes, some of which allow shops to take something inhouse, transform it, and release it under a more restrictive license agreement, if they release it at all. [ Python could be an example of this, in that developers of the IronPython codebase have been enabled to start with CPython, the original version, with the resulting codebase being free and open source under a different set of rules from the GPL or even from whatever the original Python was using. Actually, the story is more complicated than that, in that Jim H. first worked on the Java implementation before moving to C# in an attempt to prove the CLR (common language runtime) engine was unsuitable for dynamic languages. He came to the conclusion that the CLR engine could work, hence the IronPython project. ] </LORE> The original context is some Google group (a new one). The relevant thread is accessible from this blog post FYI: http://mybizmo.blogspot.com/2010/05/math-reform.html Where it says: Rhombic Triacontahedra etc. (Synergetics list) -- Urner (I'm still adding to this list of May 2010 math reform writings). Kirby

On Wed, May 26, 2010 at 4:58 PM, kirby urner <kirby.urner@gmail.com> wrote:
Creating Top-Level names root0, root1, root2... rootN
SNIP
What they'd like to do is this: for 2nd, 3rd, 4th, 5th etc. root of a number, have the functions root2, root3, root4... up to root10 routinely available, perhaps in a module called roots or radicals.
import radicals as r r.root3 ( 2.0 / 3.0 ) 0.8735804647362989
So how might we generate root0... root10 in a factory function, such that these function names become top level within their generating and/or defining module?
True, we could individually define these functions per the example below, but what is a clever way of generating rootN in principle, such as root2012, taking 2012 as an argument.
Ok, here's my solution:
def make_root(i): ... def _root(x): ... return pow(x, 1.0/i) ... return _root ... for i in range(1, 11): ... locals()['root' + str(i)] = make_root(i) ... root2(4) 2.0 root4(64) 2.8284271247461903 root6(64) 2.0
Cheers, André
Hints:
For root2, you might take an obvious shortcut:
from math import sqrt as root2
For root3, consider the following:
def root3 (x): return pow( x, 1.0/3.0 )
In general:
def rootN (x): return pow( x, 1.0/N)
Rationale for this exercise:
Students often want a way to factory-produce new names at the top level, from within a contained scope (such as from within a function).
These top level names may be of data, functions or whatever arbitrary objects. The production process often involves synthesizing new names on the fly, as strings. Strings are "right side" objects, usually assigned names, but not themselves names of anything.
By what magic might we turn a string object into a top-level name: that is the question this lesson addresses.
_______________________________________________ Edu-sig mailing list Edu-sig@python.org http://mail.python.org/mailman/listinfo/edu-sig

On Wed, May 26, 2010 at 7:21 PM, Andre Roberge <andre.roberge@gmail.com> wrote:
Ok, here's my solution:
def make_root(i): ... def _root(x): ... return pow(x, 1.0/i) ... return _root ... for i in range(1, 11): ... locals()['root' + str(i)] = make_root(i) ... root2(4) 2.0 root4(64) 2.8284271247461903 root6(64) 2.0
Cheers, André
Much appreciated André! I think our solutions are similar: """ http://mail.python.org/pipermail/edu-sig/2010-May/009998.html http://mail.python.org/pipermail/edu-sig/2010-May/009999.html """ def makeroot(N): try: assert type(N)==type(1) and N>=0 except: raise ValueError("0 <= N <= integer") fname = "root" + str(N) if N==0: fdef = "globals()['%s'] = lambda x: pow(x, 0)" % fname else: fdef = "globals()['%s'] = lambda x: pow(x, float(1)/%s)" % (fname,N) exec(fdef) Kirby

Much appreciated André!
I think our solutions are similar:
... yours is better though, in that you don't make use of exec. My use of it was superfluous. Changing my solution, in light of yours: #=== def makeroot(N): try: assert type(N)==type(1) and N>=0 except: raise ValueError("0 <= N <= integer") fname = "root" + str(N) if N==0: globals()[fname] = lambda x: pow(x, 0) else: globals()[fname] = lambda x: pow(x, float(1)/N) for i in range(11): makeroot(i) #=== Kirby

On Thu, May 27, 2010 at 2:52 AM, kirby urner <kirby.urner@gmail.com> wrote:
Much appreciated André!
It was a fun challenge. :-)
I think our solutions are similar:
... yours is better though, in that you don't make use of exec.
My use of it was superfluous.
Changing my solution, in light of yours:
#===
def makeroot(N): try: assert type(N)==type(1) and N>=0 except: raise ValueError("0 <= N <= integer")
fname = "root" + str(N)
if N==0: globals()[fname] = lambda x: pow(x, 0) else: globals()[fname] = lambda x: pow(x, float(1)/N)
for i in range(11): makeroot(i)
#===
Technically, your use of globals() instead of locals(), like I did, is better, in that it allows you to have it inside a function body, and still be available at the module level, as per your stated goal. However, I find the use of lambda to be too restrictive, in principle, as it obviously works only for expressions, and not complex functions. If this example is to be used as a prototype to show students how to do this kind of name assignment, then the way I have done it with an inner named function (which I first learned about *years* ago, from a post from you on edu-sig! I believe it was http://mail.python.org/pipermail/edu-sig/2005-March/004590.html) is a better way to go.
Kirby

On Thu, May 27, 2010 at 3:15 AM, Andre Roberge <andre.roberge@gmail.com> wrote:
On Thu, May 27, 2010 at 2:52 AM, kirby urner <kirby.urner@gmail.com> wrote:
Much appreciated André!
It was a fun challenge. :-)
I think our solutions are similar:
... yours is better though, in that you don't make use of exec.
My use of it was superfluous.
Changing my solution, in light of yours:
#===
def makeroot(N): try: assert type(N)==type(1) and N>=0 except: raise ValueError("0 <= N <= integer")
fname = "root" + str(N)
if N==0: globals()[fname] = lambda x: pow(x, 0) else: globals()[fname] = lambda x: pow(x, float(1)/N)
for i in range(11): makeroot(i)
#===
Technically, your use of globals() instead of locals(), like I did, is better, in that it allows you to have it inside a function body, and still be available at the module level, as per your stated goal.
However, I find the use of lambda to be too restrictive, in principle, as it obviously works only for expressions, and not complex functions. If this example is to be used as a prototype to show students how to do this kind of name assignment, then the way I have done it with an inner named function (which I first learned about *years* ago, from a post from you on edu-sig! I believe it was http://mail.python.org/pipermail/edu-sig/2005-March/004590.html) is a better way to go.
Kirby
Yes sir, I agree. The Python lambda is weak (on purpose) in not providing for full scale anonymous functions. Binding to the function object returned by an inner function def is a more liberal strategy, allows for more complexity. This thread will document these suggestions and nuances. Kirby
participants (2)
-
Andre Roberge
-
kirby urner