Random python notes

This is more or less a dump of all the Stackoverflow questions that I hit until I know them by heart.

QT/ PySide generic stuff

http://www.sidefx.com/docs/houdini14.0/hom/cookbook/qt/
http://doc.qt.io/qt-4.8/widgets-and-layouts.html

Sensible class output

Until further notice, I'll try adding something like this to classes:

def __repr__(self):
    output = '\n{} class\n'.format(self.__class__.__name__)
    for k, v in self.__dict__.iteritems():
        output += '\t{}: {}\n'.format(k, v)
    return output
# as above, get class name as string
self.__class__.__name__

Where's my home?

from os.path import expanduser
home = expanduser("~")

Arguments with argparse

Argparse is cool to parse all sorts of arguments.
Basic usage:

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--wiggle", help="Perform wiggle.")
parser.add_argument("bodypart", help="Required. Valid values are Bum, Arm or Wiener")
# a flag
parser.add_argument('-w', action='store_true')
args = parser.parse_args()
print args.wiggle

Exclusive mandatory arguments with optional values (think of git push, git pull --rebase)

import argparse
# Parse the arguments //////////////////////////////////////////////////////////
parser = argparse.ArgumentParser(prog='SUBCOMMAND')
subparsers = parser.add_subparsers(dest='action')
parser_one = subparsers.add_parser('action_one')
parser_one.add_argument('one_argument', type=str)
parser_pull = subparsers.add_parser('pull')
args = parser.parse_args()

# Handle the arguments /////////////////////////////////////////////////////////
if args.action == 'action_one':
        print 'action one called with arg', args.one_argument
elif args.action == 'pull':
    print 'pull action called'

Display help by default:
before parsing args:

[...]
if len(sys.argv) == 1:
    parser.print_help()
    sys.exit(1)
args = parser.parse_args()
[...]

List comprehensions

words = ['I', 'like', 'my', 'Currywurst', 'with', 'fries', 'and', 'hot', 'sauce']
print [w.upper() for w in words]
>>> ['I', 'LIKE', 'MY', 'CURRYWURST', 'WITH', 'FRIES', 'AND', 'HOT', 'SAUCE']
print [[w.upper(), len(w)] for w in words]
>>> [['I', 1], ['LIKE', 4], ['MY', 2], ['CURRYWURST', 10], ['WITH', 4], ['FRIES', 5], ['AND', 3], ['HOT', 3], ['SAUCE', 5]]
# Also cool:
print [w.upper() for w in words if w.startswith('a')]
>>> ['AND']

Dict comprehensions

Almost as above...

mydict = {key: value for (key, value) in some_list}

Colorize output

def colorize(msg, color='green'):
    names = {
        'purple': '\033[95m',
        'blue': '\033[94m',
        'green': '\033[92m',
        'warning': '\033[93m',
        'error': '\033[91m',
        'ENDC': '\033[0m',
        'bold': '\033[1m',
        'underline': '\033[4m'
    }
    if color in names:
        return '{}{}{}'.format(names[color], msg, names['ENDC'])
    else:
        return msg

Usage:

a = 10
msg = 'Hello'
print '{} is {}'.format(colorize(msg), colorize(a, color='error'))

Enumerate is good

my_list = ['apple', 'banana', 'grapes', 'pear']

# yes:
for c, value in enumerate(my_list, 1):
    print(c, value)

# no:
i=1
for c in my_list:
    print c, i
    i += 1

Set class attribute by string

This should be easy, right? Sometimes you need to set an attribute based on a string value.
These are some ways to do it:

class ClassOne():
    def __init__(self):
        self.val = None
        self.__dict__['val'] = 'yo'
    
class ClassTwo():
    def __init__(self):
        self.val = None
        setattr(self, 'val', 'what up')
    

c1 = ClassOne()
c2 = ClassTwo()
print c1.val, c2.val
>>> yo what up  
c1.__dict__['val'] = 'hey'
setattr(c2, 'val', 'dude')
print c1.val, c2.val
>>> hey dude

Class validity iteration

Sometimes I like to set class attributes first in a class. This is how I test if they have been set successfully:

class Foo(object):  
    def __init__(self):
        self.foo = 'elephant'
        self.bar = None

    def is_valid(self):
        return all([a is not None for a in self.__dict__.values()])

f = Foo()
print f.is_valid()

Traversing folders and files recursively

optionally list certain types

relevant_filetypes = ['.txt', '.py', '.json']
for root, subdirs, files in os.walk('/tmp'):
    for f in files:
        if os.path.splitext(f)[1] in relevant_filetypes:
            print os.path.join(root, f)
# how big is a file?
import os
def size(filepath):
    return os.stat(filepath).st_size

with

with will allow you to operate on something in a controlled init and end condition (or lifecyle) that allows you to set a defined condition just for one operation. You don't need __init__() for this to work, but often you might want to pass an argument, too.

class Condition():
    def __init__(self, arg):
        self.arg = arg
    def __enter__(self):
        print 'doing sth with', self.arg
    def __exit__(self, type, value, traceback):
        print 'done'

