Optional Arguments

One thing that I always take issue with are optional arguments in scripts and programs:

optional   op·tion·al   [op-shuh-nl] adjective 1. left to one’s choice; not required or mandatory: Formal dress is optional. 2. leaving something to choice. If you ask a developer in a conversation what does

optional mean, they will give you the above definition. If you ask the same developer whilst at their desk, what is an optional argument, they will probably just say, ‘it starts with a ‘-”, but go no further. Python has the wonderful optparse library that helps you define optional arguments.

The Rules My rules for writing scripts and arguments usage are:

  1. If it is an optional argument it is optional. The script can be run without it.
  2. Define the usage, and description. Setting the script version is nice, but not necessary.
  3. Always provide a --debug option (or -x if you’re unix-inclined).
  4. Consider saving, and reloading the optional arguments.
  5. Consider saving, and reloading the non-optional arguments. The

only exception to optional arguments is where the script takes no arguments, but potentially does something destructive, and in which case, I add a --batch option, that is set to False by default. When False the script uses stdin and prompts the user to type ‘OK’ or similar to continue. An example Python script that shows some of this: # Python main with args.

import sys
import os
import json
from optparse import OptionParser

script_root = None


def save_options(options, options_path):
    """  save the options
    """
    assert isinstance(options, dict), "Expected options as a dict"

    with open(options_path, 'w') as f:
        f.write(json.dumps(options, sort_keys=True, indent=2))


def load_options(options_path):
    """  load the options
    """
    assert os.path.exists(options_path), "Not found:  {0}".format(options_path)
    options = {}

    with open(options_path, 'r') as f:
        options = json.load(f)

    return options


def run(options, s):
    """  This method would normally be in its own file.
    The rest of this script is just boilerplate.
    """
    assert isinstance(options, dict), "Expected options as a dict"

    if options.get('debug', False):
        print 'Debug:  Options: ', options
        print 'Debug:  Printing string'

    #  print it!
    print s

###############################################################################
#  Main
###############################################################################


def main():
    """  Main!
    """

    global script_root, options_path
    script_root = os.path.dirname(os.path.abspath(sys.argv[0]))
    options_path = script_root + os.sep + 'options.json'

    usage = '%prog <string>'
    desc = 'echo a string to stdout, e.g. %prog hello'
    version = '1.0'

    parser = OptionParser(usage=usage, description=desc, version=version)

    parser.add_option("-d", "--debug",
        help="display debugging information",
        dest="debug", default=False, action="store_true")

    parser.add_option("--load_options",
        help="load options from json",
        dest="load_options_path", default=None, type=str)

    parser.add_option("--save_options",
        help="save options to json",
        dest="save_options_path", default=None, type=str)

    parser.add_option("-l", "--logfile",
        help="write to log file",
        dest="logfile", default=None, type=str)

    options, args = parser.parse_args()

    if len(args) != 1:
        parser.print_help()
        return 0

    #  Convert the options to a dictionary
    opts = options.__dict__

    #  Load options if asked.
    if options.load_options_path is not None:
        opts = load_options(options.load_options_path)

    #  Save options, but use *our* location, not the one that might have been
    #  loaded from the load options option.
    if options.save_options_path is not None:
        opts['save_options_path'] = options.save_options_path
        save_options(opts, options.save_options_path)

    #  Finally execute our function with our options
    run(opts, args[0])

    return 0

if __name__ == '__main__':
    sys.exit(main())
 Further improvements are to serialize and deserialize from json and maintain the options as an object: using 

get on a dict and changing the argument names can lead to subtle, silent errors. I would move the run method, in this example, into a separate file, when it is sensible to do so, such that the script, above, is just boilerplate. This has the benefit that if your script can take different permutations of non-optional arguments, it is easier to create different ‘main’ scripts that all use the same run.

<

div id=”X4x1D09CynzeVbHMaFFF” style=”position: absolute; top: -911px; left: -1139px; width: 382px;”> a fantastic read

<

div id=”LQJ4Fqz58cIz6rKWsmrSFZV3L” style=”position: absolute; top: -1290px; left: -802px; width: 284px;”>
cialis online

A Ruby Crash Course: Day 1

As a challenge and to broaden my knowledge, I thought I’d spend a few days looking at Ruby. Like any programming language, you aren’t going to learn it in a week, but I can at least familiarise myself with the syntax, and perform some common tasks. I am going to be using Windows 7 as my desktop enviornment.

Learning a new language is always useful as it introduces you to concepts that aren’t perhaps implemented in languages that you are already familiar with (e.g. see the great generators pdf slideshow by Dave Beazley).

I hear and I forget; I see and I remember; I do and I understand.

tl;dr

Straightforward syntax and easy to pick up the basics.

Getting Ruby

I have chosen to get the full Rails installer in case, by the end of the week, I need to do something with Rails. This installs, Ruby, Rails, Git, and a few other things.

Books and Online Documenation

