Decision Tree Entropy

These are some basic notes on how entropy is used when making decision trees. The examples are taken from Process Mining: Data Science In Action.

Entropy

The equation for entropy is \(E = - \sum\limits_{i=1}^n p_i \log_2 p_i\), where \(p_i\) is the probability of variable \(i\). In other words, for variable \(i\), \(p_i\) is the count of instances for that variable divided by the total number of instances for all variables. Well, I'm probably not saying this clearly enough. A concrete example might be better.

Dying Young

This example uses a data set that contains various attributes that might predict if someone died 'young' (less than 70) or 'not young' (70 or older). There are 860 entries with 546 dying young and 314 dying old. We can calculate the entropy for the root node using the proportions of \(young\) (died young) and \(\lnot young\) (didn't die young).

\[ E = -(E_{young} + E_{\lnot young})\\ = -(\frac{546}{860} \log_2 \frac{546}{860} + \frac{314}{860} \log_2 \frac{314}{860})\\ \approx 0.9468 \]

Ubuntu 16.04 and the Brother HL-2140 Printer

Introduction

I (relatively) recently bought a ZaReason Limbo Desktop PC which comes with Ubuntu 16.04. I'm debating whether to update it to 16.10, given that updating broke the USB WiFi adapter I had on my older computer (the USB is recognized but not the WiFi adapter), but, in any case, I wanted to print something using my new computer today and ran into the same problem that I had when I updated my old computer to Ubuntu 14.10 - the default printer driver printed out a bunch of blank pages rather than the page I wanted to print. The last time I solved it by choosing a different model (the HL-2142) but this time out I found a stack overflow post that suggested using the Brother HL-2140 Foomatic/hl1250 [en] driver instead. I tried it and it worked for me, so here goes.

How To Choose the Correct Printer Driver

First, open Dash (the windows-key launches it on my keyboard), type in Printers and click on the icon.

dash.png

This brings up the printers dialog.

printers.png

Double-click on the HL-2140-series icon to bring up the Printer Properties dialog.

printer_properties.png

Click on the Make and Model "Change" button (the fourth row). Let the program find the drivers and it will bring up the Choose Driver dialog.

choose_driver.png

Use the Brother (recommended) option (it should already be selected) and click on the "Forward" button. This brings up the Change Driver dialog with the postscript driver chosen (which is the one that does not work for me).

change_driver.png

Select the hl1250 driver (by clicking on it) and click on the "Forward" button.

select_driver.png

This will bring up the Existing Settings dialog. I've tried different options and haven't noticed a difference so I just leave it at whatever the default option is and click "Apply".

existing_settings.png

At this point your driver should be updated so click on the "OK" button.

click_okay.png

If your experience is the same as mine, you will be able to print your documents at this point.

Conclusion

The basic method to get the Brother HL-2140 to print in Ubuntu 16.04 is to select the hl1250 driver instead of the default. Since I only do this when I need to set up a new computer, I alway have to do some googling to remember what to do, so hopefully this will help me save some searching if I ever have to do this again.

Seek With Cocos2D

This is an implementation of Craig Reynold's flocking behavior based on the book Python Game Programming By Example. Reynolds mentions many variations for flocking. This will implement a basic boid that flocks to the mouse pointer.

Imports

# third party
from cocos.cocosnode import CocosNode
from cocos.euclid import Vector2
import cocos
import cocos.particle_systems as particle_system

Constants

CHASE = 1
RUN_AWAY = -1
CHANGE_BEHAVIOR = -1

The Boid

Boid Settings

class BoidSettings(object):
    """a settings object"""
    def __init__(self):
	self.position = None
	self.velocity = None
	self.speed = None
	self.max_force = None
	self.slowing_radius = None
	self._attributes = None
	return

    @property
    def attributes(self):
	"""list of required attributes"""
	if self._attributes is None:
	    self._attributes = ("position",
				"velocity",
				"speed",
				"slowing_radius",
				"max_force")
	return self._attributes

    def x_y(self, position):
	"""sets the initial x, y coordinates
	Parameters
	----------

	position: tuple
	  (x, y) coordinates
	"""
	self.position = position
	self.check_types(position, "position", (list, tuple))
	for item in position:
	    self.check_numeric(item, "position")
	return self

    def velocity_vector(self, velocity):
	"""initial velocity

	Parameters
	----------

	velocity: Vector2
	  2-d vector representing boid's velocity
	"""
	self.velocity = velocity
	self.check_type(velocity, "velocity", Vector2)
	return self

    def speed_scalar(self, speed):
	"""scalar for the velocity vector

	Parameters
	----------

	speed: number
	  pixels per frame to scale velocity
	"""
	self.speed = speed
	self.check_numeric(speed, "speed")
	return self

    def slow_down_distance(self, slowing_radius):
	"""denominator to create ratio to slow down

	Used to reduce steering-force (distance/radius)

	Parameters
	----------

	slowing_radius: numeric
	  steering-force reduction factor
	"""
	self.slowing_radius = slowing_radius
	self.check_numeric(slowing_radius, "slowing radius")
	return self

    def maximum_force(self, max_force):
	"""sets the max force magnitude

	Parameters
	----------
	max_force: numeric
	  upper-bound for the steering force
	"""
	self.max_force = max_force
	self.check_numeric(max_force, "max force")
	return self

    def maximum_velocity(self, max_velocity):
	"""sets the max-velocity

	Parameters
	----------

	max_velocity: numeric
	  upper-bound for magnitude of velocity
	"""
	self.max_velocity = max_velocity
	self.check_numeric(max_velocity, "max velocity")
	return self

    def check_numeric(self, value, identifier):
	"""checks value is numeric

	Parameters
	----------

	value: object
	  item to check

	identifier: string
	  name for error message

	Raises
	------

	TypeError if value is not int or float
	"""
	self.check_types(value, identifier, (int, float))
	return

    def check_types(self, value, identifier, expected):
	"""checks type of value
	Parameters
	----------

	value: object
	  the thing to check
	identifier: string
	  id for error message
	expected: collection
	  types to check if value is one of them

	Raises
	------

	TypeError if type of value not in expected
	"""
	if type(value) not in expected:
	    raise TypeError("{0} must be one of {1}, not {2}".format(
		identifier,
		expected,
		value))
	return

    def check_type(self, value, identifier, expected):
	"""checks type of the value
	Parameters
	----------

	value: object
	  thing to check
	identifier: string
	  id for error messages
	expected: type
	  what the value should be

	Raises
	------
	TypeError if type of value is not expected
	"""
	if not isinstance(value, expected):
	    raise TypeError("{0} must be {1} not {2}".format(identifier,
							     expected,
							     value))
	return

    def __call__(self):
	"""checks all the attributes are set

	Raises
	------

	TypeError if an attribute is None
	"""
	for attribute in self.attributes:
	    if getattr(self, attribute) is None:
		raise TypeError("{0} must be set, not None".format(attribute))
	return self

Boid Node

the Constructor

class Boid(CocosNode):
    """represents a boid
    Parameters
    ----------

    settings: BoidSettings
      settings for this node
    """
    def __init__(self, settings):
	super(Boid, self).__init__()
	self.settings = settings
	self.position = settings.position
	self.velocity = Vector2(0, 0)
	self.speed = settings.speed
	self.slowing_radius = settings.slowing_radius
	self.max_force = settings.max_force
	self.max_velocity = settings.max_velocity
	self.target = None

I'm not a fan of method calls in the constructor, but these next two lines help set up the node.

self.add(particle_system.Sun())
self.schedule(self.update)
return

The add method sets the Sun instance as a child of the Boid node and the schedule method sets the Boid's update method to be called once per frame.

The Current Position

This is just a convenience attribute. It probably takes a performance hit, but the original code was a little obscure so I thought I'd pull it out to document it.

@property
def current_position(self):
    """this node's position

    Returns
    -------

    Vector2: the current of this node
    """
    return Vector2(self.x, self.y)

I had to look it up, since there's no setting of self.x or self.y here - these two attributes are built into the CocosNode object and are always the current values.

The Update Method

def update(self, delta):
    """updates the current position

    Parameters
    ----------

    delta: float
      seconds since the last clock tick
    """
    if self.target is None:
	return

The target is going to be set when the mouse is move. Because of this it's initially not set, so we need to short circuit if that's the case.

distance = self.target - self.current_position
ramp_down = min(distance.magnitude()/self.slowing_radius, 1)
steering_force = distance * self.speed * ramp_down - self.velocity
steering_force = self.limit(steering_force, self.max_force)
self.velocity = self.limit(self.velocity + steering_force,
			   self.max_velocity)
self.position += self.velocity * delta
return

In the snippet above, distance is a vector with the tail on our current position and the head on the target. The steering-force is created by scaling the distance by our speed and then subtracting our current velocity, creating a vector that over-compensates to turn us toward the target. The new velocity is our old velocity plus the steering-force and our position is updated to be our new velocity times the elapsed time. That doesn't look like it's doing anything, but position is another special attribute on the CocosNode.

This next method reduces a vector if its magnitude is above a given threshold.

def limit(self, vector, upper_bound):
    """limits magnitude of vector

    Re-scales all values in the vector if the magnitude exceeds limit

    Parameters
    ----------

    vector:
      vector to check

    upper_bound: number
      upper limit for magnitude of vector

    Returns
    -------

    vector whose magnitude is no greater than upper_bound
    """
    try:
	magnitude = vector.magnitude()
    except OverflowError:
	print(vector)
	raise
    return (vector if magnitude <= upper_bound
	    else vector*(upper_bound/magnitude))

Main Layer

This is the class to act as the event handler.

class MainLayer(cocos.layer.Layer):
    """sets up the interaction

    Parameters
    ----------

    boids: collection
      boids to maintain
    """
    is_event_handler = True

That value (is_event_handler) has to be true or the Layer class doesn't handle events.

def __init__(self, boids):
    super(MainLayer, self).__init__()
    self.boids = boids
    for boid in boids:
	self.add(boid)
    return

def on_mouse_motion(self, x, y, dx, dy):
    """sets the boids' targets

    Parameters
    ----------

    x, y:
      current mouse cursor position

    dx, dy:
      change in position since the last report
    """
    for boid in self.boids:
	boid.target = Vector2(x, y)
    return

def on_mouse_press(self, x, y, button, modifiers):
    """handles mouse-clicks"""
    for boid in self.boids:
	boid.speed *= CHANGE_BEHAVIOR
    return

The on_mouse_motion method is a pass through to pyglet so the method is documented there more than on the cocos2d site.

if __name__ == "__main__":
    boid_settings = (BoidSettings()
		     .x_y((300, 200))
		     .velocity_vector(Vector2())
		     .speed_scalar(10)
		     .slow_down_distance(200)
		     .maximum_force(5)
		     .maximum_velocity(2000)())
    cocos.director.director.init(caption="Seeker")
    boid = Boid(boid_settings)
    scene = cocos.scene.Scene(MainLayer([boid]))
    cocos.director.director.run(scene)

The first thing to note is that the cocos.director.init function has to be called before any of the other cocos2D objects are created. If you move the instantiation of the Boid above that line, for instance, it will crash with a AttributeError: 'Director' object has no attribute '_window_virtual_width' error.

Flocking With Cocos2D

This is an implementation of Craig Reynold's flocking behavior based on the book Python Game Programming By Example.

Imports

# third party
from cocos.cocosnode import CocosNode
import cocos
import cocos.euclid as euclid
import cocos.particle_system as particle_system

Constants

The Boid

Boid Settings

class BoidSettings(object):
    """a settings object"""
    def __init__(self):
	self.position = None
	self.velocity = None
	self.speed = None
	self.max_force = None
	self._attributes = None
	return

    @property
    def attributes(self):
	"""list of required attributes"""
	if self._attributes is None:
	    self._attributes = ("position",
				"velocity",
				"speed",
				"max_force")
	return self._attributes

    def x_y(self, position):
	"""sets the initial x, y coordinates
	Parameters
	----------

	position: tuple
	  (x, y) coordinates
	"""
	self.position = position
	self.check_types(position, "position", (list, tuple))
	for item in position:
	    self.check_numeric(item, "position")
	return

    def velocity_vector(self, velocity):
	"""initial velocity

	Parameters
	----------

	velocity: Vector2
	  2-d vector representing boid's velocity
	"""
	self.velocity = velocity
	self.check_type(velocity, "velocity", Vector2)
	return

    def pixels_per_frame(self, speed):
	"""initial speed of the boid

	Parameters
	----------

	speed: number
	  number of pixels to move per frame
	"""
	self.speed = speed
	self.check_numeric(speed, "speed")
	return

    def maximum_force(self, max_force):
	"""sets the max force magnitude

	Parameters
	----------
	max_force: numeric
	  upper-bound for the steering force
	"""
	self.max_force = max_force
	self.check_numeric(max_force)
	return

    def maximum_velocity(self, max_velocity):
	"""sets the max-velocity

	Parameters
	----------

	max_velocity: numeric
	  upper-bound for magnitude of velocity
	"""
	self.max_velocity = max_velocity
	self.check_numeric(max_velocity)
	return

    def check_numeric(self, value, identifier):
	"""checks value is numeric

	Parameters
	----------

	value: object
	  item to check

	identifier: string
	  name for error message

	Raises
	------

	TypeError if value is not int or float
	"""
	self.check_types(value, identifier, (int, float))
	return

    def check_types(self, value, identifier, expected):
	"""checks type of value
	Parameters
	----------

	value: object
	  the thing to check
	identifier: string
	  id for error message
	expected: collection 
	  types to check if value is one of them

	Raises
	------

	TypeError if type of value not in expected
	"""
	if type(value) not in expected:
	    raise TypeError("{0} must be one of {1}, not {2}".format(
		identifier,
		expected,
		value))
	return

    def check_type(self, value, identifier, expected):
	"""checks type of the value
	Parameters
	----------

	value: object
	  thing to check
	identifier: string
	  id for error messages
	expected: type
	  what the value should be

	Raises
	------
	TypeError if type of value is not expected
	"""
	if not isinstance(value, expected):
	    raise TypeError("{0} must be {1} not {2}".format(identifier,
							     expected,
							     value))
	return

Boid Node

class Boid(CocosNode):
    """represents a boid
    Parameters
    ----------

    settings: BoidSettings
      settings for this node
    """
    def __init__(self):
	super(Boid, self).__init__()
	self.settings = settings
	self.position = settings.position
	self.velocity = euclid.Vector2(0, 0)
	self.speed = settings.speed
	self.max_force = settings.max_force
	self.max_velocity = settings.max_velocity
	self.target = None

I'm not a fan of method calls in the constructor, but these next two lines help set up the node.

self.add(particle_system.Sun())
self.schedule(self.update)
return

The add method sets the Sun instance as a child of the Boid node and the schedule method sets the Boid's update method to be called once per frame.

def update(self, delta):
    """updates the current position

    Parameters
    ----------

    delta: float
      seconds since the last clock tick
    """
    if self.target is None:
	return

The target

distance = self.target - euclid.Vector2(self.x, self.y)
steering_force = distance * self.speed - self.velocity
steering_force = truncate(self.velocity + steering, self.max_velocity)
self.position += self.velocity * delta
return

Main

Breakout With Tkinter

A tkinter breakout implementation from the book Python Game Programming by Example. Although Tkinter is referred to primarily as a GUI builder, this example shows you how to use it to re-create the arcade game Breakout using only the python standard library.

Pre-Installation

Although the tkinter python library is part of the standard python installation, it relies on Tcl/Tk and a c-python binary that you have to build or install. To get it in ubuntu you can use apt-get.

  sudo apt-get python3-tk

Imports

# python standard library
from abc import ABCMeta
from abc import abstractproperty
from collections import namedtuple
from copy import copy
import re
import string
import tkinter

Constants

BLUE = "#0000ff"
LAVENDER = "#aaaaff"
WHITE = "#ffffff"
NUMERIC = (int, float)

REVERSE_DIRECTION = -1
LEFT = -1
RIGHT = LEFT * REVERSE_DIRECTION
UP = -1
EDGE_OF_SCREEN = 0
NO_MOVEMENT = 0

Base Classes

Coordinates

This is a named tuple to see if I can keep the coordinates returned by tkinter straight.

Coordinates = namedtuple("Coordinates", ["top_left_x",
					 "top_left_y",
					 "bottom_right_x",
					 "bottom_right_y"])

Base Widget

A base-class to implement the methods common to all the game objects. The python 3 syntax seems to have changed slightly so you have to use metaclass=ABCMeta in place of object instead of putting __metaclass__==ABCMeta the way I used to in python 2.7.

The item property

class BaseWidget(metaclass=ABCMeta):
    """base class for game object's

    Parameters
    ----------

    canvas: tkinter.Canvas
      canvas to draw on
    """
    @abstractproperty
    def item(self):
	"""the canvas item"""
	return

The item will be an identifier pointing to a tkinter widget. It gets returned by the function call to create the widget (e.g. canvas.create_oval).

Position

@property
def position(self):
    """the coordinates of the object on the canvas

    Returns
    -------

    tuple: x0, y0, x1, y1
    """
    x0, y0, x1, y1 = self.canvas.coords(self.item)
    return Coordinates(top_left_x=x0,
		       top_left_y=y0,
		       bottom_right_x=x1,
		       bottom_right_y=y1)

The call to canvas.coords returns the coordinates of the bounding-box for the widget. The first two values are the x and y coordinates for the upper-left corner of the box and the last two values are the x and y coordinates of the bottom-right corner.

Move

def move(self, horizontal_offset, vertical_offset):
    """move this object to the coordinates

    Parameters
    ----------

    horizontal_offset: int
      x-axis pixels to move
    vertical_offset: int
      y-axis pixels to move
    """
    self.canvas.move(self.item, horizontal_offset, vertical_offset)
    return

This moves the object. Rather than giving it coordinates, the move method moves the object vertically and horizontally the number of pixels you pass in for each axis. You could think of the values as being added to each of the coordinates (so negative numbers will move the object in the opposite direction from positive numbers).

Delete

def delete(self):
    """destroy this canvas item"""
    self.canvas.delete(self.item)
    return

This deletes the object from the canvas. If you attempt to use it after this tkinter will raise an exception.

Base Settings

A base class to hold settings. It (partly) implements the fluent interface design pattern. I'm not one-hundred percent convinced that it's a good idea in python, but I wanted to add value checking and document the values I expect a little better.

class BaseSettings(metaclass=ABCMeta):
    """fluent interface"""
    _hex_pattern = None

    @abstractproperty
    def attributes(self):
	"""list of attribute names
	This is used by the call to check the attributes
	"""
	return

The attributes will be a list (or tuple) of strings that match the variable-names in the settings objects that are required (e.g. to require that self.table has a value other than None, put "table" in the list). This gets used by the __call__ method (defined below).

@property
def hex_pattern(self):
    """compiled regex to match hex-colors"""
    if BaseSettings._hex_pattern is None:
	hex_character = "\da-fA-f"
	base = "(?P<{{0}}[{0}])(?P={{0}})".format(hex_character)
	BaseSettings._hex_pattern = re.compile("#" +
					       base.format("r") +
					       base.format("g") +
					       base.format("b"))
    return BaseSettings._hex_pattern

The hex_pattern will match any string that starts with a pound sign (#) followed by three sets of hex-digit pairs. A hex-digit is an integer from 0 to 9 or one of the letters from a to f (case insensitive). Since it requires pairs, each hex-digit has to be repeated twice. So it will match #aabbff but not #abf or #0123ab. It's used by the next method.

def check_hex_color(self, value, identifier):
    """checks the color is a valid hex-code
    Parameters
    ----------
    value: string
      color-code to check
    identifier: string
      error-message identifier

    Raises
    ------
    TypeError: if string is malformed
    """
    if not re.match("#" + "[{0}]".format(string.hexdigits) * 6, value):
	raise TypeError("{0} must be a 6-digit hex string, not {1}".format(
	    identifier, value))

    return

The colors set in this code are based on RGB hex-strings. The check_hex_color method validates that they are look something like "#aabbcc".

def assert_positive_number(self, value, identifier):
    """checks the value

    Parameters
    ----------

    value: int or float
      value to check
    identifier: string
      something for the error message

    Raises
    ------
    TypeError if value is not a positive number
    """
    self.check_numeric(value, identifier)
    self.check_positive(value, identifier)
    return

assert_positive_number checks that the value passed in is a number greater than 0.

def check_positive(self, value, identifier):
    """check that value is greater than zero

    Parameters
    ----------

    value: numeric
      value to check

    identifier: str
      description for error messages

    Raises
    ------

    TypeError: if value is <= 0
    """
    if not value > 0:
	raise TypeError("{0} must be greater than 0 not {1}".format(
	    identifier,
	    value))
    return

The check_positive method raises a TypeError if the value passed in isn't greater than zero. It doesn't check that the value is numeric so if it isn't it will still raise a TypeError but the error message won't be as helpful. I did it this way because I (at least originally) assumed some values had to be integers so I wanted to leave the check for type as a separate operation.

def check_type(self, thing, identifier, expected):
    """checks that the value is the correct type
    Parameters
    ----------

    thing:
      object to check
    identifier: string
      message to identify the thing
    expected: object
      what the thing is expected to be

    Raises
    ------

    TypeError: if thing isn't as expected
    """
    if not isinstance(thing, expected):
	raise TypeError("Expected {0} to be {1} not {2}".format(identifier,
								expected,
								thing))
    return

The check_type was the original method I created. I'm not sure it's as useful as checking ranges of values (which I'm not doing enough of yet), but it's at least useful to check if my expectations of what's being passed in to the methods is correct.

def check_types(self, thing, identifier, expected):
    """check that thing is one of multiple types

    Parameters
    ----------

    thing: object
      thing to check
    identifier: string
      identifier for error message
    expected: collection
      types that thing might be

    Raises
    ------

    TypeError if type of thing not in expected
    """
    if not type(thing) in expected:
	raise TypeError("{0} should be one of {1}, not {2}".format(
	    identifier,
	    expected,
	    thing))
    return

check_types allows you you specify a collection of possible types for the value.

def check_numeric(self, thing, identifier):
    """check if thing is int or float

    Parameters
    ----------

    thing: object
      thing to check if is numeric
    identifier: string
      identifier for error message

    Raises
    ------
    TypeError if thing is not numeric
    """
    self.check_types(thing, identifier, NUMERIC)
    return

check_numeric will raise a TypeError if the value isn't an integer or float.

def __call__(self):
    """checks that everything was set
    Raises
    ------

    TypeError:
      if any attributes weren't set

    Returns
    -------

    GameSettings: this object
    """
    for attribute in self.attributes:
	if getattr(self, attribute) is None:
	    raise TypeError("{0} attribute not set".format(attribute))
    return self

The __call__ is meant to be the final method called when the parameters are set. It checks that all the properties in the attributes list have been set to something other than None and raises a TypeError if any of them hasn't been set.

The Ball Class

Ball Directions

This is an object to use instead of the list like they use in the book. The first value (x-direction) is set positive to make it move from left to right, and negative to move right to left. The second value is set positive to move the ball downwards and negative to move it upwards.

class BallDirections(object):
    """holds the current direction of the ball
    Parameters
    ----------

    horizontal: int
      positive to move left to right, negative otherwise
    vertical: int
      positive to move down, negative to move up
    """
    def __init__(self, horizontal, vertical):
	self.horizontal = horizontal
	self.vertical = vertical
	return

Ball Settings

class BallSettings(BaseSettings):
    """settings for the ball"""
    def __init__(self):
	self.x = None
	self.y = None
	self.radius = None
	self.direction = None
	self.speed = None
	self.fill = None
	self._attributes = None
	return

    @property
    def attributes(self):
	"""required attributes"""
	if self._attributes is None:
	    self._attributes = ("x",
				"y",
				"radius",
				"direction",
				"speed",
				"fill")
	return self._attributes
def x_position(self, x):
    """initial horizontal position

    Parameters
    ----------
    x: int or float
      pixels from the left of the canvas to start the ball
    """
    self.x = x
    self.assert_positive_number(x, "x")
    return self

def y_position(self, y):
    """initial vertical position

    Parameters
    ----------

    y: int
      pixels from the top of the canvas
    """
    self.y = y
    self.assert_positive_number(y, "y")
    return self

The x and y values for the ball are actually set in the code based on the initial location of the paddle, so requiring them here is a bad idea. Oh, well.

def circle_radius(self, radius):
    """radius of the ball
    Parameters
    ----------
    radius: int
      pixel width and height for the circle
    """
    self.radius = radius
    self.assert_positive_number(radius, "radius")
    return self

Ovals in tkinter are set by specifying the corners of their bounding boxes, the same as with creating a rectangle. So the radius is used as an offset to calculate where the corners should be. For example, if you have the center x-value for the oval, subtracting the radius gives you the top-left x-value and adding the radius gives you the bottom-right x-value. See the Game.ball property to get an idea of how it's used.

def direction_vector(self, direction):
    """2-d vector for direction"""
    self.direction = direction
    self.check_type(direction, "ball direction", BallDirections)
    return self

The direction values determine what direction the object is moving on the vertical and horizontal axes. Positive values move to the right and down, while negative values move to the left and up.

Horizontal Vertical Direction
Positive Positive Down-Right
Positive Negative Up-Right
Negative Positive Down-Left
Negative Negative Up-Left
def velocity(self, speed):
    """speed of the ball
    Parameters
    ----------

    speed: number
      pixels per move
    """
    self.speed = speed
    self.assert_positive_number(speed, "speed")
    return self

def color(self, fill):
    """fill color

    Parameters
    ----------
    fill: str
      hex-color to fill in the ball
    """
    self.fill = fill
    self.check_hex_color(fill, "fill")
    return self

The speed and fill are the number of pixels to move the ball each time and the fill is the color to put inside it.

The Ball

The ball-widget holds the reference to the ball that the player uses to smash bricks to try and break-out.

class BallWidget(BaseWidget):
    """representation of the ball

    Parameters
    ----------

    canvas: tkinter.Canvas
      what to create the ball from

    settings: BallSettings
      initial ball settings
    """
    def __init__(self, canvas, settings):
	self.canvas = canvas
	self.settings = settings
	self._item = None
	self.direction = self.settings.direction
	self.speed = self.settings.speed
	return

    @property
    def item(self):
	"""canvas item representing the ball"""
	if self._item is None:
	    x, y = self.settings.x, self.settings.y
	    radius = self.settings.radius
	    self._item = self.canvas.create_oval(
		x-radius, y-radius,
		x+radius, y+radius,
		fill=self.settings.fill,
	    )
	return self._item
def update(self):
    """moves the ball
    if the ball hits something, reverses direction
    """
    ball = self.position
    width = self.canvas.winfo_width()
    if ball.top_left_x <= EDGE_OF_SCREEN or ball.bottom_right_x >= width:
	self.direction.horizontal *= REVERSE_DIRECTION
    if ball.top_left_y <= EDGE_OF_SCREEN:
	self.direction.vertical *= REVERSE_DIRECTION
    self.move(self.direction.horizontal * self.speed,
	      self.direction.vertical * self.speed)

The update method gets its current position and if it is off-screen on either side it inverts the horizontal direction. If the ball is above the top of the screen it reverses its vertical direction. It doesn't check the bottom of the screen because going off the bottom is how the player loses so it's an expected behavior. Once it has the directions set it moves the ball by the amount defined by the speed variable.

def collide(self, others):
    """handles collisions

    Parameters
    ----------

    others: list
      collection of ther objects that the ball collided with
    """
    if len(others) > 1:
	self.direction.vertical *= REVERSE_DIRECTION
    elif len(others) == 1:
	ball = self.position
	x = (ball.top_left_x + ball.bottom_right_x)/2

	other = others[0].position
	if x > other.bottom_right_x:
	    self.direction.horizontal = RIGHT
	elif x < other.top_left_x:
	    self.direction.horizontal = LEFT
	else:
	    self.direction.vertical *= REVERSE_DIRECTION
    for other in others:
	if isinstance(other, BrickWidget):
	    other.hit()
    return

The collide method handles when a ball collides with another object. If it collided with more than one object it always reverses directions (this would only happen with bricks, not the paddle). If it collided with a single object then if the object is to the left of it (the ball's mean x-value is greater than the rightmost x-value for the object) then it sets its horizontal direction to move to the right (it bounces off it to the right). If the other object is to the right of the ball then the ball moves to the left. Otherwise the ball hit the object on top or below it so it changes vertical direction. If any of the objects are bricks then their hit methods are called.

The Paddle

A representation of the player's paddle.

The Paddle Settings

class PaddleSettings(BaseSettings):
    """settings for the player's paddle"""
    def __init__(self):
	self._attributes = None
	self.width = None
	self.height = None
	self.speed = None
	self.x = None
	self.y = None
	self.fill = None
	return

    @property
    def attributes(self):
	"""list of required settings"""
	if self._attributes is None:
	    self._attributes = ("width",
				"height",
				"speed",
				"x",
				"y",
				"fill")
	return self._attributes
def pixel_width(self, width):
    """width of the paddle
    Parameters
    ----------

    width: int
      pixel-width for the paddle
    """
    self.width = width
    self.check_type(width, "width", int)
    self.check_positive(width, "width")
    return self

def pixel_height(self, height):
    """height of the paddle

    Parameters
    ----------

    height: int
      pixel-height of the paddle
    """
    self.height = height
    self.check_type(height, "height", int)
    self.check_positive(height, "height")
    return self

The height and width are offsets to add to the upper-left corner coordinates of the bounding box to locate the lower-right corner of the bounding box, thus defining the size of the paddle.

def velocity(self, speed):
    """rate at which to move the paddle
    Parameters
    ----------

    speed: number
      amount to move paddle with each key stroke
    """
    self.speed = speed
    self.assert_positive_number(speed, "paddle speed")
    return self

The speed of the paddle is the amount it will move every-time an arrow key is hit. I think it's in pixels, but the units aren't clear.

def x_position(self, x):
    """initial x-position
    Parameters
    ----------

    x: int
      pixels from the left of the canvas
    """
    self.x = x
    self.check_numeric(x, "x")
    self.check_positive(x, "x")
    return self

def y_position(self, y):
    """initial y-position

    Parameters
    ----------

    y: int
       pixels from the top of the canvas
    """
    self.y = y
    self.check_numeric(y, 'y')
    self.check_positive(y, "y")
    return self

The x and y settings determine where the paddle will be at the start (and since it only moves horizontally the y value is where it will be vertically throughout the game).

def color(self, fill):
    """fill color for the rectangle

    Parameters
    ----------

    fill: str
      hex-code for the fill color
    """
    self.fill = fill
    self.check_hex_color(fill, "fill")
    return self

Like with the Ball the fill value for the Paddle decides what color to fill it with.

The Paddle Class

class PaddleWidget(BaseWidget):
    """the player's paddle
    Parameters
    ----------

    canvas: tkinter.Canvas
      the canvas to draw on
    settings: PaddleSettings
      initial settings for the paddle
    """
    def __init__(self, canvas, settings):
	self.canvas = canvas
	self.settings = settings
	self._item = None
	self.ball = None
	return

    @property
    def item(self):
	"""the canvas item for the paddle"""
	if self._item is None:
	    half_width = self.settings.width/2
	    half_height = self.settings.height/2
	    x, y = self.settings.x, self.settings.y
	    self._item = self.canvas.create_rectangle(
		x - half_width, y - half_height,
		x + half_width, y + half_height,
		fill=self.settings.fill
	    )
	return self._item

The item property creates a rectangle of width x height dimensions centered around (x, y).

def move(self, offset):
    """moves the paddle
    if has a ball, also moves the ball

    if already flush left or flush right, does nothing

    Parameters
    ----------

    offset: int
      amount to move the paddle and ball horizontally
    """
    coordinates = self.position
    width = self.canvas.winfo_width()
    if (coordinates.top_left_x + offset >= 0 and
	coordinates.bottom_right_x + offset <= width):  # noqa: E129
	super(PaddleWidget, self).move(offset, 0)
	if self.ball is not None:
	    self.ball.move(offset, 0)
    return

The move method moves the paddle horizontally by some offset. If moving it would place it offscreen to the left or right then it doesn't do anything. The paddle should only have the ball before the game starts (so that if the player moves the paddle the ball will stay with it until the game starts).

The Brick

Brick Settings

class BrickSettings(BaseSettings):
    """settings for the brick widget"""
    def __init__(self):
	self.x = None
	self.y = None
	self.width = None
	self.height = None
	self.colors = None
	self.tags = None
	self._attributes = None
	self._hits = None
	return

    @property
    def attributes(self):
	"""list of required values"""
	if self._attributes is None:
	    self._attributes = ("width",
				"height",
				"colors",
				"tags",
				"x",
				"y",
				"hits")
	return self._attributes
@property
def hits(self):
    """the number of hits each brick will take
    """
    if self._hits is None:
	self._hits = max(self.colors)
    return self._hits

def maximum_hits(self, hits):
    """number of hits brick will take
    Parameters
    ----------

    hits: int
      number of hits before deleting bricks
    """
    self._hits = hits
    self.check_type(hits, "hits", int)
    self.check_positive(hits, "hits")
    return self

The hits value is the number of times a brick gets hit by a ball before it deletes itself. I originally make it always use the largest value but then found out different rows use different values so I added a setter method.

def x_position(self, x):
    """horizontal position
    Parameters
    ----------
    x: int or float
      pixels from the left
    """
    self.x = x
    self.check_numeric(x, "x")
    self.check_positive(x, "x")
    return self

def y_position(self, y):
    """vertical position
    Parameters
    ----------

    y: int or float
      pixels from the top
    """
    self.y = y
    self.check_numeric(y, "y")
    self.check_positive(y, "y")
    return self

The x and y are the center-positions for a brick. Since they don't move this is their permanent position. Like the ball, this actually gets calculated when the game is set up so making this required was probably a bad ide.

def pixel_width(self, width):
    """width of the brick
    Parameters
    ----------

    width: int
      pixel-width of the brick
    """
    self.width = width
    self.check_type(width, "width", int)
    self.check_positive(width, "width")
    return self

def pixel_height(self, height):
    """height of the brick
    Parameters
    ----------

    height: int
      pixel-height of the brick
    """
    self.height = height
    self.check_type(height, "height", int)
    self.check_positive(height, "height")
    return self

The height and width give the dimensions of the brick.

def level_colors(self, colors):
    """map of level to colors

    Parameters
    ----------
    colors: dict
      map of integers to colors
    """
    self.colors = colors
    for level in range(1, len(colors) + 1):
	if level not in colors:
	    raise TypeError("colors keys must be range starting at 1")
    for level, color in colors.items():
	self.check_hex_color(color, "level {0} color".format(level))
    return self

As a brick gets hit it changes colors. The colors dictionary is a mapping between the number of remaining times the brick can be hit before being deleted (the level of the brick) and the color for that level.

def label(self, tags):
    """string to tag bricks

    Parameters
    ----------

    tags: str
      identifier for bricks
    """
    self.tags = tags
    self.check_type(tags, "tags", str)
    return self

The tags attribute is a string given to tkinter to identify a class of related widgets.

Brick Widget

class BrickWidget(BaseWidget):
    """represents a single brick

    Parameters
    ----------

    canvas: tkinter.Canvas
      what to draw the brick on
    settings: BrickSettings
      initial settings for the brick
    """
    def __init__(self, canvas, settings):
	self.canvas = canvas
	self.settings = settings
	self.hits = self.settings.hits
	self._item = None
	return

    @property
    def item(self):
	"""canvas rectangle"""
	if self._item is None:
	    half_height = self.settings.height/2
	    half_width = self.settings.width/2
	    x, y = self.settings.x, self.settings.y
	    self._item = self.canvas.create_rectangle(
		x - half_width, y - half_height,
		x + half_width, y + half_height,
		fill=self.settings.colors[self.hits],
		tags=self.settings.tags
	    )
	return self._item

The item creation is almost the same as the one for the Paddle except that the color is based on the number of remaining hits it starts with and it gets a tag

def hit(self):
    """the brick has been hit event
    Decrements the counter and changes the color or deletes the brick
    """
    self.hits -= 1
    if self.hits == 0:
	self.delete()
    else:
	self.canvas.itemconfig(self.item,
			       fill=self.settings.colors[self.hits])
    return

The hit method decrements the number of hits the brick has remaining and deletes it if it doesn't have any left. If it does have hits left it re-colors the brick to match the number of hits remaining.

The Frame

The Frame Settings

These are settings for the Tkinter Frame.

class FrameSettings(BaseSettings):
    """holds the settings for the game"""
    def __init__(self):
	self.width = None
	self.height = None
	self.color = None
	self.title = None
	self._attributes = None
	return

    @property
    def attributes(self):
	"""list of required attributes"""
	if self._attributes is None:
	    self._attributes = ("width",
				"height",
				"color",
				"title")
	return self._attributes

    def window_width(self, width):
	"""width of window
	Parameters
	----------
	width: int
	  pixel-width for the tkinter window

	Returns
	-------

	GameSettings: this object
	"""
	self.width = width
	self.check_type(width, "width", int)
	self.check_positive(width, "width")
	return self

    def window_height(self, height):
	"""height of window
	Parameters
	----------

	height: int
	  pixel height of the window

	Returns
	-------

	GameSettings: this object
	"""
	self.height = height
	self.check_type(height, "height", int)
	self.check_positive(height, "height")
	return self

    def canvas_color(self, color):
	"""background color

	Parameters
	----------

	color: string
	   hex-color for canvas background

	Returns
	-------

	GameSettings: this object
	"""
	self.color = color
	self.check_hex_color(color, 'background color')
	return self

    def window_title(self, title):
	"""title of the window
	Parameters
	----------

	title: str
	  name to give the title

	Returns
	-------

	GameSettings: this object
	"""
	self.title = title
	self.check_type(title, "window title", str)
	return self

The Frame Class

The Tk class creates the main window. Within it the Frame class creates a container which you pass the main window on instantiation. Within the frame a Canvas is placed to actually draw things. The pack method tells the children to display their widgets on their parents.

class BreakoutFrame(tkinter.Frame):
    """creates the breakout game

    Parameters
    ----------

    settings: GameSettings
      object with the settings
    parent: Tk
      parent window for this frame
    """
    def __init__(self, settings, parent):
	super(BreakoutFrame, self).__init__(parent)
	self.parent = parent
	self.parent.title(settings.title)
	self.settings = settings
	self._canvas = None
	self.height = settings.height
	return

    @property
    def canvas(self):
	"""canvas to render images"""
	if self._canvas is None:
	    self._canvas = tkinter.Canvas(self,
					  width=self.settings.width,
					  height=self.settings.height,
					  bg=self.settings.color)
	return self._canvas

    def __call__(self):
	"""runs the main-loop"""
	self.canvas.pack()
	self.pack()
	self.parent.mainloop()
	return

The Game

Game Settings

class GameSettings(BaseSettings):
    """settings for the game"""
    def __init__(self):
	self.lives = None
	self.text_x = None
	self.text_y = None
	self.text_size = None
	self.padding = None
	self._attributes = None
	return

    @property
    def attributes(self):
	"""required attributes"""
	if self._attributes is None:
	    self._attributes = ("lives",
				"text_x",
				"text_y",
				"padding")
	return self._attributes

    def font_size(self, size):
	"""text size in pixels
	Parameters
	----------

	size: int
	  size for fonts
	"""
	self.text_size = size
	self.check_type(size, "text size", int)
	self.check_positive(size, "text size")
	return self

    def allowed_failures(self, lives):
	"""number of times player can fail

	Parameters
	----------

	lives: int
	  number of failures per game
	"""
	self.lives = lives
	self.check_type(lives, "lives", int)
	self.check_positive(lives, "lives")
	return self

    def text_horizontal_position(self, text_x):
	"""pixel indent for text

	Parameters
	----------

	text_x: int
	  number of pixels from the left
	"""
	self.text_x = text_x
	self.check_type(text_x, "text indent", int)
	self.check_positive(text_x, "text indent")
	return self

    def text_vertical_position(self, text_y):
	"""pixel vertical position for text
	Parameters
	----------

	text_y: int
	   pixels from the top
	"""
	self.text_y = text_y
	self.check_type(text_y, "text y", int)
	self.check_positive(text_y, "text y")
	return self

    def outer_padding(self, padding):
	"""outer margins

	Parameters
	----------

	padding: int
	  pixels to put around the edge of the canvas
	"""
	self.padding = padding
	self.check_type(padding, "padding", int)
	self.check_positive(padding, "padding")
	return self

Game Class

class Game(object):
    """builds and holds the game

    Parameters
    ----------

    game_settings: GameSettings
      settings for the game overall

    frame_settings: FrameSettings
      settings to set-up the tkinter window

    paddle_settings: PaddleSettings
       settings to set-up the paddle_settings

    brick_settings: BrickSettings
       settings to set-up the bricks

    ball_settings: BallSettings
      settings to set-up the ball
    """
    def __init__(self, game_settings, frame_settings, paddle_settings,
		 brick_settings, ball_settings):
	self.game_settings = game_settings
	self.frame_settings = frame_settings
	self.paddle_settings = paddle_settings
	self.brick_settings = brick_settings
	self.ball_settings = ball_settings
	self.collidable = {}
	self.hud = None
	self._frame = None
	self._canvas = None
	self._paddle = None
	self._bricks = None
	self._ball = None
	return

    @property
    def frame(self):
	"""the tkinter frame"""
	if self._frame is None:
	    self._frame = BreakoutFrame(self.frame_settings, tkinter.Tk())
	return self._frame

    @property
    def canvas(self):
	"""tkinter canvas to draw on"""
	if self._canvas is None:
	    self._canvas = self.frame.canvas
	return self._canvas

    @property
    def paddle(self):
	"""the paddle widget"""
	if self._paddle is None:
	    (self.paddle_settings.x_position(self.frame_settings.width/2)
	     .y_position(self.frame_settings.height -
			 self.game_settings.padding -
			 self.paddle_settings.height))
	    self._paddle = PaddleWidget(self.canvas, self.paddle_settings)
	return self._paddle

The Paddle is created centered horizontally and at the height specified in the settings.

@property
def ball(self):
    """the ball widget"""
    if self._ball is None:
	paddle = self.paddle.position
	(self.ball_settings
	 .x_position((paddle.top_left_x + paddle.bottom_right_x)/2)
	 .y_position(paddle.top_left_y - 2 * self.ball_settings.radius)
	 .direction_vector(BallDirections(RIGHT, UP)))
	self._ball = BallWidget(self.canvas, self.ball_settings)
    return self._ball

The ball is created sitting on top of the paddle in its horizontal center (the mean of its x-coordinates). Even though I'm forcing the user to set the x and y values they actually get overwritten here. I also had to make sure that the ball is created above the paddle, which is why I'm adding twice the radius to the y-position, otherwise it would register as a collision and end up going down instead of up.

def add_brick(self, x, y, settings):
    """add a brick to items

    Parameters
    ----------

    x: int
      pixels from the left
    y: int
      pixels from the top
    """
    settings = (copy(settings)
		.x_position(x)
		.y_position(y))
    brick = BrickWidget(self.canvas, settings)
    self.collidable[brick.item] = brick
    return

def add_bricks(self):
    """adds the bricks"""
    half_width = self.brick_settings.width/2
    first_row = 50
    second_row = first_row + self.brick_settings.height
    third_row = second_row + self.brick_settings.height
    first_settings = copy(self.brick_settings).maximum_hits(3)
    second_settings = copy(self.brick_settings).maximum_hits(2)
    third_settings = copy(self.brick_settings).maximum_hits(1)
    for x in range(5, self.frame_settings.width - 5, 75):
	this_x = x + half_width
	self.add_brick(this_x, first_row, first_settings)
	self.add_brick(this_x, second_row, second_settings)
	self.add_brick(this_x, third_row, third_settings)
    return

The add_bricks method creates three rows of bricks with a 5-pixel left margin and a 50 pixel top margin. The top-row of bricks takes three hits each, the second two hits each and the bricks in the bottom row will be deleted after one hit.

def setup_canvas(self):
    """sets up some canvas settings"""
    self.canvas.focus_set()
    self.canvas.bind(
	"<Left>",
	lambda _: self.paddle.move(-self.paddle_settings.speed)
    )
    self.canvas.bind(
	"<Right>",
	lambda _: self.paddle.move(self.paddle_settings.speed)
    )
    self.canvas.bind("<space>", lambda _: self.start())
    return

The setup_canvas causes the canvas to steal focus and then sets up the keys the user uses to control the paddle and start the game.

def draw_text(self, x, y, text):
    """draws the text

    Parameters
    ----------

    x: int
      left indent
    y: int
      right indent
    text: string
      what to output

    Returns
    -------
    text-object
    """
    font = ("Helvetica", self.game_settings.text_size)
    return self.canvas.create_text(x,
				   y,
				   text=text, font=font)

def update_lives_text(self):
    """updates the text when a player fails"""
    text = "Lives: {0}".format(self.lives)
    if self.hud is None:
	self.hud = self.draw_text(self.game_settings.text_x,
				  self.game_settings.text_y,
				  text)
    else:
	self.canvas.itemconfig(self.hud, text=text)
    return

def reset(self):
    """sets up the game after it's ended"""
    self.lives = self.game_settings.lives
    self.add_bricks()
    self.setup_canvas()
    self.ball.delete()
    self._ball = None
    self.paddle.ball = self.ball
    self.update_lives_text()
    return

def set_up(self):
    """populates the collidable items dict"""
    self.lives = self.game_settings.lives
    self.collidable[self.paddle.item] = self.paddle
    self.add_bricks()
    self.setup_canvas()
    self.ball.delete()
    self._ball = None
    self.paddle.ball = self.ball
    self.update_lives_text()
    self.text = self.draw_text(300, 200, "Press Space to Start")
    return

def set_up_in_between(self):
    """sets things up when the player still has lives"""
    self.ball.delete()
    self._ball = None
    self.paddle.ball = self.ball
    self.update_lives_text()
    self.setup_canvas()
    self.text = self.draw_text(300, 200, "Press Space to Start")
    return

def start(self):
    """starts the game"""
    self.canvas.unbind("<space>")
    self.canvas.delete(self.text)
    self.paddle.ball = None
    self.game_loop()
    return

The start method un-binds the spacebar from the start method so the game won't restart if the player accidentally hits the spacebar. It also deletes the message to hit the spacebar to start the game, removes the ball from the paddle and starts the game-loop

def game_loop(self):
    """runs the game"""
    self.check_collisions()
    num_bricks = len(self.canvas.find_withtag("brick"))
    if num_bricks == 0:
	self.ball.speed = None
	self.text = self.draw_text(300, 200, "You Win. Whatever. (Hit the spacebar to restart)")
	self.reset()
    elif self.ball.position.bottom_right_y >= self.frame.height:
	self.ball.speed = None
	self.lives -= 1
	if self.lives < 0:
	    self.text = self.draw_text(300, 200, "Loser (Hit the spacebar to restart)")
	    self.reset()
	else:
	    self.frame.after(1000, self.set_up_in_between)
    else:
	self.ball.update()
	self.frame.after(50, self.game_loop)
    return

Besides checking for collisions, the game_loop method check's if the bricks have all been removed (in which case the player has won) or if the ball has fallen off the screen. If the ball has fallen off the screen and the player is out of lives then it ends the game, otherwise it decrements the players remaining lives. If there are still bricks and the ball is on the screen then it calls the ball's update method to move it.

The frame.after method sets a timer that will call the callback function you pass in after the delay (in milliseconds) that you pass in has expired.

def check_collisions(self):
    """checks if the ball has collided with anything"""
    ball = self.ball.position
    items = self.canvas.find_overlapping(*ball)
    collisions = [self.collidable[item] for item in items
		  if item in self.collidable]
    self.ball.collide(collisions)
    return

check_collisions finds all the items that we added whose coordinates overlap with those of the ball then passes those items to the BallWidget.collide method to process. The overlapping widgets are filtered so that they only contain items of interest (not text-widgets, for instance).

def __call__(self):
    """sets up the game"""
    self.set_up()
    self.frame()
    return

The Main Loop

if __name__ == '__main__':
    frame_settings = (FrameSettings()
		      .window_width(600)
		      .window_height(400)
		      .canvas_color(WHITE)
		      .window_title("Breakout! Not Pong!")())
    ball_settings = (BallSettings()
		     .x_position(10)
		     .y_position(10)
		     .circle_radius(10)
		     .direction_vector(BallDirections(horizontal=NO_MOVEMENT,
						      vertical=UP))
		     .velocity(10)
		     .color(LAVENDER)())
    paddle_settings = (PaddleSettings()
		       .pixel_width(80)
		       .pixel_height(5)
		       .x_position(40)
		       .y_position(80)
		       .velocity(10)
		       .color(BLUE)
		       ())

    brick_settings = (BrickSettings()
		      .x_position(75)
		      .y_position(20)
		      .label("brick")
		      .pixel_width(75)
		      .pixel_height(20)
		      .level_colors({1: "#999999",
				     2: "#555555",
				     3: "#222222"})())

    game_settings = (GameSettings()
		     .allowed_failures(3)
		     .text_horizontal_position(50)
		     .text_vertical_position(20)
		     .font_size(15)
		     .outer_padding(20)()
		     )
    game = Game(game_settings, frame_settings, paddle_settings, brick_settings,
		ball_settings)
    game()

Inserting Text Between Methods in Org-Mode

This is an attempt to get org-mode to insert text between methods in a class definition using the noweb-ref property. I think you can also do it by just tangling everything to the same file, but I like the noweb notation better.

The Tangle

To keep it simple I'm just going to include a section for the class definition and a main section to check out the results. Note that to be able to break up the class definition I had to turn off org-mode's clean-up so all the python blocks have to be indented exactly the way they will be in the final python file.

<<first-class>>

<<main>>

The First Class

This first class won't do much, but if I understand the documentation I should be able to insert text between the parts of it and still have it work when the python gets tangled out. The org-mode source block starts like this - #+BEGIN_SRC python :noweb-ref first-class.

The Constructor

   class FirstClass(object):
       """a class that is first
       Parameters
       ----------

       name: string
	 some kind of identifier
       """
       def __init__(self, name):
	   self.name = name
	   return

The Call

The call emits the name. To concatenate this method to the previous block I'm using the same org-mode source-block header as I did with the constructor (#+BEGIN_SRC python :noweb-ref first-class). You could use a different name and insert another reference in the tangle but this seems more logical to me. One problem here is that python-mode (or some other mode) will interpret the methods as stand-alone functions and move them flush left. To fix this I had to turn off the automatic indentation using (setq org-src-preserve-indentation t). This means that you have to keep track of the indentation yourself, regardless of where the code sits in the org-mode document (so as you create sub-sections it will look uglier and uglier in the original document, at least to me). Putting the method in a separate block also requires that you to insert an empty line before the method to keep it from being stuck to the bottom of the previous one, but it doesn't include it when it gets exported to HTML, so you can't see it in this post. In fact, I noticed afterwards that the HTML export also stripped out the indentation on the left so you can't really see what I'm talking about.

   def __call__(self):
       """prints the name"""
       print(self.name)
       return

The Main

This part constructs the FirstClass object and calls it.

  if __name__ == "__main__":
      thing = FirstClass("Bob")
      thing()

Trying it out

  python noweb_ref.py
Bob

Summary

The point of trying this out is that I want to be able to break up and document class-methods better. Unfortunately it requires you to do a little more fiddling with the white-space yourself and makes the source org-mode file a little harder to read (I think) but I'm going to try it for a little while and see if the resulting documents are worth the extra headache. Maybe I'll just have to stick with docstrings for documenting the separate methods…

This bit at the end makes it so the indentation is always preserved, even if the init.el file isn't set up that way.

  # Local Variables:
  # org-src-preserve-indentation: t
  # End:

Org-Babel Stderr Results

If you execute a code-block in org-mode the default behavior when the executed code sends output to stderr is to open a pop-up buffer rather than including it in the org-babel file. Sometimes, though, you might want to document actual output from stderr but, as it turns out, this is how org-mode is designed so you are mostly stuck with it. There is a simple work-around though, just put an entry in the source-block that outputs to stdout at the end of the block and everyhing will show up in the results.

This is how you could include the output when there are failing tests run by py.test:

#+BEGIN_SRC sh :results verbatim
  py.test tests/
  echo
#+END_SRC

This will run all the tests in the tests folder and include the output in the org-mode file even if there are failures, thanks to the echo at the end.

I got this from Stack Overflow, of course.

Install Chrome Extensions in Vivaldi

This was taken from the vivaldi forums. It explains how to install the LastPass extension into the vivaldi browser and should work for other plugins as well (although I haven't tried yet).

The Procedure

  1. Install the Chrome extension source viewer (CRX Viewer) in Chrome
  2. Go to the Chrome Web Store and search for the LastPass extension
  3. Right click on the LastPass search result to bring up the context menu
  4. In the context menu selecte View extension source
  5. In the page that opens, click on the Download link in the upper corner
  6. Go to the Downloads folder and make a new folder (e.g. mkdir lastpass)
  7. Move the zip-file that you downloaded into the folder (mine was called hdokiejnpimakedhajhdlcegeplioahd.zip so I assume the names are random)
  8. Unzip the download
  9. Re-name _metadata to metadata (the post suggests deleting it, so that might work too)
  10. In Vivaldi, open the link vivaldi://chrome/extensions
  11. Click the Developer mode checkbox
  12. Click on Load unpacked extension
  13. Select the folder you unpacked the zip file into (lastpass in this case)
  14. Configure Last Pass

Conclusion

This gives the procedure for installing extensions like LastPass into the Vivaldi browser. In the course of writing this up I found out that you don't need to do this for all the extensions (just the ones with leading underscores in some of their folder names, I think). For other extensions just install them from the store - they seem to work out of the box.

Org-Babel PDF Export

These are my notes on exporting a PDF from an org-babel file.

First Try

The key sequence to export a PDF from an org file is C-c C-e l p (c as in… c, e as in export, l as in LaTex, and p as in PDF). My first try produced this error:

! LaTeX Error: File `ulem.sty` not found.

The actual output had `ulem.sty` ending with an apostrophe (which I can't show here because org either renders the apostrophe as an accent or if I put it in an inline verbatim block it disables the verbatim markup - seems like a bug) but either nikola or org or something else marked the single apostrophe as an error with a red box around it in the output so I changed it to a backtick (`) to get rid of the box.

Past experience told me that the error indicated I was likely missing a (debian) package.

apt-cache search ulem
texlive-generic-recommended - TeX Live: Generic recommended packages
libextutils-modulemaker-perl - Perl extension to build module from scratch

Since I'm doing something with LaTex, not perl I decided that installing the texlive-generic-recommended package was probably what I should do.

sudo apt-get install texlive-generic-recommended

Second try

I hit C-c C-e l p again and this time it did output a PDF, but there was no syntax-highlighting of the code. Followng this blog post I added this to my init.el file:

;; export to latex/pdf
(require 'ox-latex)

;; syntax-highlighting for pdf's
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted)
(setq org-latex-pdf-process '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))

Once I re-loaded the init.el file, the PDF's came out with the syntax highlighting working.

The original post used xelatex instead of pdflatex but I don't use xelatex. Also minted can be found in the texlive-latex-extras package if it isn't already installed. The post also mentions needing pygments but I didn't need it, possibly because it's a dependency for other python libraries that I've already installed.

Org-Babel Cheat Sheet

Keyboard Shortcuts

Code Block Shortcuts

Keys Command Effect
C-c C-c org-babel-execute-src-block Execute the code in the current block.
C-c '   Open/close edit-buffer with mode set to match the code-block language.
C-c C-v C-z org-babel-switch-to-session Open a python/ipython console (only works with :session)

Buffer-wide Shortcuts

Keys Command Effect
<s Tab   Create a code block.
C-c C-v C-b org-babel-execute-buffer Execute all code blocks in buffer.
C-c C-v C-f org-babel-tangle-file Tangle all blocks marked to :tangle
C-c C-v C-t org-babel-tangle Seems like an alias for tangle file…

Code Block Headers

This is the subset of headers/header values that I'm interested in right now.

Code to tangle

The pattern I use to tangle (create an external code file) is:

  • python as the language (since I'm not using it with an interactive session, no need for ipython)
  • :noweb tangle is turned on from init.el so that I can substitute code defined elsewhere into the block
  • :tangle <path to file>
  #+begin_src python :tangle literate_python/literate.py
    """A docstring for the literate.py module"""

    # imports
    import sys
    <<literate-main-imports>>

    # constants

    # exception classes

    # interface functions

    # classes


    <<LiterateClass-definition>>

    # internal functions & classes

    <<literate-main>>


    if __name__ == "__main__":
	status = main()
	sys.exit(status)
  #+end_src

Since I have :noweb tangle set, the substitions (e.g. <<literate-main-imports>>) don't get expanded in HTML/Latex output (although they do when you create the python file).

  """A docstring for the literate.py module"""

  # imports
  import sys
  <<literate-main-imports>>

If you want to show the substitutions when exporting use :noweb yes in the header.

  """A docstring for the literate.py module"""

  # imports
  import sys

A named section

The noweb substitution above (<<literate-main-imports>>) worked because there was a named-section (defined here) that it could use:

  #+name: literate-main-imports
  #+begin_src python
    from argparse import ArgumentParser
  #+end_src

Update

I now prefer to use :noweb-ref in the header instead of the separate #+name: block.

  #+begin_src python :noweb-ref literate-main-imports
    from argparse import ArgumentParser
  #+end_src

Results

The :results header argument declares how to handle what's returned from executing a code block. There are three classes of arguments and you can use up to one of each in the header.

Result Classes

Class Meaning
collection How the results should be collected if there's multiple outputs.
type Declare what type of result the code block will return.
handling How should results be handled.

Collection Class

Option Meaning
value (Default) Uses the value of the last statement in the block (python requires a return statement)
output (:results output) Collects everything sent to stdout in the block.

Type Class

Option Example Meaning
table :results value table Return an org-mode table (vector)
scalar :results value scalar Return exactly the value returned (string)
file :results value file Return an org-mode link to a file
raw :results value raw Return as org-mode command
html :results value html Expect contents for #+begin_html
latex :results value latex Expect contents for #+begin_latex
code :results value code Expect contents for #+begin_src
pp :results value pp Expect code and pretty-print it

Handling Class

Option Example Meaning
silent :results output silent Don't output in org-mode buffer
replace :results output replace (Default) Overwrite any previous result
append :results output append Append output after any previous output
prepend :results output prepend Put output above any previous output

Exports

This argument tells org-babel what to put in any exported HTML or Latex files.

Option Example Meaning
code :exports code (default) The code in the block will be included in the export.
results :exports results The result of evaluating the code will be included.
both :exports both Include code and results in the file.
none :exports none Don't include anything in the file.

Running Tests

Say there was another section in the document that tangled a test-file (named testliterate.py) to test our main source file. Once both are tangled you can run it in the document using sh as the language. The org-mode documentation shows a more complex version of this which builds a pass-fail table, but that's beyond me right now.

   #+name: shell-run-pytest
   #+begin_src sh :results output :exports both
   py.test -v literate_python/testliterate.py
   #+end_src
============================= test session starts ==============================
platform linux -- Python 3.5.1+, pytest-3.0.5, py-1.4.32, pluggy-0.4.0 -- /home/cronos/.virtualenvs/nikola/bin/python3
cachedir: .cache
rootdir: /home/cronos/projects/nikola/posts, inifile: 
plugins: faker-2.0.0, bdd-2.18.1
collecting ... collected 1 items

literate_python/testliterate.py::test_constructor PASSED

=========================== 1 passed in 0.06 seconds ===========================

Specific Block Cases

Plant UML

Besides setting the language to plantuml you need to specify and output-file path and set :exports results so that the actual plantuml code won't be in the exported document but the diagram will.

#+begin_src plantuml :file literate_python/literateclass.png :exports results
skinparam monochrome true

LiterateClass : String who
LiterateClass : String ()
#+end_src

ob-ipython

The main thing to remember for ob-ipython is that you need to run it as a :session. I didn't do it for most of the examples, but I've found since I first wrote this that using named sessions makes it a lot easier to work. Otherwise you might have more than one buffer with an org-babel document and they will be sharing the same ipython process, which can cause mysterious errors.

#+begin_src ipython :session session1
  # python standard library
  import os
#+end_src

When using pandas most of the methods produce values, but the info method instead prints to stdout so you have to specify this as the :results or it will popup a separate buffer with the output.

#+begin_src ipython :session session1 :results output
housing.info()
#+end_src

When you create figures, besides making sure that you use the %matplotlib inline magic, you also need to specify a file path where matplotlib can save the image.

#+BEGIN_SRC ipython :session session1 :file "images/ocean_proximity_count.png"
figure = seaborn.countplot(x="ocean_proximity", data=housing)
#+end_src

Set Up

Dependencies

I'm using ob-ipython to use jupyter/ipython with org-babel so you have to install it (I used MELPA). In addition you need to install the python dependencies, the main ones being ipython and jupyter. Additionally, I use elpy (also from MELPA) which has its own dependencies. I think the easiest way to check and see what elpy dependencies you need is to install elpy (there's two components, an emacs one you install from melpa and a python component you install from pip) then run M-x elpy-config to see what's missing.

init.el

Since I mentioned ob-ipython and elpy I'll list what I have in my init.el file for elpy and org-babel.

Elpy

;; elpy
(elpy-enable)
(setq elpy-rpc-backend "jedi")
(eval-after-load "python"
 '(define-key python-mode-map "\C-cx" 'jedi-direx:pop-to-buffer))
(elpy-use-ipython)

org-babel

;; org-babel
;;; syntax-highlighting/editing
(add-to-list 'org-src-lang-modes '("rst" . "rst"))
(add-to-list 'org-src-lang-modes '("feature" . "feature"))

;;; languages to execute/edit
(org-babel-do-load-languages
 'org-babel-load-languages
 '((ipython . t)
   (plantuml . t)
   (shell . t)
   (org . t)
   ;; other languages..
   ))

;;; noweb expansion only when you tangle
(setq org-babel-default-header-args
      (cons '(:noweb . "tangle")
	    (assq-delete-all :noweb org-babel-default-header-args))
      )

;;; Plant UML diagrams
(setq org-plantuml-jar-path (expand-file-name "/usr/share/plantuml/plantuml.jar"))

;;; execute block evaluation without confirmation
(setq org-confirm-babel-evaluate nil)   

;;; display/update images in the buffer after evaluation
(add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append)

Integrating with Nikola/Sphinx