The recent release of Nuke 13 brought along the update from Python 2 to Python 3. After all, Python 2 has been officially dead since January 2020, and Python 3 has been around since 2008, so that update was due.
Python 3 comes with quite a few improvements and a few changes that can easily break scripts written for Python 2.
There are plenty of resources online explaining how to port older code to Py3 (for example: https://docs.python.org/3/howto/pyporting.html, https://docs.python.org/3/library/2to3.html, https://www.pythonconverter.com/), but I figured I would do a little write-up of the most common errors I encounter as I upgrade my own tools to be Nuke 13 compatible.
The first time you try to open Nuke 13, you are likely to be greeted by this friendly message:
If you click OK, everything goes away, and you have no idea what is wrong.
When you open Nuke, a Terminal Window also opens, and this is where you’ll be looking for useful information:
In this case, this was mostly a bad line of code. For Windows path, it’s recommended to use \\ instead of \ as backslashes are a special character and are used to escape other characters. This could have been a disaster in Python 2 as well, but somehow it worked. After opening the file and doubling my slashes, I can now retry to open nuke:
Oh no! Another error! We really didn’t expect that to happen, did we?
We could now open that file, fix the error, launch Nuke, find the next error, open, fix, launch, find, etc… Depending on how many packages you have installed, it could take a while.
Using Pycharm for help
As often, when working with Python, a good IDE can help.
In Pycharm, you can enable code compatibility inspection:
This will now highlight code that is not compatible with both Python 2.7 and 3.7 (The version targeted by the VFX reference platform for 2020).
This is great while writing code, as it will highlight incompatible code right away. Converting old codebases could still be tedious, so let’s look at a more efficient option:
Let’s try to use the code inspection tool of PyCharm:
We want to inspect the whole project (well, I do, but you do you). I also want to focus on Python3 errors, none of the other warnings that may show up. We’ll make a new inspection profile (2) in the next step.
At the top, duplicate your current profile, rename it, then modify it as desired.
After you click OK, it brings you back to the Inspection Dialog, where you can hit OK to run the inspection.
The results will now show up, sometimes with tips on how to solve the issue.
I can’t list all errors possible, but I’ll list below the errors I run into as I fix my own projects. Feel free to add more in the comments or ask questions here or on the Nuke forum. As more errors get discovered, I’ll update this list.
By far, the most common error you will run into is about the print statement that was replaced by a print function. What does it mean?
# print statement, Python 2.7: print "hello world!" # print function, Python 3.x: print("hello world!")
It’s not a huge change, but print statements are so commonly used that this will possibly be annoying.
Worse, Python 2.7 does not by default support the print function, so if we were to replace print statements with print functions, it could change the code’s meaning on older versions of Nuke.
It turns out we don’t really care. Typing print(“hello world!”) would print a Tuple containing the string “Hello World!” and that’s totally fine by me.
Also, I find that many print statements aren’t really that useful and were likely added while coding as a way to debug and keep track of things, so the other solution is to remove the line entirely.
TLDR: Either add parentheses around the stuff to print or delete the whole line.
In Python 2, while range() is working, it was somewhat inefficient, and when I was learning Python a decade ago, xrange() was recommended, as it was more efficient. I have used xrange quite extensively.
In Python 3, they put the optimizations of xrange and put them directly in range, then removed xrange. The fix is to replace every use of xrange() by range(). It will still work in py2, albeit slightly less efficiently.
Python 2 has a few object types to deal with words: basestrings, strings, Unicode. It also stored binary data as strings. Python 3 threw away the differences and now has a single string class, plus a new binary class (bytes). Believe it or not, dealing with different types of strings, particularly mixed-use of Unicode and strings, was one thing that gave me the biggest headaches in Python 2, so this is a change I welcome with open arms.
I’ve seen all sorts of weird things done around these issues, so I don’t have a recipe on how to fix this. If migrating from 2 > 3 permanently, usually we can remove weird workarounds.
A common pattern I have noticed is:
def some_function(argument): """ This function can accept either a string or a list as argument """ if isinstance(argument, basestring): argument = [argument] for something in argument: do_it()
In Python 2, the example above would catch any subclass of basestring (str, unicode) and convert them to a list. (Note that it doesn’t check what is in the list if you pass a list already, but that’s another story). This would error in Py3, as there is no such thing as basestring. You could replace basestring with str, but in py2, this might result in unexpected behaviour if somehow a unicode is passed as the argument. I prefer to think of it slightly differently:
def some_function(argument): """ This function can accept either a string or a list as argument """ if not isinstance(argument, list): argument = [argument] for something in argument: do_it()
implicit relative imports
This will only impact those of you who have started writing multi-file python packages.
Consider the following imaginary package, which contains a generic module defining all possible body parts that a creature might have, and so far our only created creature: a unicorn.
Our unicorn imports may look something like this in python 2:
from body_parts import Head, Leg, Torso, Tail, Horn
This is a relative import, because our package is named creatures, and we import a submodule of creatures inside another submodule of creatures, by the submodule name only. It’s also implicit because nothing in the code indicates whether it is relative or absolute. Imagine we now had another package in our python path, itself called body_parts:
Suddenly, our import statement is ambiguous. Are we referring to body_parts the package or body_parts the sister module?
To avoid confusion, Python 3 doesn’t allow implicit relative imports and would fail in both cases. In the first case (without the body_parts package), it would error because it would be looking for a package named body_parts, and not find one. In the second case, it would find our other body_parts package, which happens to not have all these classes we’re looking for defined.
The solution would be to either do an absolute import (this is what I tend to do):
from creatures.body_parts import Head, Leg, Torso, Tail, Horn
or do an explicit relative import (you make it explicit by using the dot notation):
from .body_parts import Head, Leg, Torso, Tail, Horn
This one might not cause an error on load but could cause errors during execution.
In Python 2, dividing an integer by another integer results in an integer:
>>> # Python 2 >>> 5/2 2
In Python 3, dividing an integer by another integer results in a float:
>>> # Python 3 >>> 5/2 2.5
A new integer division operator was added:
>>> # Python 3 >>> 5//2 2
Sadly this is likely going to affect quite a few scripts that performed node placement, as it was a fairly common pattern to divide the size of the node by 2 to get its center, and setXYpos methods error if you give them float values. This is what is breaking nukescripts/psd.py, lines 81 and 135 for example. Hope the foundry fixes that one.
your error here!
if you run into an error, feel free to post it in the comments and I’ll be adding them in here as we go