Python Scripting for Maya Artists

This workshop is geared towards students with little to no scripting/programming experience. By the end of this workshop, you will have the knowledge to write and run Python scripts inside and outside of Maya. You will not learn everything about Python from this workshop. This workshop includes all the basic information you should know in order to be proficient in reading, writing, running, and modifying Python scripts. The purpose of this workshop is not to make you expert Python scripters, but to give you a solid foundation from which in further your Python studies.

Additional Resources

Python in the Computer Graphics Industry

Some Programs that support Python:

What is Python used for?

Introduction to Python

Python is a general purpose scripting language used in many different industries. It is a relatively easy to use and easy to learn language. Python is used in web services, hardware testing, game development, animation production, interface development, database programming, and many other domains.

The Python Interpreter

How does the computer run a Python program? How can you get the computer to understand the Python commands you write in a text file? The Python Interpreter is the software package that takes your code and translates it into a form that the computer can understand in order to execute the commands. This translated form is called byte code.

The Python Interpreter can be downloaded and installed for free from the Python website (http://www.python.org/download/). Linux, Unix, and OSX platforms usually ship with a Python Interpreter already installed. Linux users can also use yum or apt-get to install Python. OSX users can use homebrew or macports to install Python. Windows users can either use Chocolatey to install Python or download and install the interpreter if they want to use Python outside of programs that come with an Interpreter like Maya. Maya 8.5 and later has a Python Interpreter built in so you could learn to use Python inside the script editor of Maya.

There are different versions of the Python Interpreter. At the time of this writing, the latest version is 3.5.1. Maya 2016 uses Python 2.7. There are two main flavors of Python: the 3.x series and the 2.x series. The 3.x series is the latest and greatest but the 2.x series is the most widely supported. You can search online about the differences but Python 2.x is the what you’ll mostly encounter in the CG industry.

What is a Python Script?

A Python script is simply a series of commands written in a file with a .py extension. This text file is also called a Python module. This .py file can be written in any text editor like Note Pad, Word Pad, vi, emacs, Scite, etc. However, you do not always have to write your code in a .py file. For quick tests and scripts, you can run code directly from the Maya script editor or from the interactive prompt.

The Interactive Prompt

Interactive prompts allow you to execute code on the fly and get an immediate result. This is a good way to experiment with chunks of code when you are just learning Python. When you download and install Python, you will see that Python ships with its own editor and interactive prompt called IDLE. IDLE is a convenient editor and prompt with syntax highlighting and is all you really need in order to learn Python when just starting out. The following 3 figures show different ways of accessing an interactive prompt.

IDLE

Figure 2 - Script Editor

Figure 3 - Command Prompt

Running Python Scripts From a Command Prompt

Most of the code you write will be in external .py files like the one shown below.

Figure 4 - A Simple Python Script

To run this script from a command prompt or terminal window, you call the script with the python command (which is on your system once you install Python).

D:\\>C:\\Python26\\python myFirstPythonScript.py You are running a python script. 8 You can add text together. 

If you have the PATH environment variable (Google search “path environment variable”) set to include Python (which should happen by default with the installer), you don’t need to specify the path to the Python executable.

D:\\>python myFirstPythonScript.py You are running a python script. 8 You can add text together. 

You can also route the output of your script to a file to save it for later use:

D:\\>python myFirstPythonScript.py > output.txt 

Figure 5 - Running a Script from IDLE

Another way to run a Python script is to open it in IDLE and run it from within the editor as shown below. This is a great way to quickly experiment with Python code as you learn and write new scripts and applications.

Shebang Lines

On Linux and OSX, you can use something called a shebang line to allow you to run a Python script without having to specify python. For example, you could just run:

$ myFirstPythonScript.py 

To use a shebang line, just add the following to the first lines of your script:

#!/usr/bin/env/ python 

Running Scripts in Maya and within a Python Session

When we are in Maya, we cannot call a script with “python myScript.py”. The python command starts up the Python interpreter and runs the given script with the interpreter. When we are in Maya, the Python interpreter is already running. To call a script from inside Maya and other scripts, you need to import it.

Figure 6 - Calling a Script from Maya

When you import a Python module, you leave off the .py extension. How does Python know where to find the script? Python uses an environment variable called PYTHONPATH to find scripts. The PYTHONPATH variable contains all the directories Python should search in order to find an imported module. By default, inside Maya your scripts directories are added to the PYTHONPATH. Many studios these days take advantage of Maya’s module system which handles adding script directories to the PYTHONPATH. You can read about Maya’s module system in the documentation here.

Another way to add directories to the PYTHONPATH is to create a Maya.env file. The Maya.env file is a file that modifies your Maya environment each time you open Maya. Place the Maya.env file in your “My Documents\maya” folder.

Figure 7 - maya.env File

Consult the Maya documentation for all the other variables you can set in the maya.env file. If you have multiple scripts with the same name in different directories, Python will use the first one it finds.

Notice when I import the module again, the script does not run:

Figure 8 - Re-importing a Module

Importing a module only works once per Python session. This is because when you import a module, Python searches for the file, compiles it to byte code, then runs the code, which is an expensive process to execute multiple times. Once a module is imported, it is stored in memory. To run a script again or if you’ve updated a script and wish to have access to the updates, you need to reload it:

Figure 9 - Reloading a Module

Notice that when I reload the Python module, the result states it read the module from a .pyc file. A .pyc file is a compiled Python file. When you import a Python module, Python compiles the code and generates a .pyc file. You could distribute these .pyc files if you do not want people looking at your code. Note however there are tools available on the internet that will easily decompile a pyc back into source code. Import statements will work with .pyc files.

Python Modules

As you can see, Python modules are simply Python scripts that contain specific functionality. Since Python is so widely used, you can find thousands of free Python modules on the internet that implement various tasks such as networking, image manipulation, file handling, scientific computing, etc. To interface with Maya, you import the maya.cmds module, which is the module that ships with Maya to implement all the Maya commands.

Data Types and Variables

Variables

The most common concept in almost all scripting and programming languages is the variable. A variable is a storage container for some type of data:

Figure 10 - Variables

Variables allow us to store data in order to use it later. Variables, sometimes called identifiers, must start with a non-numeric character or underscore (_), may contain letters, numbers, underscores (_). Identifiers are case sensitive. It is always a good idea to name your variables with descriptive names so your code is easy to read.

Legal variable names

Illegal variable names

Python is a dynamically typed language. This means that a variable can hold one type of value for a while and then can hold another type later.

x = 5 x = 'Now x holds a string' 

Not all languages allow you to do this. For example, in MEL, you have to declare a variable as an integer and then that variable can only hold an integer. This is what is known as a statically typed language.

Numbers and Math

There are 5 different types of number representions in Python: integers, long integers, floating-point, octal/hex, and complex numbers. The two representations that you will work with the most are integers and floating-point literals. Integers are numbers without a decimal point. Floating-point numbers are numeric values with a decimal point.

Python supports all the math operators that you would want to use on these numbers.

x = 4 + 5 # Addition y = x  8 # Subtraction y = y * 2.2 # Multiplication z = y / 1.2 # Division z = y // 3 # Floor division z = z ** 4 # Power z = -z # Negation a = 10 % 3 # Modulus (division remainder) x += 2 # Addition and store the result back in x, same as x = x + 2 x -= 2 # Subtraction and store the result back in x x *= 2 # Multiplication and store the result back in x x /= 2 # Division and store the result back in x 

Notice in some of these statements, I use the same variable on both the right and left side of the assignment operator (=). In most programming languages, the right side is evaluated first and then the result is stored in the variable on the left. So in the statement y = y * 2.2, the expression y * 2.2 is evaluated using the current value of y, in this case 1, and then the result (1 * 2.2) = 2.2 is stored in the variable y.

Math operators have a precedence of operation. That is, some operators always execute before others even when in the same expression. For example the following two lines give different results:

print((5 + 3.2) * 2) 16.4 print(6 + 3.2 * 2) 12.4 

Multiplication and division always get evaluated before addition and subtraction. However, you can control which expressions get evaluated first by using parentheses. Inner most parentheses always get evaluated first.

3 * (54 / ((2 + 7) * 3))  4 3 * (54 / (9 * 3))  4 3 * (54 / 27)  4 3 * 2  4 6  4 2 

When you mix integers and floating-point values, the result will always turn into a floating-point:

4 * 3.1 # Result: 12.4 # 

However, you can explicitly turn an int to a float or a float to an int.

int(4 * 3.1) # Result: 12 # float(12) # Result: 12.0 # 

Strings

Strings are text values. You can specify strings in single, double or triple quotes.

 'This is a string' # Single quotes "This is also a string" # Double quotes """This string can span multiple lines""" # Triple quotes 

Single and double quoted strings are the same. The main thing to keep in mind when using strings are escape sequences. Escape sequences are characters with special meaning. To specify an escape code, you use the backslash character followed by a character code. For example, a tab is specified as ‘\t’, a new line is specified as ‘\n’. You have to be mindful whenever escape sequences are involved because they can lead to a lot of errors and frustration. For example, in Windows, paths are written using the backslash: (e.x. C:\tools\new). If you save this path into a string, you get unexpected results:

print( "C:\tools\new") C: ools Ew 

Python reads the backslashes as an escape sequence. It thinks the ‘\t’ is a tab and the ‘\n’ is a new line. To get the expected results, you either use escaped backslashes or a raw string:

print( "C:\\tools\\new") C:\tools\new print(r"C:\tools\new") C:\tools\new 

The easiest thing to do in this case is to always use forward slashes for paths because Python will find the right file whether on Windows or not.

You can concatenate strings together with the + operator.

x = 'I am ' y = '25 years old.' print(x + y) I am 25 years old. 

However, you cannot add a string and a number.

x = 'I am ' y = 25 print( 'I am ' + y) # Error: cannot concatenate 'str' and 'int' objects # Traceback (most recent call last): # File "", line 3, in # TypeError: cannot concatenate 'str' and 'int' objects # 

Python will throw an error saying you cannot concatenate a string and an integer. You can fix this in a couple different ways. One way is to convert the integer to a string.

x = 'I am ' y = 25 print( 'I am ' + str(y)) I am 25 

The better way is to use string formatting.

String Formatting

String formatting allows you to code multiple string substitutions in a compact way. You use string formatting with the format function available on all strings.

x = 'I am' y = '32 years old.' print( ' '.format(x, y)) I am 32 years old. y = 32 print( 'I am years old.'.format(y)) I am 32 years old. 

The format function allows you do automatically convert multiple variables into a string. The numbers within the curly braces represent the index of argument within the format function.

 ', , '.format( 'a', 'b', 'c') 'a, b, c' '<>, <>, <>'.format( 'a', 'b', 'c') # Python 2.7+ only 'a, b, c' ', , '.format( 'a', 'b', 'c') 'c, b, a' ', , '.format(* 'abc') # unpacking argument sequence 'c, b, a' ' '.format( 'abra', 'cad') # arguments' indices can be repeated 'abracadabra' 

You can also use the format function to control decimal precision, string alignment, as well as adding zero-padding to strings. To read more about string formatting, visit the documentation.

You can also format strings with the % operator.

x = 'I am ' y = '25 years old.' print( '%s%s % (x, y)) I am 25 years old. x = 'I am ' y = 25 print( '%s%d % (x, y)) I am 25 

To use the % operator, you provide a format string on the left of the % operator and the values you want to print on the right of the % operator. Different types of values have different format codes. Common codes include:

In the second example above, ‘%s%d’ % (x, y) contains two format codes, a string followed by an integer. On the right side of the % operator, we need to specify a value for each of the format codes in the order they appear in the format string. String formatting not only lets us code in a more compact way, it also lets us format values for output:

print( '%03d' % 5) 005 print( '%+03d' % 6) +06 print( '%+03d' % -4) -04 print( '%.5f' % 4.4242353534) 4.42424 

Like the format method, the % operator allows us to specify decimal precision, how many spaces a number should be printed with, whether to include the sign, etc. This is especially useful when printing out large tables of data. To read more about the % operator in string formatting, visit the documentation.

String Methods

Methods are chunks of code that perform some type of operation. We will learn more about methods, also known as functions, in more detail later on, but now is a good time to introduce you to the syntax of calling a method. We call a method using the dot operator (.):

object.method() 

An object is an instance of a particular type. For example, we could have a string object.

x = 'This is a string.' # x is a string object y = 'Another string.' # y is another string object 

When we read the code, some_object.some_method(), we say we are calling the method named some_method from the object called some_object. Most objects have many callable methods. The format method described in the previous section is a string method. Below are some of the methods found in string objects:

x = 'This is my example string.' print(x.lower()) this is my example string. print(x.upper()) THIS IS MY EXAMPLE STRING. print(x.replace( 'my', 'your')) This is your example string. print(x.title()) This Is My Example String. print(x.endswith( 'ng.')) True print(x.endswith( 'exam')) False 

Some of the methods have arguments in the parentheses. Many methods allow you to pass in values to perform operations based on the arguments. For example x.replace(‘my’, ‘your’) will return a copy of string x with all of the ‘my’ instances replaced with ‘your’. To view a list of the available string methods, visit the documentation.

There are many methods available for many different types of objects and there is no need to memorize them. You will begin to memorize them after using them a lot. You can find help documentation for all objects with the help command.

help(str) 

The help command will print out the documentation associated with an object, class, or function.

Lists

Lists are sequences or arrays of data. Lists allow us to use organized groups of data in our scripts.

list_of_lights = [ 'keyLight', 'rimLight', 'backLight', 'fillLight'] print(list_of_lights[0]) # print the first element keyLight print(list_of_lights[2]) # print the third element backLight print list_of_lights[3]) # print the fourth element fillLight print(list_of_lights[-1]) # print the last element fillLight print(list_of_lights[0:2]) # print the first through second elements [ 'keyLight', 'rimLight'] print(len(list_of_lights)) # print the length of the list 4 

We can access elements in a list with a numeric index. The indices start at index 0 and increment with each value in the list. We can also access subsets of lists with slicing

print(list_of_lights[0:2], list_of_lights[:3], list_of_lights[:-1]) [ 'keyLight', 'rimLight'] [ 'keyLight', 'rimLight', 'backLight'] [ 'keyLight', 'rimLight', 'backLight'] list_of_lights[0:2] = [ 'newLight', 'greenLight'] print(list_of_light) [ 'newLight', 'greenLight', 'backLight', 'fillLight'] 

When an index is negative, it counts from the end of the list back. Lists can also contain mixed types of data including other lists.

my_list = [3, 'food', [another_list, 4 * 2, 'dog'], 80.3] print(my_list[2][1]) 8 

When you try to access an element that does not exist, Python will throw an error.

print(my_list[34]) # Error: list index out of range # Traceback (most recent call last): # File "", line 1, in # IndexError: list index out of range # 

Lists, like strings, have their own set of methods available.

list_of_lights = [ 'keyLight', 'rimLight', 'backLight', 'fillLight'] list_of_lights.sort() print(list_of_lights) [ 'backLight', 'fillLight', 'keyLight', 'rimLight'] list_of_lights.reverse() print(list_of_lights) [ 'rimLight', 'keyLight', 'fillLight', 'backLight'] list_of_lights.append( 'newLight') print list_of_lights [ 'rimLight', 'keyLight', 'fillLight', 'backLight', 'newLight'] 

This concept of indexing is not unique to lists. We can actually access strings the same way.

x = 'Eat your vegetables' print(x[0:6]) Eat yo print(x[:-9]) Eat your v print(x[5:]) our vegetables print(x.find( 'veg')) 9 print(x.split( 'e')) [ 'Eat your v', 'g', 'tabl', 's'] 

You can think of strings as lists of characters. The main difference is strings cannot be edited in place with indices. For example, the following is illegal.

x[4] = 't' # Error: 'str' object does not support item assignment # Traceback (most recent call last): # File "", line 1, in # TypeError: 'str' object does not support item assignment # 

Strings are what are known as immutable objects. Once they are created, they cannot be changed. Lists on the other hand are mutable objects, meaning they can change internally.

Tuples

Tuples are the same as lists except they are immutable. They cannot be changed once created.

my_tuple = (5, 4.2, 'cat') my_tuple[1] = 3 # Error: 'tuple' object does not support item assignment # Traceback (most recent call last): # File "", line 1, in # TypeError: 'tuple' object does not support item assignment # 

What is the purpose of tuples? There are aspects of Python that use or return tuples. I’d say 99.9% of the time, you’ll be using lists. Just be aware that you cannot change a tuple when one eventually pops up.

Dictionaries

Dictionaries are like lists except their elements do not have to be accessed with numeric indices. Dictionaries are a type of look-up table or hash map. They are useful in storing unordered types of data.

characters =  'male': [ 'Derrick', 'Chad', 'Ryan'], 'female': [ 'Olivia', 'Sarah', 'Zoe']> print(characters[ 'male']) [ 'Derrick', 'Chad', 'Ryan'] print(characters[ 'female']) [ 'Olivia', 'Sarah', 'Zoe'] # Same functionality, different syntax: characters = dict(male=[ 'Derrick', 'Chad', 'Ryan'], female=[ 'Olivia', 'Sarah', 'Zoe']) print(characters[ 'male']) [ 'Derrick', 'Chad', 'Ryan'] print(characters[ 'female']) [ 'Olivia', 'Sarah', 'Zoe'] # Some methods of dictionaries print(characters.keys()) [ 'male', 'female'] print(characters.values()) [[ 'Derrick', 'Chad', 'Ryan'], [ 'Olivia', 'Sarah', 'Zoe']] print( 'male' in characters.keys()) True print( 'clown' in characters.keys()) False # Elements can be added on the fly characters[ 'alien'] = [ 'ET', 'Alf'] characters[6] = 4.5 

Booleans and Comparing Values

In many of your programs, you’ll need to determine the relationship between variables such as if values are equal or if a value is greater than or less than another. These comparisons return a boolean value. A boolean value is simply True or False. You have already seen these values returned in a few of the previous examples.

x == y # tests if x is equal to y x != y # tests if x does not equal y x > y # tests if x is greater than y x  y # tests if x is less than y x >= y # tests if x is greater than or equal to y x = y # tests if x is less than or equal to y x = 4.3 y = 2 5 == 5, 5 == 8, x == y, x == 4.3 (True, False, False, True) x = y, x > y, x == y + 2.3, x >= 93 (False, True, True, False) a = 'alpha' b = 'beta' a > b, a == 'alpha', a  b (False, True, True) 

The following values are also considered False:

 '' # Empty strings [] # Empty lists > # Empty dictionaries 0 # 0 valued numbers None # An empty value 

The following values are considered True:

 'text' # Strings with any characters [2, 3] # Lists with elements 3 : 4.5> # Dictionaries with elements 2.3 # Non-zero numbers 

Controlling the Flow of Execution

All the scripts so far have been executed line by line. In most of your scripts, you will need to be able to control the flow of execution. You will need to execute code only if certain conditions are met and you will need to execute code multiple times. This is called the logic of the script. Python, like most other languages, supports the basic constructs to accomplish this.

Conditionals

To execute code only if a condition is True or False, you use the if , elif , and else statements.

The if statement runs a block of code (the indented portion) if the corresponding condition is True.

x = 5 if x == 5: x += 3 print(x) 8 

An if statement can have a corresponding else statement which runs if the condition in the if statement is False.

x = 5 if x  5: x += 3 else: # Optional else x *= 2 print(x) 10 

Multiple if statements can be chained together with elif statement.

x = 5 if x > 5 and x != 2: x += 2 elif x == 5: # Optional elif x += 4 elif x == 9: # Optional elif x -= 3 else: # Optional else x *= 2 print(x) 9 

Conditionals can take many forms.

x = [ 'red', 'green', 'blue'] if 'red' in x: print( 'Red hot!') Red hot! 

if statements let you select chunks of code to execute based on boolean values. In a sequence of if/elif/else statements, the first if statement is evaluated as True or False. If the condition is True, the code in its corresponding code block is executed. If the condition is False, the code block is skipped and execution continues to the next else or elif (else if) statement if one exists. else statements must always be preceded by an if or elif statement but can be left out if not needed. elif statements must always be preceded by and if statement but can left out if not needed.

Code Blocks

Code blocks are chunks of code associated with another statement of code. Take the following code for example.

x = 'big boy' y = 7 if x == 'big boy' and y  8: print( 'Big Boy!') y += 3 

The last two lines are in the code block associated with the if statement. When the condition of the if statement is evaluated as True, execution enters the indented portion of the script. Code blocks in Python are specified by the use of whitespace and indentation. You can use any number of spaces or even tabs, you just have to be consistent throughout your whole script. However, even though you can use any amount of whitespace, the Python standard is 4 spaces. Code blocks can be nested in other code blocks, you just need to make sure your indentation is correct.

x = 5 y = 9 if x == 5 or y > 3: x += 3 if x == 8: x *= 2 elif y == 7: y -= 3 if x == 16 or y  21: x -= 10 else: y *= 3 else: x += 2 print(x) 16 print(y) 9 

if/elif/else statements that are chained together need to be on the same indentation level. Read though the previous example and work out the flow of execution in your head or on paper.

While Loops

While loops allow you to run code while a condition is True.

x = 5 while x > 1: x -= 1 print(x) 4 3 2 1 

In the above example, the condition is tested as True, so execution enters the code block of the while loop. When execution reaches the print statement, the value of x has been decremented and execution returns to the while statement where the condition is tested again. This loop continues until the condition is False. You have to take care that the condition eventually evaluates to False or else you will get an infinite loop.

x = 5 while x > 1: x += 1 

In the above example, x will continue to increment and the condition will always be True. This is called an infinite loop. If you create one of these, you’ll have to shut down your program, Ctrl-Alt-Delete, or force quit out of Maya.

Sometimes you will want to exit out of a loop early or skip certain iterations in a loop. These can be done with the break and continue commands.

x = 0 while x  10: x += 1 if x % 2: continue # When x is an odd number, skip this loop iteration if x == 8: break # When x == 8, break out of the loop print(x) else: # This optional else statement is run if the loop finished x = 2 # without hitting a break 2 4 6 # This code causes an infinite loop, try to find out why. x = 0 while x  10: if x % 2: continue if x == 8: break print(x) x += 1 

For Loops

For loops iterate over sequence objects such as lists, tuples, and strings. Sequence objects are data types comprised of multiple elements. The elements are usually accessed by square brackets (e.g. my_list[3]) as you’ve seen previously. However, it is often useful to be able to iterate through all of the elements in a sequence.

some_items = [ 'truck', 'car', 'semi'] for x in some_items: print(x) truck car semi 

The range function generates a list of integers.

print(range(5)) # Built-in function range creates a list of integers [0, 1, 2, 3, 4] for x in range(5): # Create a spine joint in Maya pass # pass is used when you have no code to write 

You can iterate through letters of a string.

for letter in 'sugar lumps': print(letter) s u g a r l u m p s 

You can iterate through two lists of the same length with the zip function.

some_items = [ 'truck', 'car', 'semi'] some_colors = [ 'red', 'green', 'blue'] # The zip function pulls out pairs of items for item, color in zip(some_items, some_colors): print( 'The is '.format(item, color)) The truck is red The car is green The semi is blue 

Like the while loop, for loops support the continue, break, and else statements.

for i in range(0, 10, 2): # range(start, stop, step) if i % 2 == 1: continue if i > 7: break print(i) else: # Optional else print( 'Exited loop without break') 0 2 4 6 

Functions

Previously, we’ve seen functions and methods built in to Python (such as range and zip) and built in to different data types (string, list, and dictionary methods). Functions allow us to create reusable chunks of code that we can call throughout our scripts. Functions are written in the form

def function_name(optional, list, of, arguments): # statements def my_print_function(): print( 'woohoo!') my_print_function() woohoo! for x in range(3): my_print_function() woohoo! woohoo! woohoo! 

Functions also accept arguments that get passed into your function.

def print_my_own_range(start, stop, step): x = start while x  stop: print(x) x += step print_my_own_range(0, 5, 2) 0 2 4 

Functions can return values.

def get_my_own_range(start, stop, step): x = start list_to_return = [] while x  stop: list.append(x) x += step return list_to_return x = getMyOwnRange(0, 5, 2) print(x) [0, 2, 4] 

Functions can also return multiple values.

def get_surrounding_numbers(number): return number + 1, number - 1 x, y = get_surrounding_numbers(3) print(x, y) 2 4 

Function Arguments

Function parameters (arguments) can be passed to functions a few different ways. The first is positional where the arguments are matched in order left to right:

def func(x, y, z): pass func(1, 2, 3) # Uses x = 1, y = 2, z = 3 

Functions can have a default value if a value isn't passed in:

def func(x, y=3, z=10): pass func(1) # Uses x = 1, y = 3, z = 10 

You can also specify the names of arguments you are passing if you only want to pass certain arguments. These are called keyword arguments. This is the method used in the Maya commands.

def func(x=1, y=3, z=10): pass func(y=5) # Uses x = 1, y = 5, z = 10 

You can also have an arbitrary number of arguments:

def func(*args): print(args) func(1, 2, 3, 4) # Passes the arguments as a tuple (1, 2, 3, 4) 

And you can have an arbitrary number of keyword arguments:

def func(**kwargs): print(kwargs) func(joints=1, x=2, y=3, z=4) # Passes the arguments as a dictionary  'y': 3, 'joints': 1, 'z': 4, 'x': 2> 

Often you will see code where the arguments are unknown and both *arg and **kwargs will be used:

def func(*args, **kwargs): print(args) print(kwargs) func( 'a', 'b', joints=1, x=2, y=3, z=4) ( 'a', 'b')  'y': 3, 'joints': 1, 'z': 4, 'x': 2> 

Scope

Scope is the place where variables and functions are valid. Depending on what scope you create a variable, it may or may not be valid in other areas of your code.

def my_function(): x = 1 # x is in the local scope of my_function 

Basic scope rules:

  1. The enclosing module (the .py file you create the variable in) is a global scope.
  2. Global scope spans a single file only.
  3. Each call to a function is a new local scope.
  4. Assigned names are local, unless declared global.
x = 10 def func(): x = 20 func() print(x) # prints 10 because the function creates its own local scope x = 10 def func(): global x x = 20 func() print(x) # prints 20 because we explicitly state we want to use the global x x = 10 def func(): print(x) func() # prints 10 because there is no variable x declared in the local # scope of the function so Python searches the next highest scope 

Lambda Expressions

Lambda expressions are basically a way of writing short functions. Normal functions are usually of the form:

def name(arg1, arg2): # statements. 

Lambda expressions are of the form:

lambda arg1, arg2: expression 

This is useful because we can embed functions straight into the code that uses it. For example, say we had the following code:

def increment(x): return x + 1 def decrement(x): return x  1 def crazy(x): return x / 2.2 ** 3.0 D =  'f1': increment, 'f2': decrement, 'f3': crazy> D[ 'f1'](2) 

A drawback of this is that the function definitions are declared elsewhere in the file. Lambda expressions allow us to achieve the same effect as follows:

D =  'f1': (lambda x: x + 1), 'f2': (lambda x: x - 1), 'f3': (lambda x: x / 2.2 ** 3.0) > D[ 'f1'](2) 

Note that lambda bodies are a single expression, not a block of statements. It is similar to what you put in a def return statement. When you are just starting out using Python in Maya, you probably won’t be using many lambda expressions, but just be aware that they exist.

Exceptions and Error Handling

Whenever an error is encountered in your program, Python will raise an exception stating the line number and what went wrong:

x = [1,2,3] x[10] Traceback (most recent call last): File " ", line 1, in x[10] IndexError: list index out of range 

If we want the code to continue running, we can use a try/except block:

x = [1,2,3] try: x[10] except IndexError: print( "What are you trying to pull?") print( "Continuing program. ") 

Another variation includes both the else and finally statements, both of which are optional.

x = [1,2,3] try: x[10] except IndexError: print( 'What are you trying to pull?') else: # Will only run if no exception was raised print( 'No exception was encountered') finally: print( 'This will run whether there is an exception or not') print( "Continuing program. ") 

You can raise your own exception if you want to prevent the script from running:

if func() == 'bad return value': raise RuntimeError( 'Something bad happened') 

There are many different types of built-in exceptions that you can raise. You can even create your own types of exceptions. To read more about the different types of built-in exceptions visit the documentation here.

Exceptions that I regularly use include:

Modules

We learned in the beginning that Python modules are simply .py files full of Python code. These modules can be full of functions, variables, classes (more on classes later), and other statements. We also learned that to load a Python module from within Maya or another interactive prompt, we need to import the module. When we import a module, we gain access to all the functionality of that module.

# mymathmodule.py def add(x, y): return x + y def subtract(x, y): return x  y 

Inside Maya, we can access this functionality as follows:

import mymathmodule mymathmodule.add(1, 2) 
import mymathmodule as mm mm.add(1,2) 
from mymathmodule import add add(1, 2) 
from mymathmodule import * add(1, 2) subtract(1, 2) 

Remember, we can only import a module once per Python session. If we were to update the code in mymathmodule.py, we wouldn’t have access to the updates until we reload the module.

reload(mymathmodule) 

We can also import modules into other modules. If we have one module full of some really useful functions, we can import that module into other scripts we write (which are also modules) in order to gain access to those functions in our current module.

Module Packages

Packages allow us to organize our Python modules into organized directory structures. Instead of placing all of our modules into one flat directory, we can group our modules in subdirectories based on functionality.

Figure 11 - A Sample Package Organization

To create a package, create a folder in one of you PYTHONPATH directories like your Maya script directory or the scripts directory of your Maya module (not to be confused with a Python module), then create a file called __init__.py inside of that new folder. The __init__.py can be empty. You can then place your modules into that package and import them as follows.

import packagename.modulename 

You can have packages inside of packages.

import mymayatools.rigging.ikleg as ikleg ikleg.create( 'L_leg_joint') 

Any code you put in the __init__.py file of a package gets executed with the package. However, it is usually good practice not to execute any complex code when the user imports your module or package.

Built-In and Third Party Modules

Python ships with many built-in modules that give you access to really useful functionality. There is a module with many useful math functions, a module to copy files, a module to generate random numbers, etc. Some commonly used useful modules include:

import sys # System commands import os # Generic module to the operating system import shutil # Contains file copying functions import struct # Deals with binary data import json # Tools to deal with JSON data import xmllib # XML parser module import math # Contains many useful math operations import random # Random number module import re # Regular expressions module import argparse # Command-line option parser import logging # Customizable logging functionality import webbrowser # Open a web browser to a certain page 

There are also hundreds of third-party modules available online. For example, the PIL module contains many functions that deal with image manipulation. To find out what functions are in a module, run the help command or view online documentation.

Files and Paths

Python is used a lot in managing files and file systems.

output = open( 'X:/data.dat', 'w') # Open file for writing input = open( 'data.txt', 'r') # Open file for reading x = input.read() # Read entire file x = input.read(5) # Read 5 bytes x = input.readline() # Read next line lines = input.readlines() # Read entire file into a list of line strings output.write(Some text) # Write text to file output.writelines(list) # Write all strings in list L to file output.close() # Close manually 

Here’s a more practical example of opening a maya ascii file and renaming myBadSphere to myGoodSphere.

with open( 'C:/myfile.ma', 'r') as fh: lines = fh.readlines() # Use a list comprehension to generate a list of items in one go lines = [line.replace( 'myBadSphere', 'myGoodSphere') for line in lines] with open( 'C:/myfile2.ma', 'w') as fh: fh.writelines(lines) 

Python also makes it easy to find files and traverse directory hierarchies.

# Get all the Maya files in a directory import os maya_files = [] for content in os.listdir( 'C:/somedirectory'): if content.endswith( '.ma') or content.endswith( '.mb'): maya_files.append(content) # Traverse a directory and all subdirectories for fbx files fbx_files = [] for root, dirs, files in os.walk( '/my/tools/directory'): print( 'Currently searching '.format(root)) for file_name in files: if file_name.endswith( '.fbx'): fbx_files.append(os.path.join(root, file_name)) 

Classes and Object Oriented Programming

Python is an object-oriented language. It supports all the usual functionality of such languages such as classes, inheritance, and polymorphism. If you are not familiar with object-oriented programming (or scripting), it basically means it is easy to create modular, reusable bits of code, which are called objects. We have already been dealing with objects such as string objects and list objects. Below is an example of creating a string object and calling its methods.

x = "happy" x.capitalize() # returns Happy x.endswith( "ppy") # returns True x.replace( "py", "hazard") # returns "haphazard" x.find( "y" ) # returns 4 

You are free to use Python without using any of its object oriented functionality by just sticking with functions and groups of statements as we have been doing throughout these notes. However, if you would like to create larger scale applications and systems, I recommend learning more about object oriented programming. The Maya API and PyMEL, the popular Maya commands replacement module, are built upon the notions of object oriented programming so if you want to use API functionality or PyMEL in your scripts, you should understand the principles of OOP.

Note that while I give a brief introduction to object-oriented programming in this paper, a few pages cannot do the topic any justice. I recommend reading more about object-oriented programming in the endless resources found online and in books.

Classes

Classes are the basic building blocks of object oriented programming. With classes, we can create independent instances of a common object. It is kind of like duplicating a cube a few times. They are all cubes, but they have their own independent attributes.

class Shape(object): def __init__(self, name): self.name = name def print_me(self): print( 'I am a shape named .'.format(self.name)) shape1 = shape(name= 'myFirstShape') shape2 = shape(name= 'mySecondShape') shape1.print_me() I am a shape named myFirstshape. shape2.print_me() I am a shape named mySecondShape. 

The above example shows a simple class that contains one data member (name), and two functions. Functions that begin with a double underscore usually have a special meaning in Python. The __init__ function of a class is a special function called a constructor. It allows us to construct a new instance of an object. In the example, we create two independent instances of a shape object: shape1 and shape2. Each of these instances contains its own copy of the name attribute defined in the class definition. In the shape1 instance, the value of name is “myFirstShape”. In the shape2 instance, the value of name is “mySecondShape”. Notice we don’t pass in any value for the self argument. We don’t pass in any value for the self argument because the self argument refers to the particular instance of a class.

The first argument in all class member methods (functions) should be the self argument. The self argument is used to represent the current instance of that class. You can see in the above example when we call the print_me method of each instance, it prints the name stored in each separate instance. So objects are containers that hold their own copies of data defined in their class definition.

Inheritance and Polymorphism

Inheritance and polymorphism are OOP constructs that let us build off of existing functionality. Say we wanted to add additional functionality to the previous shape class but we don't want to change it because many other scripts reference that original class. We can create a new class that inherits the functionality of that class and then we can use that inherited class to build additional functionality:

# Inherit from Shape class PolyCube(Shape): def __init__(self, name, length, width, height): # Call the constructor of the inherited class super(PolyCube, self).__init__(name) # Store the data associated with this inherited class self.length = length self.width = width self.height = height def print_me(self): super(PolyCube, self).print_me() # The .2f in the string format means use 2 decimal places print( 'I am also a cube with dimensions , , .'.format( self.length, self.width, self.height)) class PolySphere(Shape): def __init__(self, name, radius): # Call the constructor of the inherited class super(PolySphere, self).__init__(name) # Store the data associated with this inherited class self.radius = radius def print_me(self): super(PolySphere, self).print_me() print( 'I am also a sphere with a radius of .'.format(self.radius)) cube1 = PolyCube( 'firstCube', 2.0, 1.0, 3.0) cube2 = PolyCube( 'secondCube', 3.0, 3.0, 3.0) sphere1 = PolySphere( 'firstSphere', 2.2) sphere2 = PolySphere( 'secondSphere', 2.5) shape1 = Shape( 'myShape') cube1.print_me() I am a shape named firstCube. I am also a cube with dimensions 2.00, 1.00, 2.00. cube2.print_me() I am a shape named secondCube. I am also a cube with dimensions 3.00, 3.00, 3.00. sphere1.print_me() I am a shape named firstSphere. I am also a sphere with a radius of 2.20. sphere2.print_me() I am a shape named secondSphere. I am also a sphere with a radius of 2.50. 

In the above example, we create two new classes, PolyCube and PolySphere, that inherit from the base class, Shape. We tell a class to inherit from another class by placing the class to inherit from in parentheses when we declare the derived class. The two new classes will have all the data and methods associated with the shape base class. When we call the constructor method, __init__, of PolyCube and PolySphere, we still want to use the functionality of the constructor of its super class, shape. We can do this by calling the super class constructor explicitly with super(PolyCube, self).__init__(name). This will set the name variable since the Shape constructor initializes the name member variable. The second method, print_me, contains the added functionality of our new class. The method name is the same as the method name in the super class, Shape. However, when we call the method with cube1.print_me(), Python knows to use the method in PolyCube and not the method from Shape. This is called polymorphism. It allows us to replace, change, or add functionality to existing classes. By creating these hierarchies of objects, you can create pretty complex systems in neat, reusable classes that will keep your code clean and maintainable.

The previous example is an extremely simplified version of Maya’s architecture. Nodes inherit off of other nodes to build a complex hierarchy. Below is part of Maya’s object oriented node hierarchy:

Figure 12 - Maya Inheritance Hierarchy

Documenting Your Code

You have seen me putting in comments in most of my samples. These not only help other people understand your code but will also help you remember what you were thinking when you revisit your scripts months later to fix something. Comments not only explain your code, but also explain how to use your code or functions that you want others to use.

Python supports a method of creating documentation for your modules known as docstrings. Docstrings are strings written with triple quotes (“””) and can be placed at the top of modules and inside functions. When you type help(modulename), Python will print some nice documentation using the docstrings that you specified in your module.

Figure 13 - Documenting Your Code

There is an official specification on how you should format your docstrings, called PEP 0257. Many people don't strictly follow this format and use a format that is supported by documentation generation tools like Doxygen, Epydoc, and Sphix. Formats include (taken from StackOverflow):

Epytext

 """ This is a javadoc style. @param param1: this is a first param @param param2: this is a second param @return: this is a description of what is returned @raise keyError: raises an exception """ 

reST

 """ This is a reST style. :param param1: this is a first param :param param2: this is a second param :returns: this is a description of what is returned :raises keyError: raises an exception """ 

Google

 """ This is an example of Google style. Args: param1: This is the first param. param2: This is a second param. Returns: This is a description of what is returned. Raises: KeyError: Raises an exception. """ 

Numpydoc

 """ My numpydoc description of a kind of very exhautive numpydoc format docstring. Parameters ---------- first : array_like the 1st param name `first` second : the 2nd param third : 'value', 'other'>, optional the 3rd param, by default 'value' Returns ------- string a value in a string Raises ------ KeyError when a key error OtherError when an other error """ 

I recommend using one of these widely used formats instead of making one up on your own. You should try to put docstrings in your code as much as possible. It will save you and your coworkers a lot of time down the road.

Coding Conventions

Unlike many other languages, Python has an official coding convention called the PEP8 standard. A coding convention describes the style rules and format of your code. It is by no coincidence that all the code examples listed so far use underscore_separated variable and function names, four spaces for indentations, and CaptilizedCamelCase for class names. These conventions are all part of the PEP8 standard. You'll notice however that Maya does not follow the PEP8 standard. The main reason for this is that the Python API is generated procedurally from the C++ API. When writing Python code, I highly encourage you to follow the PEP8 standard even if you prefer or have used a different convention in the past. The exception being if you are contributing to an existing project that uses a different standard.

Most of the Python world uses the PEP8 standard. Unless you are working in an existing codebase, try to use the official and accepted standard. The only time not to follow PEP8 conventions (as noted in PEP8 itself) is to follow the standard of the existing codebase you are working with. You do not need to follow every single little rule, just be consistent.

You can read more about the PEP8 standard here.

An easy way to automatically format your code is to use a tool such as Python Black

PEP8 Quick Notes

># Class names class MyClassName(object): # Yes class myclassName(object): # No # Variable names skin_joints = [ 'joint1', 'joint2'] # Yes skinJoints = [ 'joint1', 'joint2'] # No # Function names def calculate_bounds(): # Yes def calculateBounds(): # No # Spacing [1, 2, 3, 4] # Yes [1,2,3,4] # No if joint_height > 10.0: # Yes if joint_height>10.0: # No get_export_nodes(root= 'skeleton_grp') # Yes get_export_nodes( root = 'skeleton_grp' ) # No 

Writing Clean Code

Writing clean, readable code is something we should all strive for. If you're just learning to program, you may not be paying attention to how clean your code is, but after a while when you start working with other people with a shared code base you may begin to recognize the importance of writing clean code. Code is read a lot more than it is written and sloppy code just depresses and frustrates people. Being sloppy up front when you are under pressure actually slows you down in the long term. You will end up creating more bugs which leads to more maintenance.

In this section, I'll give brief pointers on how to make your code cleaner and easier to read. For more in-depth discussions on writing clean code, I recommend the excellent book, Clean Code: A Handbook of Agile Software Craftsmanship, by Robert C Martin, and the Pluralsight course, Clean Code: Writing Code for Humans by Cory House.

The DRY Principle

DRY stands for Don't Repeat Yourself. You should state a piece of logic once and only once. If you find yourself copying and pasting chunks of code multiple times, that should be a signal that you are repeating yourself. Repeating code just leads to more code to maintain and debug. For example, the following code has repeated code:

sphere = create_poly_sphere(name= 'left_eye') assign_shader(sphere, 'blinn1') parent_constrain(head, sphere) sphere = create_poly_sphere(name= 'right_eye') assign_shader(sphere, 'blinn2') parent_constrain(head, sphere) 

This code should be written like the following:

def create_eye(name, shader): sphere = create_poly_sphere(name=name) assign_shader(sphere, shader) parent_constrain(head, sphere) create_eye( 'left_eye', 'blinn1') create_eye( 'right_eye', 'blinn2') 

The second code example is easier to maintain. If an update needs to be implemented, we only have to update code in one place rather than multiple places.

Use Clean Names

The names of your classes, variables, and functions contribute greatly to how readable and clean your code is. Take this code snippet from an actual VFX studio pipeline tool:

curr= os.environ.get( 'CURRENT_CONTEXT') if curr: cl= curr.split( '/') self.__curr= [None] * 6 self.setType( cl[0] ) self.setSeq( cl[1] ) if len( cl ) > 3: self.setSubseq( cl[2] ) self.setShot( '/'.join( cl[2:] ) ) else: self.setShot( cl[-1] ) if wa: self.__wa= wa else: self.__wa= os.environ.get( 'CURRENT_WORKAREA') 

Can you tell what this code is doing? If you're familiar with writing pipeline environment tools, you might recognize what it is trying to do. What is self.__curr? Why is it a list of 6 values? What do the elements of cl repreprent? Why does the length of cl being greater than 3 differentiate one block of the if statement from the other?

A cleaner implementation would look something like this:

context_type, sequence, subsequence, shot = self.get_current_context() self.set_type(context_type) self.set_sequence(sequence) if subsequence: self.set_subsequence(subsequence) if shot: self.set_shot(shot) self.__work_area = work_area if work_area else self.get_current_work_area() 

The above code cleans up all the list indices and string manipulations to make the code easier to read and understand. The individual list elements have been assigned meaningful names. Also the environment variable accesses have been extracted away into new methods. This makes the code easier to maintain. What happens if we want to change the name of the environment variables or maybe we want to read the values from a configuration file on disk? Extracting those values to functions would let us update the code in one place rather than multiple direct accesses to the environment variable.

Naming Classes

Class names should be a noun because they represent objects. The name should be as specific as possible. If the name cannot be specific, it may be a sign that the class needs to be split into smaller classes. Classes should have a single responsibility.

Bad class names include:

Good class names include:

Naming Methods

Method names should be verbs because they perform actions. There should be no need to read the contents of a method if the name accurately describes what the method does. If the function is doing one thing (as it should) it should be easy to name. If not, split the code into smaller functions. Sometimes explaining the code out loud and help you name the function. If you say “And”, “If”, or “Or” it is a warning sign that the method is doing too much.

Bad method names include:

Good method names include:

Methods should only perform the actions described by the name. Any other actions are called side effects and they can confuse people using your code. For example, a method called validate_form should not save the form. A method called publish_model should not smooth the normals. A method called prune_weights should not remove unused influences.

Avoid Abbreviations

Abbreviated text may be easier to type, but code is read more than it is written. When people talk about code or read it silently, it is harder to say the abbreviations. There are also no standards when referring to abbreviations.

Naming Booleans

Boolean values should be able to fit in an actual sentence of saying something is True or False.

Bad boolean names:

Good Boolean names:

Symmetrical Names

When names have a corresponding opposite, be consistent and always use the same opposite.

Working with Booleans

When comparing Booleans, compare them implicitly:

# Don't do this if (is_valid == True): # do something # Instead do this if is_valid: # do something 

When assigning booleans, assign them implicitly:

# Don't do this if len(items) == 0: remove_entry = True else: remove_entry = False # Instead do this remove_entry = len(items) == 0 

Avoid using booleans that represent negative values. This leads to double negatives, which end up confusing people:

# Don't do this if not not_valid: pass # Instead do this if valid: pass 

Use Ternaries

Ternaries are ways of assigning a value to a variable depending on if some condition is True or False. For example:

# Don't do this if height > height_threshold: category = 'giant' else: category = 'hobbit' # Instead do this category = 'giant' if height > height_threshold else 'hobbit' 

Don't Use String as Types

You may have encountered code similar to the following:

if component_type == 'arm': # do something elif component_type == 'leg': # do something else 

This is considered bad form for various reasons. If we end up wanting to change the value of one of these types, we have to change it in all the places that it is used. It can also lead to typos and inconsistencies. A better approach would be:

class ComponentType(object): arm = 'arm' leg = 'leg' if component_type == ComponentType.arm: # do something elif component_type == ComponentType.leg: # do something else 

The new code provides one place to change and update values (the DRY principle). It also provides auto-completion support and is more searchable if you are using an IDE like PyCharm or Eclipse.

Don't Use Magic Numbers

Magic numbers are numeric values that seemed to have been pulled out of nowhere. For example, the following code was pulled from an actual VFX pipeline tool:

if run_mode  3: run_mode = 5 elif run_mode == 3: run_mode = 4 

What do these numbers mean? You would need to search all over code that could span multiple files to figure out what these numbers represent. A better approach would be:

class JobStatus(object): waiting = 1 starting = 2 running = 3 aborting = 4 done = 5 def __init__(self, value=JobStatus.waiting): self.status = value def not_yet_running(self): return self.status  JobStatus.running def abort(self): if self.not_yet_running(): self.status = JobStatus.done elif self.status == JobStatus.running: self.status = JobStatus.aborting # job_status is the new run_mode job_status.abort() 

Encapsulate Complex Conditionals

Sometimes you may have conditionals with many comparisons chained together. At some point, this is going to get hard to read. For example:

# Instead of this if (obj.component.partial_path.startswith( 'model') and namespace == GEOM and has_rigging_publish(obj.child) and edits_path): # Encapsulate the complex conditional in a function or variable def is_model_only_publish(obj): return (obj.component.partial_path.startswith( 'model') and namespace == 'GEOM' and has_rigging_publish(obj.child) and edits_path) if is_model_only_publish(obj): 

Writing Clean Functions

Functions should be created in order to help convey intent. They should do one thing and one thing only as this aids the reader, promotes reuse, eases testing, and avoids side effects. Strive for a function to only have 0-3 parameters with a max of 7-9 parameters. Anything longer makes it harder for readers to keep track of all the parameters while running through the code in their head. Functions should be relatively short, maybe no more than 100 or so lines. If a function is longer, it may be time to refactor (update) the code into smaller functions.

Extracting a Method

If you find your code 3 or 4 indentation levels deep, it may be time to extract some of that code into a separate function. For example:

# Instead of this if something: if something_else: while some_condition: # do something complicated # Do this instead if something and something_else: do_complicated_things() def do_complicated_things(): while some_condition: # do something complicated 

Return Early

People can usually only keep track of a handful of trains of thought at a time. Therefore, we should try to organize our code in as many discrete independent chunks as possible. For example:

# Instead of this def validate_mesh(mesh): result = False if has_uniform_scale(mesh): if has_soft_normal(mesh): if name_is_alphanumeric(mesh): result = name_is_unique(mesh) return result # Do this def validate_mesh(mesh): if not has_uniform_scale(mesh): return False if not has_normal(mesh): return False if not name_is_alphanumeric(mesh): return False return name_is_unique(mesh) 

This is not a strict rule. Like everything listed so far, use it when it enhances readability.

Signs Your Function is Too Long

Functions should hardly ever be over 100 lines. Longer functions are harder to test, debug, and maintain since users need to keep track if updates at the start of the function affect areas and the end of the function. Here are some simple rules to determine if a function is too long:

Writing Clean Classes

Classes are like headings in a book, there should be multiple layers of abstraction going from high level ideas to more detailed lower level ideas: