[Tutor] Data persistence problem

Steven D'Aprano steve at pearwood.info
Sat Jun 22 13:00:32 CEST 2013


On 22/06/13 09:00, Jim Mooney wrote:
> On 21 June 2013 14:59, ALAN GAULD <alan.gauld at btinternet.com> wrote:
>
>>
>> Give us a clue, show us your code!
>
> I was hoping you wouldn't say that since it's another of my insane
> Lazy Typer programs to avoid typing, which are no doubt considered
> frivolous. Although I'm learning a lot doing them ;')

Well, perhaps not *frivolous*. After all, Python itself might be said to be a "lazy typer" programming language, compared to some such as Pascal, C, or especially Java.

Compare the archetypal "Hello World" program in Java versus Python:


=== Java version ===

public class HelloWorld {
     public static void main(String[] args) {
         System.out.println("Hello, World");
     }
}


=== Python version ===

print("Hello, World")



Speaking of Java, I get a kick out of this article and love to link to it on every possible opportunity:

http://steve-yegge.blogspot.com.au/2006/03/execution-in-kingdom-of-nouns.html

:-)




> Okay, I have a snippet that posts the below text in automatically. I
> can use it to make any number of dicts, lists, or sets, from just
> tokens in a single string, and they are printed out as the different
> types, named and and numbered so they have different program names.
> That way I don't have to type in quotes, commas, colons, and so forth.
> Once the interpreter prints them out, I just select all and paste them
> into my program, replacing what's below. Kind of a manual
> meta-programming ;')
>
> ============= pasted in snippet ================
>
> ## Output sequence will be all strings. The first type in the 2nd
> parameter list will be the
> ## default due to 'or" short circuiting, so you can just delete to get
> what you want.
> ## You MUST have an even number of tokens for a dict or you'll get an exception.
>
> from maker import makeseq
> makeseq("Replace me, even number of tokens for dict", dict or list or set)


I think that a better design here would be three functions that explicitly tell you what they do:

makedict
makelist
makeset

The three of them could call an underlying internal function that does most of the work. Or not, as the case may be.



> ============= end snippet ====================
>
>  From this I can create any number of numbered dicts, lists or sets

*Numbered* variables? Oh wow, is it 1975 again? I didn't think I'd be programming in BASIC again...

:-)


> (The 'or' short circuit means I get dicts as default, and only have to
> delete from the front to get the others - dicts are default since
> they're the most annoying to type - now That's lazy.)

Surely typing one of "dict", "list" or "set" is not too much effort?

makeseq("stuff goes here ...", dict)
makeseq("stuff goes here ...", list)
makeseq("stuff goes here ...", set)
makeseq("stuff goes here ...", tuple)
makeseq("stuff goes here ...", frozenset)


You don't even need to put quotes around the type. In fact, you *shouldn't* put quotes around the type, since that will stop it from working.


[...]
> makeseq("this is a list and it is not a very big list", list or set)


Creating your own tools is fine, but they ought to do a little more than just duplicate the functionality of built-in tools. I'll come back to the dict case again further down, but the list and set cases are trivial:


L1 = "this is a list and it is not a very big list".split()

L2 = "Yet another list to show the different types increment separately".split()

S1 = set("and finally some sets".split())

Best of all, the time you save not having to type "makeseq" can now be used to think of meaningful, descriptive names for the variables :-)


Here are some improvements to your makeseq function:


> dictnumfarkadoodle = listnumfarkadoodle = setnumfarkadoodle = 0
> # Since these are global I'm using words not likely to be duplicated
> until I figure a different way and
> # replace 'farkadoodle' with '' ;')
>
> def makeseq(instring, typein):
>      global dictnumfarkadoodle, listnumfarkadoodle, setnumfarkadoodle
>      if isinstance(dict(), typein):

Rather than create a new dict, then check to see if it is an instance of typein, you can just do this:

     if typein is dict:
         ...
     elif typein is list:
         ...
     elif typein is set:
         ...

and similar for tuple and frozenset.


>          newdict = {}
>          dl = instring.split()
>          if len(dl) % 2 != 0:
>              raise Exception ("list entries must be even") # so they match

It is normally better to use a more specific exception. In this case, I recommend using TypeError, since TypeError is used for cases where you pass the wrong number of arguments to a type constructor (among other things).


>          for idx in range(0,len(dl),2):
>              newdict[dl[idx]] = dl[idx+1]

In general, any time you find yourself channeling Pascal circa 1984, you're doing it wrong :-) There is very rarely any need to iterate over index numbers like this. The preferred Python way would be to slice the list into two halves, then zip them together:

           keys = d1[0::2]  # Every second item, starting at 0.
           values = d1[1::2]  # Every second item, starting at 1.
           newdict = dict(zip(keys, values))

which can be re-written as a single line:

           newdict = dict(zip(d1[0::2], d1[1::2]))


>          dictnumfarkadoodle += 1
>          print('D' + str(dictnumfarkadoodle) + ' =', newdict)
>      elif isinstance(list(), typein):
>          newlist = []
>          dl = instring.split()
>          for word in dl:
>              newlist.append(word)

dl is already a list. There's no need to laboriously, and manually, copy the items from dl one by one. Instead you can tell Python to copy them:

            newlist = list(dl)

or if you prefer slicing notation:

            newlist = dl[:]  # Slice from the beginning to the end.

but really, why bother to copy the list?

            newlist = instring.split()

>          listnumfarkadoodle += 1
>          print('L' + str(listnumfarkadoodle) + ' =', newlist)
>      elif isinstance(set(), typein):
>          newset = set()
>          dl = instring.split()
>          for word in dl:
>              newset.add(word)

I'm going to leave this one for you. Given what you've seen with the list section, how would you improve this one?

(Hint: here too, dl is a list, and set() takes a list as argument and returns a set.)


>          setnumfarkadoodle += 1
>          print('S' + str(setnumfarkadoodle) + ' =', newset)
>      else: print('Second parameter must be list, set, or dict')
>
> # oops, I error out on a non-type 2nd parameter. Fix this later

Actually, no, you don't error out. You just print a message. To make it a proper error, you need to raise an exception:

raise TypeError("second param blah blah blah...")

or similar.


-- 
Steven


More information about the Tutor mailing list