Python Recipes

From Tech Artists Wiki

Jump to: navigation, search

Contents

Introduction

This is a collection of Python code snippets and example scripts, targeted at typical Tech Art tasks. Please contribute!

Windows

Examples related to Windows. Most of these require the Win32 Extensions.

Get Special Windows Folders

Here's how to get various special folders under Windows (User's Desktop, Startup, Common Application Folders, etc.)

# Get current user's Desktop folder path
from win32com.shell import shell, shellcon
folderPath = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, None, 0)

For other special folders, replace "CSIDL_DESKTOP" above with the appropriate constant defined in shellcon. The full list includes:

CSIDL_ADMINTOOLS
CSIDL_ALTSTARTUP
CSIDL_APPDATA
CSIDL_BITBUCKET
CSIDL_CDBURN_AREA
CSIDL_COMMON_ADMINTOOLS
CSIDL_COMMON_ALTSTARTUP
CSIDL_COMMON_APPDATA
CSIDL_COMMON_DESKTOPDIRECTORY
CSIDL_COMMON_DOCUMENTS
CSIDL_COMMON_FAVORITES
CSIDL_COMMON_MUSIC
CSIDL_COMMON_OEM_LINKS
CSIDL_COMMON_PICTURES
CSIDL_COMMON_PROGRAMS
CSIDL_COMMON_STARTMENU
CSIDL_COMMON_STARTUP
CSIDL_COMMON_TEMPLATES
CSIDL_COMMON_VIDEO
CSIDL_COMPUTERSNEARME
CSIDL_CONNECTIONS
CSIDL_CONTROLS
CSIDL_COOKIES
CSIDL_DESKTOP
CSIDL_DESKTOPDIRECTORY
CSIDL_DRIVES
CSIDL_FAVORITES
CSIDL_FONTS
CSIDL_HISTORY
CSIDL_INTERNET
CSIDL_INTERNET_CACHE
CSIDL_LOCAL_APPDATA
CSIDL_MYDOCUMENTS
CSIDL_MYMUSIC
CSIDL_MYPICTURES
CSIDL_MYVIDEO
CSIDL_NETHOOD
CSIDL_NETWORK
CSIDL_PERSONAL
CSIDL_PRINTERS
CSIDL_PRINTHOOD
CSIDL_PROFILE
CSIDL_PROGRAMS
CSIDL_PROGRAM_FILES
CSIDL_PROGRAM_FILESX86
CSIDL_PROGRAM_FILES_COMMON
CSIDL_PROGRAM_FILES_COMMONX86
CSIDL_RECENT
CSIDL_RESOURCES
CSIDL_RESOURCES_LOCALIZED
CSIDL_SENDTO
CSIDL_STARTMENU
CSIDL_STARTUP
CSIDL_SYSTEM
CSIDL_SYSTEMX86
CSIDL_TEMPLATES
CSIDL_WINDOWS

Get User's Profile folder

The os module contains a method for expanding a path string to match the current user's profile folder:

>>> import os
>>> os.path.expanduser(r'~\myStuff')
'C:\\Users\\joe.himdickel\\myStuff'

The tilde '~' character is where the expanded user profile path is inserted. That method is equivalent to using CSIDL_PROFILE in the Get Special Windows Folder method explained above.

Get/Set Read-Only File Attribute

Originally posted on Tech Art Tiki. You can do this a couple ways. Using the standard libarary:

import os, stat
myFile = r'C:\stuff\grail.txt'

fileAtt = os.stat(myFile).st_mode
if not fileAtt & stat.S_IWRITE:
   # File is read-only, so make it writeable
   os.chmod(myFile, stat.S_IWRITE)
else:
   # File is writeable, so make it read-only
   os.chmod(myFile, stat.S_IREAD)

Or using the Win32 Extensions:

import win32api, win32con
myFile = r'C:\stuff\grail.txt'

fileAtt = win32api.GetFileAttributes(myFile)
if fileAtt & win32con.FILE_ATTRIBUTE_READONLY:
   # File is read-only, so make it writeable
   win32api.SetFileAttributes(myFile, ~win32con.FILE_ATTRIBUTE_READONLY)
else:
   # File is writeable, so make it read-only
   win32api.SetFileAttributes(myFile, win32con.FILE_ATTRIBUTE_READONLY)

Watch a Directory for Changes

A class and working example illustrating how to watch a directory (and optionally subdirs under it) for file changes. Runs in a separate thread, allowing you to check for changes when it's convenient in your script.

import types
import threading
import win32file
import win32con
import os

class WatchDirectory(object):
   """
   This method was adapted from example by Tim Golden:
   http://tgolden.sc.sabren.com/python/win32_how_do_i/watch_directory_for_changes.html

   Watches for changes to any file/dir inside specified directory.
   Instantiating this class spawns the watcher function in a separate thread.
   self.changeList accumulates changes until client requests list using the check() method.
   Calling that method returns the changeList then empties it.

   The watching thread will self-terminate when the WatchDirectory instance is destroyed
   or falls out-of-scope.

   Usage Examples:
      watcher = WatchDirectory(r'D:\myStuff')
      # Typically you'd have a main loop here somewhere, inside of which you'd make occasional
      # calls to watcher.check, like so:
      while not doneWithLoop:
         changes = watcher.check()
         for c in changes:
            # do something to each modified file, etc...
            print "file: %s, change: %s" % c

   See self.ACTIONS below for a list of possible change strings.

   Also, the "onlyExtensions" keyword arg can be used to only watch certain filetypes:
      watcher = WatchDirectory(r'D:\myStuff', onlyExtensions=('.jpg', '.xml') 

   NOTES:
   ChangeList returned is chronological, oldest to newest.  A file that's modified early in the list
   could show up as deleted later.  Your client code is responsible for verifying files still
   exist, etc.

   The win32 method used here seems very reliable.  I did notice that it fails to report
   deleted files if they were inside a subdir that was deleted in its entirety, however.
   Seems like a border case, however, so I'm inclined to leave it as-is for now.
   Also, deleted subdirs still sometimes show up as a change with watchSubdirs=False.

   TODO:
   - Maybe add option to not multithread, making it a blocking call.
 
   Adam Pletcher
   adam@volition-inc.com
   Volition, Inc./THQ
   """
   def __init__(self, dirToWatch, bufferSize=1024, watchSubdirs=True, onlyExtensions=None):
      self.dirToWatch = dirToWatch
      self.changeList = []
      self.bufferSize = bufferSize
      self.watchSubdirs = watchSubdirs
      if type(onlyExtensions) == types.StringType:
         onlyExtensions = onlyExtensions.split(',')
      self.onlyExtensions = onlyExtensions
      # Constants for making win32file stuff readable
      self.ACTIONS = {
         1 : "Created",
         2 : "Deleted",
         3 : "Modified",
         4 : 'Renamed to something',
         5 : 'Renamed from "%s"'
      }
      # Start watching in a separate thread
      self.thread = threading.Thread(target=self._checkInThread).start()

   def _checkInThread(self):
      """
      Watch for changes in specified directory.  Designed to be run in a
      separate thread, since ReadDirectoryChangesW is a blocking call.
      Don't call this directly.
      """
      while True:
         FILE_LIST_DIRECTORY = 0x0001
         hDir = win32file.CreateFile (
            self.dirToWatch,
            FILE_LIST_DIRECTORY,
            win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
            None,
            win32con.OPEN_EXISTING,
            win32con.FILE_FLAG_BACKUP_SEMANTICS,
            None
         )
         # The following is a blocking call.  Which is why we run this in its own thread.
         newChanges = win32file.ReadDirectoryChangesW (
            hDir,                # Previous handle to directory
            self.bufferSize,     # Buffer to hold results
            self.watchSubdirs,   # Watch subdirs?
            win32con.FILE_NOTIFY_CHANGE_FILE_NAME |   # What to watch for
            win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
            win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
            win32con.FILE_NOTIFY_CHANGE_SIZE |
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
            win32con.FILE_NOTIFY_CHANGE_SECURITY,
            None,
            None
         )
         # Changes found, process them
         finalChanges = []
         oldFilename = None
         for change in newChanges:
            if change[0] == 4:   # renamed to something
               oldFilename = os.path.split(change[1])[1]
            else:
               file = os.path.join(self.dirToWatch, change[1])
               skip = False
               # Verify a few things first
               if not self.watchSubdirs and os.path.isdir(file):
                  skip = True
               elif self.onlyExtensions and os.path.splitext(file)[1] not in self.onlyExtensions:
                  skip = True
               if not skip:  # passed checks, so use it
                  action = self.ACTIONS.get (change[0], "Unknown")
                  if change[0] == 5: # renamed from something
                     # Insert old filename, prior to being renamed
                     action = action % (oldFilename)
                     oldFilename = None
                  # Add change tuple to list
                  finalChanges.append((file, action))
         # Add processed changes to running list
         self.changeList += finalChanges

   def check(self):
      """
      Fetches list of changes our watcher thread has accumulated.
      """
      changes = self.changeList
      self.changeList = []  # clear changeList
      return changes
 
### MAIN ###
if (__name__ == '__main__'):
   import time
 
   watcher = WatchDirectory(r'D:\temp\watchTest')
   while True:
      changes = watcher.check()
 
      for c in changes:
         print "file: %s, change: %s" % c
 
      time.sleep(5)  # wait 5 seconds before checking again

Primitive Types

Classes implementing basic data types.

Colour

This code is not production tested and doesn't yet implement all the operator methods (pow etc.) you might need.

Please use it and PM me if you find any bugs/improvements.

class Colour(object):
    def __init__(self, r=0.0, g=0.0, b=0.0, a=None, type="float"):
        self.type = type
        self.r = r
        self.g = g
        self.b = b
        if type == "float":
            self.valrange = 1.0
        elif type == "byte":
            self.valrange = 255
        else:
            raise AttributeError, "Unknown type for colour class creation"
        self.a = a or 1*self.valrange

##########################
## Overloaded built-ins ##
##########################
    def __str__(self):
        if self.type == "float":
            return "Colour(%f, %f, %f, %f, type = %s)" % (self.r, self.g, self.b, self.a, self.type)
        else:
            return "Colour(%d, %d, %d, %d, type = %s)" % (self.r, self.g, self.b, self.a, self.type)
    
    def __eq__(self, other):
        if not isinstance(other, Colour):
            return False
        return self.r == other.r and self.g == other.g and self.b == other.b and self.a == other.a
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __nonzero__(self):
        if self.r or self.g or self.b:
            return True
        return False
    
    def __lt__(self, other):
        if not isinstance(other, Colour):
            raise TypeError, "Invalid types for comparison"
        return self.luminosity() < other.luminosity()
    
    def __le__(self, other):
        if not isinstance(other, Colour):
            raise TypeError, "Invalid types for comparison"
        return (self.luminosity() < other.luminosity()) or self.__eq__(other)

    def __gt__(self, other):
        if not isinstance(other, Colour):
            raise TypeError, "Invalid types for comparison"
        return self.luminosity() > other.luminosity()
        
    def __ge__(self, other):
        if not isinstance(other, Colour):
            raise TypeError, "Invalid types for comparison"
        return (self.luminosity() > other.luminosity()) or self.__eq__(other)
    
    def __abs__(self):
        newCol = Colour(type=self.type)
        newCol.r = abs(self.r)
        newCol.g = abs(self.g)
        newCol.b = abs(self.b)
        newCol.a = abs(self.a)
        return newCol
        
    def __add__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        newCol = Colour(type=self.type)
        newCol.r = self.r + other.r
        newCol.g = self.g + other.g
        newCol.b = self.b + other.b
        newCol.a = self.a + other.a
        return newCol
    
    def __iadd__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        self.r += other.r
        self.g += other.g
        self.b += other.b
        self.a += other.a
        return self
    
    def __sub__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        newCol = Colour(type=self.type)
        newCol.r = self.r - other.r
        newCol.g = self.g - other.g
        newCol.b = self.b - other.b
        newCol.a = self.a - other.a
        return newCol
    
    def __isub__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        self.r -= other.r
        self.g -= other.g
        self.b -= other.b
        self.a -= other.a
        return self
    
    def __mul__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        newCol = Colour(type=self.type)
        newCol.r = self.r * other.r
        newCol.g = self.g * other.g
        newCol.b = self.b * other.b
        newCol.a = self.a * other.a
        return newCol
    
    def __imul__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        self.r *= other.r
        self.g *= other.g
        self.b *= other.b
        self.a *= other.a
        return self
    
    def __div__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        newCol = Colour(type=self.type)
        newCol.r = self.r / other.r
        newCol.g = self.g / other.g
        newCol.b = self.b / other.b
        newCol.a = self.a / other.a
        return newCol
    
    def __idiv__(self, other):
        if not isinstance(other, Colour):
            other = Colour(r=other, g=other, b=other, a=other, type=self.type)
        elif self.type != other.type:
                raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        self.r /= other.r
        self.g /= other.g
        self.b /= other.b
        self.a /= other.a
        return self
    
###########
## Casts ##
###########

    def toHTML(self):
        tempCol = self.toByte()
        return "#%02x%02x%02x" % (tempCol.r, tempCol.g, tempCol.b)
    
    def toTuple(self):
        return (self.r, self.g, self.b, self.a)
    
    def toList(self):
        return [self.r, self.g, self.b, self.a]
    
    def toFloat(self):
        newCol = Colour()
        newCol.r = self.r
        newCol.g = self.g
        newCol.b = self.b
        newCol.a = self.a
        if self.type == "byte":
            newCol /= 255.0
        newCol.type = "float"
        newCol.valrange = 1.0
        return newCol
    
    def toByte(self):
        newCol = Colour()
        newCol.r = self.r
        newCol.g = self.g
        newCol.b = self.b
        newCol.a = self.a
        if self.type == "float":
            newCol *= 255
        newCol.r = int(newCol.r)
        newCol.g = int(newCol.g)
        newCol.b = int(newCol.b)
        newCol.a = int(newCol.a)
        newCol.type = "byte"
        newCol.valrange = 255
        return newCol

#######################
## Utility functions ##
#######################
    
    def _sat_(self, n):
        if n < 0:
            n = 0
        elif n > self.valrange:
            n = self.valrange
        return n
    
    def saturate(self, affectAlpha=False):
        newCol = Colour(a = self.a, type=self.type)
        newCol.r = self._sat_(self.r)
        newCol.g = self._sat_(self.g)
        newCol.b = self._sat_(self.b)
        if affectAlpha:
            newCol.a = self._sat_(self.a)
        return newCol
    
    def invert(self, affectAlpha=False):
        return self.__invert__(affectAlpha)
    
    def __invert__(self, affectAlpha=False):
        newCol = Colour(a=self.a, type=self.type)
        newCol.r = self.valrange - self.r
        newCol.g = self.valrange - self.g
        newCol.b = self.valrange - self.b
        if affectAlpha:
            newCol = newCol.invertAlpha()
        return newCol
    
    def invertAlpha(self):
        newCol = Colour(self.r, self.g, self.b, self.a, self.type)
        newCol.a = self.valrange - self.a
        return newCol
    
    def luminosity(self):
        temp = self.toList()
        return (max(temp[:-1]) + min(temp[:-1])) / 2.0
    
    def cheapLuminosity(self):
        lumCoeff = Colour(r=0.299,g=0.587,b=0.114)
        if self.type == "byte":
            lumCoeff = lumCoeff.toByte()
        lumCoeff *= self
        lumCoeff = lumCoeff.toFloat()
        return lumCoeff.r + lumCoeff.g + lumCoeff.b
    
    def lerp(self, other, factor):
        if self.type != other.type:
            raise ValueError, "Mismatch in operand types \"float\" and \"byte\""
        lerped = (other - self) * factor + self
        return lerped

Usage

>>> a = Colour()
Colour(0.000000, 0.000000, 0.000000, 1.000000, type = float)
>>> b = Colour(1.0, 1.0, 0.5, 1.0)
Colour(1.000000, 1.000000, 0.500000, 1.000000, type = float)
>>> c = Colour(r=1.0, g=1.0, b=1.0, a=1.0, type="float")
Colour(1.000000, 1.000000, 1.000000, 1.000000, type = float)
>>> d = Colour(r=127, type="byte")
Colour(127, 0, 0, 255, type = byte)
>>> a + b
Colour(1.000000, 1.000000, 0.500000, 2.000000, type = float)
>>> b / 2
Colour(0.500000, 0.500000, 0.250000, 0.500000, type = float)
>>> a == b
False
>>> b < c
True
>>> a.toByte()
Colour(0, 0, 0, 255, type = byte)
>>> d.toFloat()
Colour(0.498039, 0.000000, 0.000000, 1.000000, type = float)
>>> a.lerp(b, 0.4)
Colour(0.400000, 0.400000, 0.200000, 1.000000, type = float)
>>> (b/3).toHTML()
#55552a
>>> d.toList()
[127, 0.0, 0.0, 255]
>>> d.toTuple()
(127, 0.0, 0.0, 255)
>>> d.invert()
Colour(128, 255, 255, 255, type = byte)
>>> d.invert(affectAlpha=True)
Colour(128, 255, 255, 0, type = byte)
Personal tools