< Return to Video

Google Python Class Day 2 Part 2

  • 0:00 - 0:06
    >> PARLANTE: So, in this section, I want to
    play up this idea of modules of existing code
  • 0:06 - 0:09
    that you might want to use to just sort of
    solve common problems. In this case, I'm going
  • 0:09 - 0:16
    to show you some file system interface stuff
    and also how you call an external process
  • 0:16 - 0:19
    and capture its output and the like to do
    something. So you can imagine using Python
  • 0:19 - 0:24
    sort of--you might use Bash but just--it's
    sort of a better Bash to sort of glue something
  • 0:24 - 0:31
    together some sort of--some sort of utility.
    So I'll start off in the interpreter here,
  • 0:31 - 0:38
    fire up Python. And the first module I want
    to talk about is the OS module since we're
  • 0:38 - 0:44
    operating system, I think, and I'm just going
    to do a DIR on it. So I import the OS module.
  • 0:44 - 0:48
    I'm going to look inside of there, and you
    can see there are all sorts of functions in
  • 0:48 - 0:54
    there. There's obviously, you know, "setpgid"
    and "nice." There are obviously kind of operating
  • 0:55 - 1:02
    system-oriented utilities, have a very kind
    of a UNIX-y feeling. In theory, these are--these
  • 1:05 - 1:09
    try to be platform-independent. So if you
    write a--wrote a Python program and it's running
  • 1:09 - 1:14
    on Windows, some of these are stubbed out
    where you could call, you know, and try to
  • 1:14 - 1:18
    get the current time or whatever, and it's
    going to translate it. I don't believe it's
  • 1:18 - 1:22
    done perfectly, but it tries. So going through
    those, there's a--there's at least, theoretically,
  • 1:22 - 1:29
    a degree of platform independence. So I would
    like to show you a couple--so obviously, there's
  • 1:29 - 1:34
    tons of stuff in here. The one I'd like to
    show you for starters is listdir. That one.
  • 1:34 - 1:40
    So, actually, I could do help on it. Just
    to show you how that works, so I say, "os.listdir."
  • 1:40 - 1:46
    So it says--okay, what this does, it takes--nice
    summary--it takes a path and it's going to
  • 1:46 - 1:50
    give me a list of strings. So what it's going
    to do is I give it a path to a directory and
  • 1:50 - 1:53
    then it's going to figure out what all the
    filenames are in that directory and just return
  • 1:53 - 2:00
    it to me as a list of Python strings. So let
    me go to the interpreter here. So to demonstrate
  • 2:00 - 2:04
    this, what I thought I'll do is I'll modify
    the long-suffering hello.py example to just,
  • 2:04 - 2:11
    you know, I don't know, list files. So I'll
    say "import os" here. I'll rename this list,
  • 2:11 - 2:18
    upper case L. Let's say this will take a directory.
    So let's see, I'll say, "filenames = os.listdir
  • 2:19 - 2:26
    (dir)" and I just sort of as I've been encouraging
    you to do for the exercise--well, I'll just
  • 2:30 - 2:35
    print what that gives me for starters. So
    here I'll say, lists are here in the main.
  • 2:35 - 2:39
    I'll just leave it the way it is. So I'll
    just assume that there's one command line
  • 2:39 - 2:46
    argument and I'll just list it. So, hopefully,
    it's in the same directory. So if I say, "hello."
  • 2:47 - 2:54
    and you could see it's a, you know, it's found
    so I'll do an ls; that way, we can access
  • 2:55 - 3:00
    this information. So there's this .DS_Store
    thing that the Macintosh, like, pathologically
  • 3:00 - 3:06
    puts everywhere, and other than that, you'll
    see there's just kind of regular file names.
  • 3:06 - 3:12
    So let me make this code do something a little
    more interesting. So at least I printed that
  • 3:12 - 3:18
    it's there. So here, I'll--let's loop through
    them. So I'll say, "for filename in filenames:"
  • 3:18 - 3:25
    sort of typical kind of thing. So one thing
    I can do, if I want to make a path out of
  • 3:26 - 3:31
    this--but what's important to understand is
    that when you do a listdir to get file names
  • 3:31 - 3:37
    out of a directory, just that filename on
    its own, just out in space, is not a valid
  • 3:37 - 3:43
    path, right? It needs to be connected to the
    directory it came from to make a valid path.
  • 3:43 - 3:45
    So the way I could do that--and sort--you
    always sort--you always sort--as soon as you
  • 3:45 - 3:48
    call listdir, you're disconnecting the filename
    from path. So you have to realize, you've
  • 3:48 - 3:51
    immediately--now, with the current directory
    as dot or something, you might be able to
  • 3:51 - 3:55
    kind of fudge around some of these but then
    you'd have a bug if someone was running in
  • 3:55 - 4:02
    a different directory. So in the OS package,
    there's an OS--there's a subpart called os.path.
  • 4:03 - 4:09
    And inside of os.path, there are utilities
    for manipulating file paths; taking them apart,
  • 4:09 - 4:13
    putting them together, that kind of stuff.
    And again, these are a little bit platform-independent,
  • 4:13 - 4:17
    so on Windows or whatever, like, there's some
    chance this might work. I'm sorry, it'll--it
  • 4:17 - 4:24
    would definitely work. So, "join" takes a
    directory and a filename and then puts them
  • 4:24 - 4:31
    back together in a platform-valid way, so
    that makes a valid path. So I could say, like,
  • 4:31 - 4:38
    "print." I'll just print that. And then also,
    there's an os.path.abspath; I'll do that on
  • 4:41 - 4:48
    path. What that's going to do--it's kind of
    like a PWD--oh, I'm sorry, it's path there.
  • 4:48 - 4:54
    It takes the path and it's going to just fill
    it out to be replete. So let's try that. So
  • 4:54 - 5:01
    if I run that on dot and there's a module--okay,
    what did I do wrong there? OS--oh, it's not
  • 5:05 - 5:12
    absbath. All right. Well, let it be said,
    my demos do not lack for realism. All right.
  • 5:14 - 5:20
    So here, I'm running it on the directory dot,
    and so then here, the, you know, the first
  • 5:20 - 5:24
    line is it just puts it back together with
    a slash. I think--let's just try--if I said
  • 5:24 - 5:31
    like, "./" notice, it's smart about not doubling
    up the slash there where if you just put it
  • 5:31 - 5:35
    together with a plus, you would have done
    the wrong thing in there; it would have said
  • 5:35 - 5:40
    ".//". Anyway, it's a nicety of going through
    the real utility to do it right. And then
  • 5:40 - 5:45
    here is, you know, this is just on my--oh
    no, this is on my unit--my Google box or whatever.
  • 5:45 - 5:49
    That's, you know, that's the full path of
    the thing. And I'm sort of cheating on--just--I
  • 5:49 - 5:54
    could have--you know, I could say, like, "/tmp"
    and like, whatever--God knows what that is.
  • 5:54 - 5:59
    It's some source thing or whatever. Anyway,
    so I can--as an argument, I can just give
  • 5:59 - 6:06
    it any directory. It's going to list it up.
    All right, so let me go back to--so, mundane
  • 6:06 - 6:10
    yet useful, all right? You want to be able
    to manipulate list, do stuff with directory
  • 6:10 - 6:12
    paths; take them apart; put them together.
    I've shown you just a few of the utilities.
  • 6:12 - 6:16
    You can look in there--you know, there's--yeah,
    there's all the imaginable utilities you would
  • 6:16 - 6:19
    want for manipulating that kind of stuff.
    So let me show you--I'm going to drop back
  • 6:19 - 6:26
    into Python here--I want to show you one other
    one--just one I want. There's a os.path.exist('/tmp/foo').
  • 6:27 - 6:33
    That's hopefully--I don't actually know if
    that exists--oh, it does. All right, of course.
  • 6:33 - 6:40
    How about "baz"? Oh, okay, that's not there.
    There's also os.--I'm not going to run this
  • 6:45 - 6:51
    one--"os.mikdir" you get a path if you want
    to make something. And then finally, one that
  • 6:51 - 6:57
    you would never in a million years would it
    occur to you to find, but there's a module
  • 6:57 - 7:04
    called, "s-h-u-til" of which I think, historically,
    was sort of like shell utilities. And in s-h-u-til,
  • 7:07 - 7:14
    there is a ".copy" and what this does is file
    copying for you. So you give it a source path
  • 7:15 - 7:19
    and a dest path and it just kind of like goes
    right in there. Obviously, you could do it
  • 7:19 - 7:23
    manually by reading the bytes of the file
    or whatever, but if--yeah, it's--you just--yeah,
  • 7:23 - 7:27
    as I was saying, living higher on the food
    chain, yeah, you just want to call the thing
  • 7:27 - 7:32
    that does that. I think the name of s-h-u-til--it
    also shows how, I think, Python has grown
  • 7:32 - 7:36
    sort of organically, right? It's not like
    a committee got together and said, well, for
  • 7:36 - 7:39
    a job, I feel like it's a much more a top-down
    design with the names and stuff, where, you
  • 7:39 - 7:42
    know--it's not that a committee got together
    and said, "Well, I think we should have a
  • 7:42 - 7:45
    file copying utility" and, you know, "Here's
    what the names should be done." Instead--I'm
  • 7:45 - 7:50
    just guessing--like, some guy said, "Oh, here,
    I've made this s-h-u-til thing, you know,
  • 7:50 - 7:53
    didn't really give a lot of thought into the
    name and it was just kind of useful and it's
  • 7:53 - 7:57
    open source so it just kind of gotten picked
    up." And so now, by historical accident, like
  • 7:57 - 8:02
    that's the slightly obscure name for that
    utility is now, so typical kind of community-driven
  • 8:02 - 8:06
    open source, you know? It's kind of lovable
    and powerful, but yet like a little bit undisciplined.
  • 8:06 - 8:12
    The--all righty, so that is the--that stuff
    I wanted to show you with OS. Now, I wanted
  • 8:12 - 8:17
    to show you another--I'm going to stick, I'm
    going to stick with doing stuff in the interpreter
  • 8:17 - 8:21
    just to reinforce that though. So, the other
    thing I wanted to show you is how you launch
  • 8:21 - 8:27
    an external process and wait for it to finish
    like very common kind of, you know, utility,
  • 8:27 - 8:32
    get things done, sort of things to do. There
    are a bunch of Python modules that do this,
  • 8:32 - 8:36
    a bizarrely large number. I'm going to show
    you what I--if you only knew one, I think
  • 8:36 - 8:43
    this is the most useful one. There's a module
    called "commands." And inside of commands,
  • 8:44 - 8:51
    there's a function called, "get status output,"
    I'll do help on it. Oh, boy, the help is pretty
  • 8:52 - 8:58
    short. What it does is it runs that command.
    So it's going to shell out as an external
  • 8:58 - 9:01
    process, it's going to run that command and
    you're going to block. So it causes you to
  • 9:01 - 9:08
    wait. It's going to wait for that certain
    process to exit. And the standard out and
  • 9:08 - 9:13
    standard error of that cell so process--so
    process are captured; they're not just written
  • 9:13 - 9:16
    onto your standard out--standard dir. So the
    thing is--it's kind of sealed. So once the
  • 9:16 - 9:22
    thing exits, then what gets--what output is
    going to do is it returns a Tuple-length tube.
  • 9:22 - 9:25
    Returning a Tuple is kind of the Python way
    of saying, "Well look, I wanted to return
  • 9:25 - 9:29
    two things," or two or three things or whatever
    so you could just return a Tuple. The Tuple
  • 9:29 - 9:36
    that it returns is--the first is the "int"
    exit code. So just in a very typical UNIX-y
  • 9:36 - 9:40
    kind of way where, you know, you can recover
    the exit code out of there. And then the second
  • 9:40 - 9:44
    is a big string, which is all of the output
    of this thing. And in this case, I think it's
  • 9:44 - 9:49
    both the standard output and the standard
    error kind of caught into each other. Now,
  • 9:49 - 9:53
    there are a bunch of variance of this if you
    want to capture the standard dir separately
  • 9:53 - 9:57
    or--all sorts of permutations are covered,
    but this is the one we're going to use today.
  • 9:57 - 10:03
    And so I'll get out of here and I think what
    I'm going to do is I'm going to modify my--well,
  • 10:03 - 10:09
    here, we'll leave this as list but I'm going
    to, I'm going to have it work differently
  • 10:09 - 10:15
    now. So I'm going to say, let's make this
    command; I'll say, "'ls -l' + dir." It's kind
  • 10:15 - 10:21
    of weird, right? So as a string, I'm putting
    together like, "Oh, here's the thing I'd like
  • 10:21 - 10:26
    to shell out and have it, like, launch the
    ls program." And so then, I'm going to write
  • 10:26 - 10:33
    a Tuple so I'll say, "status, output" is equal
    to--actually, no here, I'm going to--let's
  • 10:33 - 10:40
    skip this stuff. So the way I like to do these--well,
    I'll do this one. So the way you call it is
  • 10:40 - 10:45
    I'll say, "status output" that's the Tuple,
    "= commands.getstatusoutput" and I'll just
  • 10:45 - 10:52
    pass in the command I want to do. And then
    here, we'll just like, you know, print the
  • 10:53 - 11:00
    output. Get rid of all these. And for a--normally,
    I would forget to do the import and go through
  • 11:00 - 11:04
    that but just--since we're short of time,
    I'll just--I'll go ahead and do it correctly
  • 11:04 - 11:10
    so import commands. All right, yeah, I think
    that might work. All right. So I'd enter the
  • 11:10 - 11:17
    Phyton. So if I say--I'm just going to give
    it a dot again. Oh, there we go. So what that
  • 11:17 - 11:24
    did is it put together the ls-l. It went through
    the commands module. It launched it. My Phyton
  • 11:24 - 11:28
    number waits, blocks. Eventually, the thing
    ran. It produced your, you know, typical ls-l
  • 11:28 - 11:34
    sort of output. And then, then I'm done. All
    right, so now this is--now, I'm going to fix
  • 11:34 - 11:40
    this up in a couple of ways. I'll regard this
    as like, "Not quite right." So one thing I
  • 11:40 - 11:47
    want to do is I want to notice if this thing
    failed. And the way I'm going to do that,
  • 11:47 - 11:52
    the simplest way is I'm just going to say,
    "if status," if the status is non-zero, then
  • 11:52 - 11:56
    I want to notice if there was an error. So
    because status is coming through as an int
  • 11:56 - 11:59
    (ph--if, you know, if it's zero, that's going
    to count as false and the other value is kind
  • 11:59 - 12:02
    of true. So that's sort of the most primitive
    way of detecting an error here. So then I'm
  • 12:02 - 12:09
    going to say something like print--I think
    I could refer to "sys.stderr," you know, there's,
  • 12:10 - 12:15
    you know, whatever. There was an error. Now,
    I'm being little picky here because when you
  • 12:15 - 12:20
    capture the standard error of a subprocess,
    if I were to sort of squelch it, if I was
  • 12:20 - 12:26
    just try to kind of eat it and hide it, it
    makes the system undebuggable. I mean if you
  • 12:26 - 12:30
    think about software systems where it's, you
    know, some big thing with a lot of parts,
  • 12:30 - 12:34
    the key piece of information when it's used
    incorrectly which of course it is is that
  • 12:34 - 12:39
    whatever the lowest level was that ran into
    error reports it. It raises some kind of message
  • 12:39 - 12:45
    like, "Hey, this didn't work and you are really
    dependent on that low level letting you know.
  • 12:45 - 12:48
    Or put the other way, if the low level fails
    and remains silent, it's very, very difficult
  • 12:48 - 12:52
    to debug. And I'm pointing this out because
    this the rare case where we are capturing
  • 12:52 - 12:56
    the standard error of that thing. And so we
    are kind of responsible for making sure that
  • 12:56 - 13:03
    it gets supported. So, I--and, I'll just say
    something like that. And then I'm going to
  • 13:03 - 13:07
    say "sys.exit(1)" I'm just going to be like,
    yes, we are--I'm just--I'm giving up--I'm
  • 13:07 - 13:11
    terminating. So, that is one--one thing I
    would want to do. Now, the other thing I'm
  • 13:11 - 13:17
    going to change here is when I'm--like suppose
    you have a bug in your baby name's code, you
  • 13:17 - 13:20
    know, you like did the regular expression
    wrong. And like, really, what are the consequences
  • 13:20 - 13:25
    of that? Oh, well, you know, whatever, some
    of the baby name data is a little bit incorrect
  • 13:25 - 13:29
    or you missed something. But having--an error
    in your code, you just get like slightly bad
  • 13:29 - 13:36
    data which is not that bad, I'm going to say.
    Now, what if I have a bug here in the string
  • 13:36 - 13:41
    where I'm putting together a command which
    I'm about to shell out and run as me? And
  • 13:41 - 13:45
    I just wanted to point out, the ramifications
    of doing that wrong are potentially much worse.
  • 13:45 - 13:49
    All right, that I'm--whenever I write command,
    I'm immediately on this slightly heightened
  • 13:49 - 13:52
    sense of paying attention. I'm like, "Okay,
    well, yeah, I could really delete everything
  • 13:52 - 13:58
    or whatever." So just to demonstrate that,
    what if I were to change this to say, "'rm
  • 13:58 - 14:04
    -rf' *" or let's say, you know, why stop there,"/*,"
    right? Oh, I'm sorry, the directory is already
  • 14:04 - 14:08
    there. It's an argument. Okay, there, all
    right? Here, I'll--here, I'll show you. I'm
  • 14:08 - 14:12
    going to save it, all right? Now, if you're
    anything like me, like I maybe like, "Oh,
  • 14:12 - 14:17
    okay, it sounds good, all right, so here's
    what I recommend doing: when I'm writing this
  • 14:17 - 14:24
    kind of stuff I'll say "print 'about to do
    this:,'" Oh, there's the command. And then
  • 14:26 - 14:30
    I'll just like return, whatever, just don't
    get to the stuff below because you can sort
  • 14:30 - 14:34
    of debug your program, all these other reading
    directories or whatever kind of stuff and
  • 14:34 - 14:38
    you can still have it printed, here's the
    command it's going to do. And so it's more
  • 14:38 - 14:43
    pleasing I think to debug it that way. So
    let's just try this. So I'm going to save
  • 14:43 - 14:50
    it and that definitely returns, right? Oh,
    hey, you know, the snapshot directory will
  • 14:50 - 14:54
    be out here. It's unscrapable. All right,
    sorry. I just got on the wrong part. All right,
  • 14:54 - 15:01
    so what I meant to do is go down here, "hello.py."
    There was a--what's the problem with that?
  • 15:01 - 15:08
    Did I forget a "if, print, if status print"--oh,
    oh, oh, oh--all right, okay, this one's--okay,
  • 15:13 - 15:20
    never mind that. Let me just--I'm not used
    to write some text for--so, let me just get
  • 15:25 - 15:30
    rid of that for now. All right, okay, so I'm
    about to do this, "'rm -rf.'" So I'll be like,
  • 15:30 - 15:33
    "Oh, oh, wait a minute, I didn't mean rf--rm-rf,
    I meant "'ls -l'" so that's our--that's what
  • 15:33 - 15:36
    we're going to do. So that's just kind of--I
    mean, you know, in your next exercise, I'm
  • 15:36 - 15:40
    going to ask you to shell out and so just,
    you know, just for like saying it or whatever.
  • 15:40 - 15:47
    Now, this error--I'll try to do it the other
    way. The print syntax for writing--like normally,
  • 15:47 - 15:53
    when we say "print," it just go to standard
    out. But printing to another file handle,
  • 15:53 - 15:57
    the syntax is sort of terrible. I'm going
    to--I think--I think I can do "dot" right
  • 15:57 - 16:04
    there. I'll put this together with a plus.
    I think that's better. So let's see. Now,
  • 16:07 - 16:11
    it's doing "'ls -l'," all right. Anyway, so
    that's the--that is the better syntax for
  • 16:11 - 16:18
    that. All righty, so let me show you--so those
    are the two module things I want you to work
  • 16:18 - 16:25
    on for this next bit. So let me show you our
    next exercise. All right, so I'm going to
  • 16:25 - 16:32
    go into day 2 here and the next one I want
    to work on is "copyspecial." So as before,
  • 16:32 - 16:35
    there's a printed form of the description
    of this. So I'm just going to kind of demo
  • 16:35 - 16:38
    through it, but then you really want to look
    at the printed direction. So, you know, it's
  • 16:38 - 16:42
    going to have a part A and a part B. This
    one's a little smaller and so I want to spend
  • 16:42 - 16:46
    like a little bit last on this one. If you
    don't get to Part B, that's okay because then
  • 16:46 - 16:48
    the third assignment, the last one I think
    is the most interesting and that's one of
  • 16:48 - 16:51
    the bigger ones so I want to make sure we
    save time for that. Okay, so here's the idea
  • 16:51 - 16:58
    with this. The idea is in the file system,
    there are certain file names which are special.
  • 17:00 - 17:07
    In a particular, I'm going to say that a file
    name is special if it has the pattern that
  • 17:07 - 17:13
    somewhere in the file name there are two underbars
    and then one or more word characters followed
  • 17:13 - 17:20
    by two underbars. And so for example, in this
    directory, there are two special files. There's
  • 17:21 - 17:24
    the "hello" and the something and then the
    solution directory and copyspecial--well,
  • 17:24 - 17:29
    those aren’t special. So, this is, you know,
    sort of Google admin kind of thing. You got
  • 17:29 - 17:32
    at least directories scattered all over the
    place and you want to move from around and
  • 17:32 - 17:37
    stuff. So the first thing I'd like to do,
    let's see--now, if you run the command with
  • 17:37 - 17:41
    no arguments, it always kind of tells you
    what the--what the arguments are. So in this
  • 17:41 - 17:46
    case, I can run it just with a directory.
    So here, I'm going to run it on "dot" as the
  • 17:46 - 17:52
    current directory. So if I run it on "dot"
    what I want you to do--so it takes a directory
  • 17:52 - 17:57
    as an argument. What I want you to do is I
    want you to find all the special files and
  • 17:57 - 18:03
    just list them. And oh, in particular, list
    them by their absolute paths. The absolute
  • 18:03 - 18:06
    path is something--if you were write a path
    to a file or whatever, that's the path that's
  • 18:06 - 18:10
    nice because it's independent of the process
    that produced it. It doesn't depend on the
  • 18:10 - 18:15
    notion of current directory. It's like this
    really is where that file is. So that's the--that's
  • 18:15 - 18:20
    the simplest case. Just find them, list them.
    The next most complicated thing I want you
  • 18:20 - 18:26
    to do is this thing takes a two-directory
    argument. So I'll say "/tmp/"--now, I'm thinking
  • 18:26 - 18:31
    of some random word I haven’t used. What
    day is it today? Thursday, I'll say Thursday.
  • 18:31 - 18:38
    All right, so in that case, what I wanted
    to do is find all the special files and create
  • 18:39 - 18:43
    that directory if it doesn't exist and copy
    all the special files to it. So, I'll find
  • 18:43 - 18:49
    out "cd /tmp/ thus" and do it--oh, somebody
    checked it out over there. All right, I'll
  • 18:49 - 18:56
    go back. That's Part B. So then, I'm pretty
    happy if you get that. But if you just have
  • 18:57 - 19:04
    enough time, then I also want you to have
    a two-zip, which is very similar to the two-directory
  • 19:04 - 19:08
    but instead, now I want to be able to say,
    "blah.zip" and what I want it to do is I want
  • 19:08 - 19:15
    to find all the special files, invoke the
    zip utility to zip them all up into the zip
  • 19:15 - 19:22
    file named here. So if I call that, and you
    can see actually my debugging is still in
  • 19:22 - 19:28
    here, right? "Command I'm about to do," and
    then, oops, and then here is the zip command.
  • 19:28 - 19:33
    Zip incidentally by the way--I think the worst
    man page ever written. I defy you to find
  • 19:33 - 19:36
    one less useful. It just talks about all the
    stuff you would never want to do. And it never
  • 19:36 - 19:39
    talks about the thing that you want to do--my
    personal experience. So it turns out the command
  • 19:39 - 19:45
    you want is "zip -j" and then the name of
    the zip file and then you just--and then you
  • 19:45 - 19:51
    just have all the paths. Now in this case
    I used absolute paths--really the zip is going
  • 19:51 - 19:53
    to have the same current directory as me,
    so you could do the shorthand--anyway--depending
  • 19:53 - 19:57
    on your tolerance for that kind of fragility.
    It's fine. So that will--that will zip it
  • 19:57 - 20:02
    up. Okay, so that is--that's the next exercise?
    So I'd like you to go ahead and get started
  • 20:02 - 20:08
    on that. And then let's say I'll pull you
    guys back here a little before 2, and then
  • 20:08 - 20:09
    we'll do the next exercises. All right, go.
Title:
Google Python Class Day 2 Part 2
Description:

Google Python Class Day 2 Part 2:
Utilities: OS and Commands.

By Nick Parlante.

Support materials and exercises:
http://code.google.com/edu/languages/google-python-class

more » « less
Video Language:
English
Duration:
20:20

English subtitles

Revisions