A gentle intro' to Python

Python makes programming, especially graphics programming, FUN!!

Below is a set of 'topics' to explore sequentially - together, they will give you an introductory view of Python.


Python philosophy

Quote: Python is a freely available, very high-level, interpreted language developed by Guido van Rossum. It combines a clear syntax with powerful (but optional) object-oriented semantics. Python is widely available and highly portable.

Summary: Python is *simple*, yet *powerful*. Elimination of visual clutter, adhering to consistency and useful shorthand for common tasks provides this refreshing combination. For comparison, BASIC is simple but not powerful; C++ is powerful and is in no way 'simple'!

Also, Python comes with so many modules (libraries, ie. separate, bundled code) for doing everything under the sun - 'regular expressions' to UI creation to webservers to graphics to games to data acquisition - all ready to use right out of the box, in identical ways! Hence the expression "Python comes with batteries included".


Python shells

Python is interpreted, so the best way to learn it is to interactively type commands into a Python shell. Choices for the Python shell:


Calculations

Type the following (and everything else!) into Maya's 'Python' tab, select what you typed (just like with MEL), and hit Enter at the right of the keyboard (again, just like with MEL). Maya will run the Python code inside its embedded interpreter, and provide you feedback above the Script Editor window (once again, just like with MEL).

2+5

0.2

8*4.5

a = 2
b = 3
area = a*b
area
peri= 2*(a+b)
peri

Delete names:

del(a)
del(b)
del(peri)
del(area)
area # this name is not defined anymore

Division:

7/3 # integer division!

7/-3

7.0/3

7.0/-3

7.0/3

7.0//3 # floored int div, ie integer quotient

Type 'casting':

float(7)

int (7.0)

float(7)/3

int(float(7))/3 # big noop with int() and float()

int(float(7))/3.0

int(float(7))/int(3.0) # bigger noop

mod:

# modulus (excess, or remainder):
45.67%3.2
# remove the excess, divide again, to get exact quotient
(45.67 - (45.67%3.2)) / 3.2
# int quotient, float reminder both in one step:
divmod(7.666,1.0)

Two ways to do exponentiation:

pow(2,4) # or 2**4
2**400
13*457 # !!

Modules, strings

Modules:

import math # for math calculations, we need the math "module"
math.sin(1.5)

from math import * # so we don't need to prefix 'math.'
sin(1.57)

dir()
dir(__builtins__)
dir(math)

Strings-related:


# string can be expressed in any of these eqvt. ways
a = 'flower.jpg'
a = "flower.jpg"
a = '''flower.jpg''' # uncommon, since such a 'triple quoted string' is used while documenting code
a = """flower.jpg""" # ditto comment

'Python' ' is ' 'fun' # strings placed next to each other are auto. concatenated

raw_input("Enter a number: ") # raw_input always returns a string

int(raw_input("Enter a number: ")) # int() casts a valid 'intable' string to int

Dynamic, yet strong typing

No need to declare variables' types during creation - Python infers types. Variables stay bound to their types until they're reassigned to new types.

a = 45 # implicitly, a is declared an int
type(a)

b = 12.0
type(b)

a/b # floating point /

s = a/b # s is now a float
s[0] # not a string
type(s)

s = "Python is fun" # now it is
s[0] # Each char is a string


c = 3+4j # complex # type is built in
type(c)

c.real

c.imag

d = 1+2j

c*d # complex number mult :)

c.conjugate()

c = c.conjugate()
c
# again:
c = c.conjugate()
c

c*c.conjugate() # if c=a+ib, this is (a**2+b**2)

pow((c*c.conjugate()).real,.5) # magnitude

abs(c) # simpler way!

c = complex(3,4) # constructor

complex(3,0)

complex(3) # imag. is 0

(0.5+0.8j)**0.5

((0.5+0.8j)**0.5)**2 # :)

More on numeric types: http://python.about.com/od/pythonstandardlibrary/a/numeric_types.htm


Comments

# this is a one liner comment
'''This
comments
spans several
lines'''

Recap

Keywords (from dir(__builtins__)) learned so far:

print()
type()
dir()
del()
divmod()
int()
float()
str()
help()
pow()
abs()

No pointers/refs

Every name in Python is a ref. to an object, there are no explicit pointers/refs for the user to manipulate.


Printing

print "Complex c's value is",c # expressions, separated by commas

or use:

format % values

'values' needs to be a "list". A single element of such a list does not need to be in parantheses, but multiple items DO (see below).

a=10
b=45
print "a and b are %d and %d" % (a,b) # two items to print, specified using ( )

c = 3+4j
print "complex number c is %s" % repr(c) # just one item to print, the ( ) is optional
print "complex number c is %s" % (repr(c)) # exact same result as prev. line

d = 5 + 6j
print "c and d are %r, %r" % (repr(c),repr(d)) # two items, specified as a list

%d, %f, %r, %s etc. are 'format specifiers'. repr() is a function that returns a string representation of a non-string (eg. complex) type.


Indentation

