Tag Archives: Python
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:
- If it is an optional argument it is optional. The script can be run without it.
- Define the usage, and description. Setting the script version is nice, but not necessary.
- Always provide a
--debugoption (or-xif you’re unix-inclined). - Consider saving, and reloading the optional arguments.
- 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__,
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_sis 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_byemethod doesn’t use each, instead it checks to see if@namesresponds to thejoinmethod, 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).