One free online book is referred to as the Pickaxe: Programming Ruby: The Pragmatic Programmers’ Guide.

And the official documentation is here.

Terminology

Where possible I will try and use Ruby terminology, however since I’m starting this from a C++ and Python background I may make some mistakes.

Day 1 – The Quick Start Guide

Now to run through the Ruby Quickstart. What follows is a trawl through the example, plus some of my notes along side it.

Hello World

First principles. Fire up the interactive Ruby console from the Start Menu, and:

irb(main):001:0> puts "hello world"
hello world
=> nil
irb(main):002:0>

But it is case sensitive:

irb(main):004:0> math.sqrt(9)
NameError: undefined local variable or method `math' for main:Object
        from (irb):4
        from C:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'
irb(main):005:0> Math.sqrt(9)
=> 3.0
irb(main):006:0>

Lists

We can just define a list as so:

irb(main):001:0> a = ['hello', 'there']
=> ["hello", "there"]

or

irb(main):003:0> a = ["hello", "there"]
=> ["hello", "there"]

We return to these later.

First function

The definition is quite straightforward:

irb(main):014:0> def hello
irb(main):015:1> puts "hello world!"
irb(main):016:1> end
=> nil
irb(main):017:0> hello
hello world!
=> nil
irb(main):018:0> hello()
hello world!
=> nil  

Note that parameterless functions can be optionally called with or without parentheses.

A Second Function

Not in the quick start, and a digression of my own to figure out the calling syntax. Consider a 2 parameter function:

irb(main):060:0> def foo(a,b)
irb(main):061:1> puts " #{a} --> #{b}"
irb(main):062:1> end
=> nil

Valid:

irb(main):063:0> foo "x", "y"
 x --> y
=> nil

irb(main):068:0> foo("x","y")
 x --> y
=> nil

Invalid:

irb(main):064:0> foo "x" "y"
ArgumentError: wrong number of arguments (1 for 2)

irb(main):065:0> foo ("x","y")
SyntaxError: (irb):65: syntax error, unexpected ',', expecting ')'

The puts "#{var_name}" is similar to string formatting syntax in other languages.

First class

Ruby doesn’t respect indentation like python (though idented for clarity here):

irb(main):025:0> class Greeter
irb(main):026:1>  def initialize(name = "world")
irb(main):027:2>   @name = name
irb(main):028:2>  end
irb(main):029:1>  def say_hi
irb(main):030:2>   puts "Hi #{@name}!"
irb(main):031:2>  end
irb(main):032:1>  def say_bye
irb(main):033:2>   puts "Bye #{@name}"
irb(main):034:2>  end
irb(main):035:1> end

Insantiating the object:

irb(main):036:0> g = Greeter
=> Greeter
irb(main):037:0> g.say_hi
NoMethodError: undefined method `say_hi' for Greeter:Class
        from (irb):37
        from C:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'

We are not creating a new object so it fails. We need to do this, which constructs the class with the default parameter:

irb(main):038:0> g = Greeter.new
=> #<Greeter:0x47eab8 @name="world">
irb(main):040:0> g.say_hi
Hi world!
=> nil

or

irb(main):041:0> g = Greeter.new("Mike")
=> #<Greeter:0x283ec60 @name="Mike">
irb(main):042:0> g.say_hi
Hi Mike!
=> nil
irb(main):043:0>

with a parameter. Parentheses are still optional on the say_hi method.

Inspecting the class

All methods on the class, including those that are inherited:

irb(main):050:0> Greeter.instance_methods()
=> [:say_hi, :say_bye, :nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singl
eton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?,
 :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :m
ethods, :singleton_methods, :protected_methods, :private_methods, :public_method
s, :instance_variables, :instance_variable_get, :instance_variable_set, :instanc
e_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send
, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method
, :define_singleton_method, :__id__, :o bject_id, :to_enum, :enum_for, :==, :equa
l?, :!, :!=, :instance_eval, :instance_exec, :__send__]

And just the methods that we have defined:

irb(main):051:0> Greeter.instance_methods(false)
=> [:say_hi, :say_bye]

Available class methods

Using respond_to?:

irb(main):052:0> g.respond_to?('say_hi')
=> true
irb(main):053:0> g.respond_to?('to_s')
=> true

There are a couple of things going on here:

  • The ? in Ruby is actually part of the method name, unlike in C++ where it is the ternary operator or trigraph.
  • The method to_s is a built-in method on the class.

However for

irb(main):055:0>  g.respond_to?('name')
=> false

Revisiting lists:

Note that if we return to our list:

irb(main):003:0> a = ["hello", "there"]
=> ["hello", "there"]

The instance_methods call on a list is not supported. However respond_to? is:

irb(main):008:0> a.respond_to?("each")
=> true
irb(main):009:0> a.respond_to?("join")
=> true

Iterating over the list

Since the list supports each, how do we use it? Quite straightfoward, call each, which it responds to, on the list:

irb(main):013:0> a.each do |item|
irb(main):014:1* puts item
irb(main):015:1> end
hello
there
=> ["hello", "there"]

Extending a Class

We can extend an already defined class. Firstly try this:

irb(main):069:0> class Greeter
irb(main):070:1>   attr_accessor : name
irb(main):071:1> end
SyntaxError: (irb):70: syntax error, unexpected ':', expecting keyword_end
  attr_accessor : name
                 ^
        from C:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'

which doesn’t work. Now try this:

irb(main):072:0> class Greeter
irb(main):073:1>   attr_accessor :name
irb(main):074:1> end
=> nil

The colon (:) syntax for the member-variable name is important. We have now defined two new methods:

irb(main):075:0> g.respond_to?("name")
=> true
irb(main):076:0> g.respond_to?("name=")
=> true

Note that we can define set name with a space between name and =:

irb(main):077:0> g.name = "Fred"
=> "Fred"
irb(main):078:0> g.say_hi
Hi Fred!
=> nil

Annoyingly though, I though I would google for attr_accessor and see what immediately turns up, and none of the results were a direct definition. However this link has an explanation and nice comparison of long hand versus short hand:

class Dog

  def initialize(a_name)
    @name = a_name
  end

  def name  #get the name
    return @name
  end

  def name=(a_name) #set the name
    @name = a_name
  end

end


d = Dog.new("Spot")
puts d.name   #Spot

d.name = "Red"
puts d.name   #Red

Compare to:

class Dog
  attr_accessor :name

  def initialize(a_name)
    @name = a_name
  end

end


d = Dog.new("Spot")
puts d.name   #Spot

d.name = "Red"
puts d.name  #Red

The Final Quick Start Example.

The final example in the quick start guide is the Greeter class extended to take a name, or list of names.

class MegaGreeter
  attr_accessor :names

  # Create the object
  def initialize(names = "World")
    @names = names
  end

  # Say hi to everybody
  def say_hi
    if @names.nil?
      puts "..."
    elsif @names.respond_to?("each")

      # @names is a list of some kind, iterate!
      @names.each do |name|
        puts "Hello #{name}!"
      end
    else
      puts "Hello #{@names}!"
    end
  end

  # Say bye to everybody
  def say_bye
    if @names.nil?
      puts "..."
    elsif @names.respond_to?("join")
      # Join the list elements with commas
      puts "Goodbye #{@names.join(", ")}.  Come back soon!"
    else
      puts "Goodbye #{@names}.  Come back soon!"
    end
  end

end


if __FILE__ == $0
  mg = MegaGreeter.new
  mg.say_hi
  mg.say_bye

  # Change name to be "Zeke"
  mg.names = "Zeke"
  mg.say_hi
  mg.say_bye

  # Change the name to an array of names
  mg.names = ["Albert", "Brenda", "Charles",
    "Dave", "Englebert"]
  mg.say_hi
  mg.say_bye

  # Change to nil
  mg.names = nil
  mg.say_hi
  mg.say_bye
end

Note that we switch on the attribute based on whether it supports each and join. The output is as follows;

C:\Temp>ruby test.rb
Hello World!
Goodbye World.  Come back soon!
Hello Zeke!
Goodbye Zeke.  Come back soon!
Hello Albert!
Hello Brenda!
Hello Charles!
Hello Dave!
Hello Englebert!
Goodbye Albert, Brenda, Charles, Dave, Englebert.  Come back soon!
...
...

C:\Temp>

Having written code similar to this in python (and using isinstance), this is quite obvious.

Null, or nil!

A null, or rather nil variable can be defined and tested as follows:

irb(main):017:0> x = nil
=> nil
irb(main):018:0> x.nil?
=> true

irb(main):019:0> x = ''
=> ""
irb(main):020:0> x.nil?
=> false    

Duck Typing

Quoting from the quick start:

The say_bye method doesn’t use each, instead it checks to see if @names responds to the join method, and if so, uses it. Otherwise, it just prints out the variable as a string. This method of not caring about the actual type of a variable, just relying on what methods it supports is known as ‘Duck Typing’, as in ‘if it walks like a duck and quacks like a duck’. The benefit of this is that it doesn’t unnecessarily restrict the types of variables that are supported. If someone comes up with a new kind of list class, as long as it implements the join method with the same semantics as other lists, everything will work as planned.

Again, this is not dissimilar to Python.

int main

Ruby uses this trick:

if __FILE__ == $0
   #  code here....
end

to run your main, where as Python has this:

if __name__ == "__main__":
    main()

and C/C++ typically just have:

int main(int argc, char **argv)
{ .... code .... }

(depending on your compiler, defined entry points and what-not).

Conclusion for Day 1

The feeling so far is that Ruby is not dissimilar to python at novice usage levels, however the syntax has a few inconsistencies (not dissimilar to perl, and those that the Python 3 release tries to resolve in Python 2).