Part of the syntax!!!!!!!!!!

Each new level of indentation starts a new *block*; these can be nested.

a = 25
 print a # error!
print a # works

Data types, structures

Built-in types:


path

import sys
print "PYTHONPATH is ",sys.path # list of dirs.

help

help()

help>modules

help>keywords

help>topics

help(5)

help(1+2j)

# HTML help!
# note: 'Tcl' needs to be installed for this to work
import pydoc
pydoc.gui()


Flow control: if, while, for [and also break, continue, pass]

These constructs (if, while, for) serve as building blocks for branching and looping.

if

a,b = 10,2
# usual comparison ops: ==, !=, <, >, <=, >=
a==b
a!=b

# for two tests, use 'and', 'or' to join; 'not' negates a single test
a!=b and b==1
a!=b or b!=2

a = 12
b = 25
if a>b:
  print a
elif a==b:
  print "a and b are both equal to %d" % a
else:
  print b
# elif and else are both independently optional

c = -5
d = 5

if a<b and c<d:
  print a
else:
  print b

elif avoids deep nested blocks like the following. It takes the place of switch() in C/C++/..

if a>b:
  print "a>b"
else:
  if a==b:
    print "a==b"
  else:
    if a==100:
      print "a is 100"

# cleaner
if a>b:
  print "a>b"
elif a==b:
  print "a==b"
elif a==100:
  print "a is 100"

while

import random
random.seed(45)
running = True
while running:
  r = random.random()
  print r
  if r>0.5:
    running = False
else: # this is optional (should have been called 'after'!)
  print "end of while()"


random.seed(450)
# try above loop again..

Fibonacci sequence:

a,b=0,1 # :)
while b<10000:
  a,b=b,a+b
  print b, float(b)/a 
# qn: what is the limit of b/a?

Note that in the above, we did multiple assignments in the same statement. These can even be done to vars of different types!

a,b = 5, "Test"

a,b = b,a # !!!!

a,b,c,d = 1,2,3,4
print a,b,c,d

a,b,c,d = b,c,d,a
print a,b,c,d

for

for i in range(1,10): # iterates NINE times, not 10
  print random.random()

range(6,12) # generates a LIST: [6, 7, 8, 9, 10, 11] 

count=0
for i in range(1,100):
  r = random.random()
  if r<0.5:
    print r
    count = count+1
else: # optional
  print "printed %d values" % count

Exercise: let's translate the following to Python:

 float $goldenAngle = 137.5*3.14159278/180.0;

 for($i=1;$i<=$n;$i++) {
   float $r = $c*sqrt($i);
   float $a = $goldenAngle*$i;

   float $x=$r*cos($a), $y=$r*sin($a);

   cylinder -ax 0 0 1 -hr 2 -d 3   -r $seedRad; // -s 8 -nsp 1
   move $x $y 0;
 }// next $i

break, continue, pass

'break' breaks out of an immediate while or for loop, before loop condition becomes false - it is an 'early' exit. When a 'break' occurs, an else: block that is present will *not* get executed.

'continue' skips rest of a loop block, to return control to the top, to start a new iteration.

'pass' is simply a no-op 'filler' statement (it means "do nothing").

a = True
while a==True:
  s = raw_input('Enter something: ')
  if s=="done" or s=="exit" or s=="quit":
    print "all done, 'break'ing"
    break
  if len(s)>5:
    print "input too long - try again"
    continue
  print "You entered ",s
  a = False
else:
  print "End of while() loop"
pass

# infinite loop!
while True:
  pass # NEED this no-op stmt. to create a valid block - eqvt to while(1){} in C/C++

dir(), type()

type(), dir(), len() etc. are examples of built-in functions.

'dir' lists names in the current module or in a named module.

dir()

import math
dir(math)

dir(__builtins__) # "built in" functions, exceptions, attrs

'type()' returns an object's type.

d=56
type(d)

d='Test'
type(d)

type(5)

type({})

type([]}

type(4+6j)

The above material was a brief introduction to Python. Here are more topics that you do need to explore, to round out your knowledge: lists, tuples and dictionaries, modules, classes and functions, "Pythonisms" (eg. list comprehension), built-in modules, file handling, documentation, exceptions, math, recursion, reflection, UI, modules for CG and math..


