Thursday, February 13, 2014

Importing modules in RUR-PLE

When I first started working on RUR-PLE some 10 years ago, I wanted to create a "safe" environment so that beginners using it could not be "tricked" by others into running malicious programs.  So, before running a user program, I would parse it and prevent the use of keywords such as "chr", "exec", "eval", "input" and "import".  However, I still wanted to teach the idea of "importing a module", so I created a "fake" module allowed the user to have the line

import useful

or

from useful import turn_right

into their program, and this would import various "useful" definitions so that they would not have to retype them.  I added a note to teachers  at the bottom of the relevant lesson, telling them that they could get in touch with me if they wanted to remove the "import" restriction, and I would tell them how to modify the code.

Somewhere along the way, prompted by a discussion with a user, discussion whose details I have long forgotten, I removed the so-called useful module and enabled the import statement to be used. However, the lesson about the "import useful" module remained (and has been translated into other languages) as I just noticed, much to my embarassment.

Today, five years after I last modified the code, I was contacted by a teacher had read what I wrote and who wanted their students to be able to save their own functions and import them.  Here's a way to do it. Note however it involves a bit of "magic" (at least from the point of view of the student).

First, just to demonstrate that import works, one can type the following:

import sys
print sys.path   # python 2 syntax
turn_off()

Here is the output from the window below the robot world (I used the version installed by the .exe file)

['C:\\Program Files (x86)\\RUR-PLE', 'C:\\Program Files (x86)\\RUR-PLE\\library.zip']

I used a usb key (recognized as drive d: on my computer) to save the file my_def.py and containing the following:

from rur_py.cpu import rur_program
robot = rur_program()

def turn_around():
robot.turn_left()
robot.turn_left()

The first two lines contained the required "magic" so that our user-defined module can use all the same robot instruction that a "normal" program can.  However, these instructions, such as turn_left(), must be preceded by "robot."  [or any other name given to rur_program()].   

Note that I could have used just about any name instead of "my_def".

Once we have saved this module, we can now use it.  First, we need to make rur-ple aware of where our definitions exist.  To this end, we first run the following program once within RUR-PLE:

import sys
sys.path.append("D:\\")
turn_off()

Next, if we want to use our definitions found in module my_def.py, we do as follows:

import my_def
my_def.turn_around()
turn_off()

Of course, one could use the  "from my_def import turn_around" syntax instead so as not to have to write
"my_def." in front of every instruction I define.

Note that, if my_def is modified, RUR-PLE will have to be restarted and the entire procedure above will have to be repeated. 

===
Note added after the original post.  I wrote RUR-PLE when I was just relearning computer programming and I did not really understand a lot of things very well.  rur_program() referred to above is not a function, but a class (I was learning!...).  And, having learned a bit of Java some years before and having come accross some sample code about singletons in Python, I wrote rur_program() to be a singleton, thinking I was never going to need more than one user program at a time.  I don't think most Python programmers would have done that.  Yet, through sheer luck, this is needed for the above to work, otherwise "turn_left" in the imported module, defined as a method of rur_program, would have referred to a different instance than that used in the user's program, and it would not have been possible to import user definitions as done above. 
===
Edit #2 
I know that some teachers that use rur-ple are not familiar with advanced Python features; so here's another equivalent way (sent to me by a reader) to import and use modules with rur-ple; the path uses the syntax one may have on a Mac.  (For people familiar with Python, I do realize that both ways will look essentially the same - but they do not for beginners.)

from rur_py.cpu import rur_program
robot = rur_program()

def left():
    robot.turn_left()

def turn_around():
    left()
    left()

def right():
    turn_around()
    left()


======== and within rur-ple

import sys
my_py_dir = "/Users/andre/rurple_files"
if my_py_dir no in sys.path:
     sys.path.append(my_py_dir)       

from my_module import *

# main program

right()
turn_off()