with Condition('option'):
    #do some stuff
    pass

For example, you could retain the current selection:

class KeepSelection():
    def __enter__(self):
        self.selection = pm.selected()
    def __exit__(self, type, value, traceback):
        pm.select(self.selection)

with KeepSelection():
    pm.polyCube()

Invoking pm.polyCube() would normally cause you to lose the selection. Because we store the selection in the __enter__() function, we can later restore them in the __exit__() function which will trigger after the last operation inside the with statement. Super handy sometimes.

Misc stuff

# quick and proper formatting
print 'a is {0}, b is {1}'.format(a, b)


# call me dumb, did't know that.
test = float('inf')
# to store a minimum value
for i in sth:
    if i < test : test = i


# Whitespace collapsing:
_str = 'Hello    little   fella  !'
print _str.split()
# or for the whole thing
" ".join(_str.split())

# JSON: write to file
import json
results = [1, 2, 3]
with open('log.json', 'w') as f:
    json.dump(results, f, indent=4)


# JSON: load from file
with open('log.json') as logfile:
    log_data = json.load(logfile)
    print log_data


# get file extensiom
filename, file_extension = os.path.splitext('/path/to/somefile.ext')
#or..
ext = os.path.splitext('/path/to/somefile.ext')[1]

# join list elements into a space-separated string
list = ['ham', 'eggs', 'lazors']
' '.join(list)

# does the file exist?
import os.path
os.path.isfile(fname)

#is file executable?
os.access(fpath, os.X_OK)

# set executable bit
import os
import stat
st = os.stat('somefile')
os.chmod('somefile', st.st_mode | stat.S_IEXEC)

Simplistic threading

import time # Just for the sleeping part
from multiprocessing.dummy import Pool as ThreadPool
images=['1.img', '2.img', '3.img', '4.img', '5.img']
def myExpensiveFunction(arg):
    time.sleep(3)
    print arg
pool = ThreadPool(4) # use 4 threads
results = pool.map(myExpensiveFunction, images)

VirtualEnv and PySide (OSX)

Especially for pyside, virtualenv is a great way not to mess with system pyside and screwing up maya. It seems you can't just install pyside with pip here, so we need to build from source. Fortunately, this is quite straightforward. Afterwards you're rewarded with a tiny independent sanbox to do your pyside gui apps in. Thanks to Fredrik Averpil who did a lot of digging.

# this is only needed if you don't have these dependencies:
brew install python qt cmake

sudo pip install myenv
virtualenv myenv
source myenv/bin/activate
cd myenv
git clone --recursive https://github.com/PySide/pyside-setup.git pyside-setup
cd pyside-setup
python setup.py bdist_wheel --ignore-git
cd dist
pip install PySide*.whl

Package PySide app

With the above in effect, it is rather straightforward to package your (gui) app to a standalone executable.
YMMV, but I had to copy myenv/lib/python2.7/site-packages/PySide/lib* to myenv/lib. Try without that first.

# first, install pyinstaller into virtualenv.
pip install pyinstaller
# run that thang
bin/pyinstaller -y --hiddenimport json --osx-bundle-identifier com.my.app -w pythonfile.py
# -y : don't warn to overwrite existing files
# --hiddenimport: if you run into trouble because of missing imports list them here.
# --osx-bundle-identifier: On osx, this will be set for the app bundle
# -w on osx, triggers the build of an *.app. Win/OSX will not show a console window.

Cookbook

Resize images with PIL

#!/usr/bin/env python
import os
import sys
import argparse
from PIL import Image


class Resize(object):
    def __init__(self, path):
        self.maxsize = (1024, 1024)
        self.square = True
        self.path = path
        self.extensions = ['.tga', '.png', '.jpg']
        self.dryrun = False

    def collect_img(self):
        if os.path.isdir(self.path):
            for root, dirs, files in os.walk(self.path):
                for file_ in files:
                    for ext in self.extensions:
                        if ext.lower() in file_.lower():
                            yield os.path.join(root, file_)


    def run(self):
        for img_path in self.collect_img():
            # print img_path
            img = Image.open(img_path)
            img_basename = os.path.basename(img_path)
            if img.size[0] > self.maxsize[0] or img.size[1] > self.maxsize[1]:
                print '{} too large: {}px of {}px'.format(img_basename, img.size, self.maxsize)
                print 'Source info:', img.mode, img.size, img.format

                if self.dryrun:
                    continue
                try:
                    new_img = img.resize(self.maxsize)
                    # img.thumbnail((self.maxsize, self.maxsize))
                    new_img.save(img_path, img.format)
                    print 'Successfully wrote', img_path
                except Exception as e:
                    print 'Could not write', img_path, e







if __name__ == '__main__':
    parser = argparse.ArgumentParser(prog='resizer')
    parser.add_argument('folder', type=str)
    parser.add_argument('maxsize', type=int)
    args = parser.parse_args()

    # Handle the arguments ////////////////////
    resizer = Resize(args.folder)
    s = int(args.maxsize)
    size = (s, s)
    resizer.maxsize = size
    resizer.run()