To use Maya-related commands, use the 'maya.cmds' module (for now; soon, we'd switch to a better 'pymel.core' module)

import maya.cmds # our 'hook' into the world of Maya!!!

a = dir(maya.cmds)
for i in range(len(a)):
  print a[i]

maya.cmds.sphere() # equivalent to MEL's 'sphere()'

Here is more (the rest) of Python: functions, classes, module collections ('packages').


Functions, classes!

As we saw above, a function is defined using 'def', and is called (after defining) just like in MEL.

def sind(x):
  import math
  return math.sin(x*math.pi/180)

In the above, we're recreating MEL's useful sind() function. The 'def' keyword starts off the fn defn; next is the function's name, then comes the arg list (just like in MEL - but HUGE diff - only var names, NO TYPES!). In the body of the fn, one or more 'return' calls return values..

Once a fn has been defined, use it (call it) just like in MEL..

sind(45)
  
for i in range(90):
  print sind(i)

A huge benefit/feature: a fn can return a tuple!!!!!!

The above means that a Python fn can be written to output possibly MULTIPLE items, of possibly MULTIPLE types! This is a huge no-no in other languages, so programmers use sneaky techniques to get around this. In Python, we'll output multiples openly and with blessings :)

# returns a hodgepodge type of 7 items, as a tuple, in a single return call!!
def howCool():
  return (1,0.56,(),[5,"Test",5-3j],[],7.45,[[]])
  
# this alone is worth switching to Python :) 
a,b,c,d,e,f,g = howCool()
a
b
c
d
e
f
g
g[0] 
g[0][0] # not defined, so will generate an error


Python functions can recurse:

def fact(n):
  if n==1:
    print "End of recursion, returning 1"
    return 1
  else:
    print "Recursing: returning",n,"*","fact(",n-1,")"
    return n*fact(n-1)
    
fact(45) #!!!!!

Try fact(105)!! FYI it has 169 digits, which means they can be arranged like so (1+3+5+7+9+11+13+15+17+19+21+23+25=169):

                                        1                        
	                               081                      
	         	              39675                    
	        	             8240290                  
	       	                    900504101                
		                   30580032964              
	    	                  9720646107774            
    		                 902579144176636          
    		                57322653190990515        
  		               3326984536526808240      
 	 	              339776398934872029657    
	 	             99387290781343681609728  
		            0000000000000000000000000

For more on 105!, see http://webdocs.cs.ualberta.ca/~smillie/APE/APE43.html

Classes are used to group together related variables and functions into a useful unit. Once created, a class can be instantiated into an object, whose methods can be get/set and functions invoked. Objects can communicate with each other by calling functions on them. The class/object mechanism is the way to create custom types.

Here is a basic example, to give you a taste. Overall idea - define a class (with a name, acting as a container), place fns inside it, create "objects" (instances, ie rubber stamps, of the class), call fns as usual, but call them as 'object.function_name'.

class A:
  '''Class A, with two methods f1 and f2'''
  def f1(self): # note the 'self' keyword
    print "Running A.f1()"
  def f2(self):
    print "Running A.f2()"


a = A() # a is now a variable (object) of type (class) A

type(A)
type(a)

A
a

dir(A)
dir(a)


A.__doc__
A.__module__


a.f1() # invoke a's f1() method
a.f1(self) # error - no need to (can't) pass in 'self' - that keyword is valid only inside a class def.

a.f2()

'Packages'

The Maya site-packages directory contains paths to all sorts of packages, and contains packages too..

What are packages? They are collections of modules!

import maya # 'maya' is a package, contains maya.cmds module and others..
import os
print os.path.dirname(maya.__file__)

dir(maya) # lists all the modules in the package
dir(maya.cmds) # lists contents of the maya.cmds module

Lists, map(), "list comprehension"

l = [] # an empty list, fillable (unlike a tuple!)
l.append(1)
l.append('test')
l
l.__contains__(7)
l.__contains__('test')

l[1]
len(l)


# print each elemnt in a list
for a in l:
  print a

# roundabout:
for a in range(len(l)):
  print l[a]

l.append([]) # empty list is appended to our list
l

l[2].append('r')
l # can insert elements into a list's list element!
for i in range(10):
  l[2].append('r')
l

l[2] = {} # last list element is replaced with {}
l

del(l[1])
l

m = l + [1,2,3,9,8,7] # + concatenates lists
m
m += m

map() is a function used to iterate a function over a list or several lists. Output is a list of the function's results.

# iterate sind() for each el in range(0,360)
# replaces the for() version shown earlier!
snf = map(sind,range(0,360)) 
snf
# qn: above prints the list in a single line - how would you print
# each item in a separate line?

'List comprehension' is an alternate way to do this kind of function looping (over a range of inputs), esp. for simpler functions. Example:

li = [1,3,5,7]
li
[item*2 for item in li]
li = [item*2 for item in li] # input (li) is a list, so is the output!
li

We can safely overwrite a list variable with its processed version (which is copied to the variable AFTER getting fully formed in memory).

Q. Where is list comprehension useful?

A. provides a useful shortcut for this common pattern:

for item in list
  if condition on item
   process item, accumulate for output

The 'list comprehension' version is

[expr for item in li if condition]

Example: li = [1,2,3,4,5,6,7,8,9,10] li = [x*x for x in li if x%2 == 0] li # prints [4, 16, 36, 64, 100]

In the above, the input is a list, so is the output, which is a PROCESSED VERSION of the input list!

In summary - following are 3 eqvt ways to create this list: [0,.1,.2..0.9]

l = []
for i in range(10):
 l.append(float(i)/10)

def decimalify(x):
  return float(i)/10.0

l = map(decimalify, range(10))

l = [decimalify(i) for i in range(10)] # optionally, *can* have an if