HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Python with alternative keywords

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withalternativepythonkeywords

Problem

A while ago, a user on Programming Puzzles and Code Golf had an idea:


How about a language where all of the core commands are PPCG usernames? -- Helka Homba

A list was made of username/(keyword|builtin function) pairs, but nobody really wanted to actually make the code, so it kinda went on hold.

Recently (read: yesterday) another user suggested using the tokenize module, so I did. With his help, I finished some py2 code for replacing the names.

Here is my code:

import tokenize
import ast
import sys

def handle_token(type, token, (srow, scol), (erow, ecol), line):
    # Return the info about the tokens, if it's a NAME token then replace it

    if tokenize.tok_name[type] == "NAME":
        token = token_names.get(token, token)
    return (type, token, (srow, scol), (erow, ecol), line)

def run(assignments="assignments.txt",open_from="peoples.txt"):
    with open(assignments, "r") as f:
        # Read the replacements into token_names
        global token_names
        token_names = ast.literal_eval(f.read())

    with open(open_from) as source:
        # Get the tokenized version of the input, replace it, and untokenize into pretty output
        tokens = tokenize.generate_tokens(source.readline)
        handled_tokens = (handle_token(*token) for token in tokens)

        output = tokenize.untokenize(handled_tokens)

    with open(open_from[:-4]+"-output.txt",'w') as outfile:
        # Write to the output file
        outfile.write(output)

    return output

if __name__ == "__main__":
    if len(sys.argv) > 1:
        if len(sys.argv) > 2:
            try:exec run(assignments=sys.argv[1],open_from=sys.argv[2])
            except:pass
        else:
            try:exec run(assignments=sys.argv[1])
            except:pass
    else:
        try:exec run()
        except:pass


This is the content of main.py. It uses assignments.txt, which is a file containing a python dict of the replacement pairs.

```
{"Martin":"False",
"Geobits":"None",
"Denni

Solution

Some Musings about Exceptions

(1) NEVER pass on every single exception!

You're basically absorbing every possible exception without usefulness. Normally, to prevent runtime crashes, I do this with my excepts when I want to catch all:

try:
    # some code here...
except Exception as e:
    print "An exception has occurred:\n%s" % str(e)


... which will put a nicer message. Of course, you can expand this by defining different types to capture in the except blocks. There may be cases for this, but since this isn't a code golf challenge, you can do this here instead.

(2) "Blank" except blocks

There's a rule of thumb that I abide by: "In most cases, you should always try and catch the most narrow exception that you expect to end up seeing in a try/except block, with an ultimate "catch-all" block later to handle any unexpected exceptions." That basically means that if I'm trying to catch an "Invalid JSON" error when running json.load (you'll see why I mention this later), I'd do something like this:

with open('jsondata.json', 'r') as f:
    try:
        data = json.load(f)
    except ValueError:
        print "Could not properly parse JSON data, is the file 'jsondata.json' comprised of actual JSON data?"


In effect, we do something different with a ValueError (we could pass, or we could simply quit, or we can print a nice message like this does), but for all other Exceptions will not capture them and will raise whatever exception it was trying to raise. Let's say, though, this was in a run() function, and I call the run function in a manner like you do in your code:

if __name__ == "__main__":
    try:
        run()
    except Exception as e:
        print "An unhandled exception has occurred:\n%s" % str(e)


Since most errors inherit from Exception this captures all other errors, but not warnings, this will allow for us to capture unhandled exceptions and 'handle' them as a "final option" type of try/except block, for when they aren't handled in other blocks below run directly.

There are always rare cases where you want to do nothing when an exception happens, so you can use pass in those cases, but in 99% of all cases, you should not be simply 'allowing exceptions to pass on without raising some notice'.

Replace ast with json instead

JSON data is basically a structured dict. Since you're storing a dict and it meets the most basic JSON formatting, we can just parse your assignments file as JSON instead, and remove the literal_eval and import ast. This does require you to import json instead of import ast but this is a more sane approach:

with open(assignments, "r") as f:
    # Read the replacements into token_names
    global token_names
    token_names = json.load(f)


This way, we don't have to worry about ast and having a literal evaluation that could cause evil in the future. This also permits us to error out proper with an exception if we don't have a valid assignments file. (And we can handle it with customized error messages if we wish).

As for storing the 'massive dict' in main.py, if it's likely to expand in the future leave it in its own file, and continue to load it as a JSON object as my suggestion here has.

This isn't code golf! Whitespace is a good thing!

Your code is hard to read when your try/except blocks and such are all golfed to remove whitespace. Add whitespace for readability.

You don't need that many try/accept blocks around your 'run' calls, and you don't need exec either!

You have four separate try/except blocks, yet all they do is silently let exceptions go by. Taking into account I don't suggest using 'pass' on exceptions that could be important, we can just simply take a page from my book, and change your code to have one try/except block wrapped around all the run calls, and handle exceptions whenever they come up, without having individual try/except blocks just for each call.

You also don't need the exec calls you have in here.

So, ultimately, you save some code:

if __name__ == "__main__":
    try:
        if len(sys.argv) > 1:
            if len(sys.argv) > 2:
                run(assignments=sys.argv[1], open_from=sys.argv[2])

            else:
                run(assignments=sys.argv[1])
        else:
                run()
    except Exception as e:
        print "An exception has occurred:\n%s" % str(e)


Nitpicking Section

I'mma nitpick a little here with some of your code, and bring more suggestions that are minor/aesthetic in nature, rather than code-critical. These suggestions are reflected below though.

type is actually a builtin, use a different name in handle_token

This one's fairly obvious, but type is actually a built-in. Shadowing built-ins is bad if you eventually could use a builtin, so let's replace type with type_ in handle_token, to get rid of the "Shadows built-in 'type'" problems.

Unnecessary parentheses on 'return' in `handle_toke

Code Snippets

try:
    # some code here...
except Exception as e:
    print "An exception has occurred:\n%s" % str(e)
with open('jsondata.json', 'r') as f:
    try:
        data = json.load(f)
    except ValueError:
        print "Could not properly parse JSON data, is the file 'jsondata.json' comprised of actual JSON data?"
if __name__ == "__main__":
    try:
        run()
    except Exception as e:
        print "An unhandled exception has occurred:\n%s" % str(e)
with open(assignments, "r") as f:
    # Read the replacements into token_names
    global token_names
    token_names = json.load(f)
if __name__ == "__main__":
    try:
        if len(sys.argv) > 1:
            if len(sys.argv) > 2:
                run(assignments=sys.argv[1], open_from=sys.argv[2])

            else:
                run(assignments=sys.argv[1])
        else:
                run()
    except Exception as e:
        print "An exception has occurred:\n%s" % str(e)

Context

StackExchange Code Review Q#156043, answer score: 9

Revisions (0)

No revisions yet.