diff options
Diffstat (limited to 'Tools/demo')
-rw-r--r-- | Tools/demo/README | 16 | ||||
-rwxr-xr-x | Tools/demo/beer.py | 9 | ||||
-rwxr-xr-x[-rw-r--r--] | Tools/demo/eiffel.py (renamed from Tools/demo/Eiffel.py) | 125 | ||||
-rwxr-xr-x[-rw-r--r--] | Tools/demo/hanoi.py | 26 | ||||
-rwxr-xr-x | Tools/demo/life.py | 93 | ||||
-rwxr-xr-x | Tools/demo/markov.py | 6 | ||||
-rwxr-xr-x | Tools/demo/mcast.py | 20 | ||||
-rwxr-xr-x | Tools/demo/queens.py | 6 | ||||
-rw-r--r-- | Tools/demo/redemo.py | 171 | ||||
-rwxr-xr-x | Tools/demo/rpython.py | 8 | ||||
-rwxr-xr-x | Tools/demo/rpythond.py | 13 | ||||
-rwxr-xr-x[-rw-r--r--] | Tools/demo/sortvisu.py | 7 | ||||
-rwxr-xr-x[-rw-r--r--] | Tools/demo/ss1.py | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | Tools/demo/vector.py (renamed from Tools/demo/Vec.py) | 10 |
14 files changed, 367 insertions, 149 deletions
diff --git a/Tools/demo/README b/Tools/demo/README new file mode 100644 index 0000000..e914358 --- /dev/null +++ b/Tools/demo/README @@ -0,0 +1,16 @@ +This directory contains a collection of demonstration scripts for +various aspects of Python programming. + +beer.py Well-known programming example: Bottles of beer. +eiffel.py Python advanced magic: A metaclass for Eiffel post/preconditions. +hanoi.py Well-known programming example: Towers of Hanoi. +life.py Curses programming: Simple game-of-life. +markov.py Algorithms: Markov chain simulation. +mcast.py Network programming: Send and receive UDP multicast packets. +queens.py Well-known programming example: N-Queens problem. +redemo.py Regular Expressions: GUI script to test regexes. +rpython.py Network programming: Small client for remote code execution. +rpythond.py Network programming: Small server for remote code execution. +sortvisu.py GUI programming: Visualization of different sort algorithms. +ss1.py GUI/Application programming: A simple spreadsheet application. +vector.py Python basics: A vector class with demonstrating special methods.
\ No newline at end of file diff --git a/Tools/demo/beer.py b/Tools/demo/beer.py index 56eec7b..af58380 100755 --- a/Tools/demo/beer.py +++ b/Tools/demo/beer.py @@ -1,6 +1,11 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 -# By GvR, demystified after a version by Fredrik Lundh. +""" +A Python version of the classic "bottles of beer on the wall" programming +example. + +By Guido van Rossum, demystified after a version by Fredrik Lundh. +""" import sys diff --git a/Tools/demo/Eiffel.py b/Tools/demo/eiffel.py index 15fa58a..3a28224 100644..100755 --- a/Tools/demo/Eiffel.py +++ b/Tools/demo/eiffel.py @@ -1,13 +1,20 @@ -"""Support Eiffel-style preconditions and postconditions.""" +#!/usr/bin/env python3 +""" +Support Eiffel-style preconditions and postconditions for functions. + +An example for Python metaclasses. +""" + +import unittest from types import FunctionType as function class EiffelBaseMetaClass(type): def __new__(meta, name, bases, dict): meta.convert_methods(dict) - return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases, - dict) + return super(EiffelBaseMetaClass, meta).__new__( + meta, name, bases, dict) @classmethod def convert_methods(cls, dict): @@ -31,6 +38,7 @@ class EiffelBaseMetaClass(type): if pre or post: dict[k] = cls.make_eiffel_method(dict[m], pre, post) + class EiffelMetaClass1(EiffelBaseMetaClass): # an implementation of the "eiffel" meta class that uses nested functions @@ -39,16 +47,17 @@ class EiffelMetaClass1(EiffelBaseMetaClass): def method(self, *args, **kwargs): if pre: pre(self, *args, **kwargs) - x = func(self, *args, **kwargs) + rv = func(self, *args, **kwargs) if post: - post(self, x, *args, **kwargs) - return x + post(self, rv, *args, **kwargs) + return rv if func.__doc__: method.__doc__ = func.__doc__ return method + class EiffelMethodWrapper: def __init__(self, inst, descr): @@ -58,7 +67,8 @@ class EiffelMethodWrapper: def __call__(self, *args, **kwargs): return self._descr.callmethod(self._inst, args, kwargs) -class EiffelDescriptor(object): + +class EiffelDescriptor: def __init__(self, func, pre, post): self._func = func @@ -79,63 +89,58 @@ class EiffelDescriptor(object): self._post(inst, x, *args, **kwargs) return x + class EiffelMetaClass2(EiffelBaseMetaClass): # an implementation of the "eiffel" meta class that uses descriptors make_eiffel_method = EiffelDescriptor -def _test(metaclass): - class Eiffel(metaclass=metaclass): - pass - - class Test(Eiffel): - - def m(self, arg): - """Make it a little larger""" - return arg + 1 - - def m2(self, arg): - """Make it a little larger""" - return arg + 1 - - def m2_pre(self, arg): - assert arg > 0 - - def m2_post(self, result, arg): - assert result > arg - - class Sub(Test): - def m2(self, arg): - return arg**2 - def m2_post(self, Result, arg): - super(Sub, self).m2_post(Result, arg) - assert Result < 100 - - t = Test() - t.m(1) - t.m2(1) - try: - t.m2(0) - except AssertionError: - pass - else: - assert False - - s = Sub() - try: - s.m2(1) - except AssertionError: - pass # result == arg - else: - assert False - try: - s.m2(10) - except AssertionError: - pass # result == 100 - else: - assert False - s.m2(5) + +class Tests(unittest.TestCase): + + def testEiffelMetaClass1(self): + self._test(EiffelMetaClass1) + + def testEiffelMetaClass2(self): + self._test(EiffelMetaClass2) + + def _test(self, metaclass): + class Eiffel(metaclass=metaclass): + pass + + class Test(Eiffel): + def m(self, arg): + """Make it a little larger""" + return arg + 1 + + def m2(self, arg): + """Make it a little larger""" + return arg + 1 + + def m2_pre(self, arg): + assert arg > 0 + + def m2_post(self, result, arg): + assert result > arg + + class Sub(Test): + def m2(self, arg): + return arg**2 + + def m2_post(self, Result, arg): + super(Sub, self).m2_post(Result, arg) + assert Result < 100 + + t = Test() + self.assertEqual(t.m(1), 2) + self.assertEqual(t.m2(1), 2) + self.assertRaises(AssertionError, t.m2, 0) + + s = Sub() + self.assertRaises(AssertionError, s.m2, 1) + self.assertRaises(AssertionError, s.m2, 10) + self.assertEqual(s.m2(5), 25) + if __name__ == "__main__": - _test(EiffelMetaClass1) - _test(EiffelMetaClass2) + unittest.main() diff --git a/Tools/demo/hanoi.py b/Tools/demo/hanoi.py index 34a0bba..dad0234 100644..100755 --- a/Tools/demo/hanoi.py +++ b/Tools/demo/hanoi.py @@ -1,17 +1,18 @@ -# Animated Towers of Hanoi using Tk with optional bitmap file in -# background. -# -# Usage: tkhanoi [n [bitmapfile]] -# -# n is the number of pieces to animate; default is 4, maximum 15. -# -# The bitmap file can be any X11 bitmap file (look in -# /usr/include/X11/bitmaps for samples); it is displayed as the -# background of the animation. Default is no bitmap. +#!/usr/bin/env python3 -# This uses Steen Lumholt's Tk interface -from tkinter import * +""" +Animated Towers of Hanoi using Tk with optional bitmap file in background. +Usage: hanoi.py [n [bitmapfile]] + +n is the number of pieces to animate; default is 4, maximum 15. + +The bitmap file can be any X11 bitmap file (look in /usr/include/X11/bitmaps for +samples); it is displayed as the background of the animation. Default is no +bitmap. +""" + +from tkinter import Tk, Canvas # Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c # as temporary. For each move, call report() @@ -123,7 +124,6 @@ class Tkhanoi: self.pegstate[b].append(i) -# Main program def main(): import sys diff --git a/Tools/demo/life.py b/Tools/demo/life.py index 3cbc053..dfb9ab8 100755 --- a/Tools/demo/life.py +++ b/Tools/demo/life.py @@ -1,23 +1,23 @@ #!/usr/bin/env python3 -# life.py -- A curses-based version of Conway's Game of Life. -# Contributed by AMK -# Mouse support and color by Dafydd Crosby -# -# An empty board will be displayed, and the following commands are available: -# E : Erase the board -# R : Fill the board randomly -# S : Step for a single generation -# C : Update continuously until a key is struck -# Q : Quit -# Cursor keys : Move the cursor around the board -# Space or Enter : Toggle the contents of the cursor's position -# -# TODO : -# Make board updates faster -# - -import random, string, traceback + +""" +A curses-based version of Conway's Game of Life. + +An empty board will be displayed, and the following commands are available: + E : Erase the board + R : Fill the board randomly + S : Step for a single generation + C : Update continuously until a key is struck + Q : Quit + Cursor keys : Move the cursor around the board + Space or Enter : Toggle the contents of the cursor's position + +Contributed by Andrew Kuchling, Mouse support and color by Dafydd Crosby. +""" + import curses +import random + class LifeBoard: """Encapsulates a Life board @@ -31,7 +31,7 @@ class LifeBoard: next generation. Then display the state of the board and refresh the screen. erase() -- clear the entire board - makeRandom() -- fill the board randomly + make_random() -- fill the board randomly set(y,x) -- set the given cell to Live; doesn't refresh the screen toggle(y,x) -- change the given cell from live to dead, or vice versa, and refresh the screen display @@ -53,7 +53,7 @@ class LifeBoard: # Draw a border around the board border_line = '+'+(self.X*'-')+'+' self.scr.addstr(0, 0, border_line) - self.scr.addstr(self.Y+1,0, border_line) + self.scr.addstr(self.Y+1, 0, border_line) for y in range(0, self.Y): self.scr.addstr(1+y, 0, '|') self.scr.addstr(1+y, self.X+1, '|') @@ -62,21 +62,21 @@ class LifeBoard: def set(self, y, x): """Set a cell to the live state""" if x<0 or self.X<=x or y<0 or self.Y<=y: - raise ValueError("Coordinates out of range %i,%i"% (y,x)) + raise ValueError("Coordinates out of range %i,%i"% (y, x)) self.state[x,y] = 1 def toggle(self, y, x): """Toggle a cell's state between live and dead""" - if x<0 or self.X<=x or y<0 or self.Y<=y: - raise ValueError("Coordinates out of range %i,%i"% (y,x)) - if (x,y) in self.state: - del self.state[x,y] + if x < 0 or self.X <= x or y < 0 or self.Y <= y: + raise ValueError("Coordinates out of range %i,%i"% (y, x)) + if (x, y) in self.state: + del self.state[x, y] self.scr.addch(y+1, x+1, ' ') else: - self.state[x,y] = 1 + self.state[x, y] = 1 if curses.has_colors(): - #Let's pick a random color! - self.scr.attrset(curses.color_pair(random.randrange(1,7))) + # Let's pick a random color! + self.scr.attrset(curses.color_pair(random.randrange(1, 7))) self.scr.addch(y+1, x+1, self.char) self.scr.attrset(0) self.scr.refresh() @@ -115,8 +115,9 @@ class LifeBoard: # Birth d[i,j] = 1 if curses.has_colors(): - #Let's pick a random color! - self.scr.attrset(curses.color_pair(random.randrange(1,7))) + # Let's pick a random color! + self.scr.attrset(curses.color_pair( + random.randrange(1, 7))) self.scr.addch(j+1, i+1, self.char) self.scr.attrset(0) if not live: self.boring = 0 @@ -128,7 +129,7 @@ class LifeBoard: self.state = d self.scr.refresh() - def makeRandom(self): + def make_random(self): "Fill the board with a random pattern" self.state = {} for i in range(0, self.X): @@ -152,9 +153,9 @@ def display_menu(stdscr, menu_y): if curses.has_colors(): stdscr.attrset(curses.color_pair(1)) stdscr.addstr(menu_y, 4, - 'Use the cursor keys to move, and space or Enter to toggle a cell.') + 'Use the cursor keys to move, and space or Enter to toggle a cell.') stdscr.addstr(menu_y+1, 4, - 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit') + 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit') stdscr.attrset(0) def keyloop(stdscr): @@ -186,10 +187,10 @@ def keyloop(stdscr): xpos, ypos = board.X//2, board.Y//2 # Main loop: - while (1): + while True: stdscr.move(1+ypos, 1+xpos) # Move the cursor c = stdscr.getch() # Get a keystroke - if 0<c<256: + if 0 < c < 256: c = chr(c) if c in ' \n': board.toggle(ypos, xpos) @@ -201,14 +202,14 @@ def keyloop(stdscr): # Activate nodelay mode; getch() will return -1 # if no keystroke is available, instead of waiting. stdscr.nodelay(1) - while (1): + while True: c = stdscr.getch() if c != -1: break - stdscr.addstr(0,0, '/') + stdscr.addstr(0, 0, '/') stdscr.refresh() board.display() - stdscr.addstr(0,0, '+') + stdscr.addstr(0, 0, '+') stdscr.refresh() stdscr.nodelay(0) # Disable nodelay mode @@ -219,18 +220,19 @@ def keyloop(stdscr): elif c in 'Qq': break elif c in 'Rr': - board.makeRandom() + board.make_random() board.display(update_board=False) elif c in 'Ss': board.display() else: pass # Ignore incorrect keys - elif c == curses.KEY_UP and ypos>0: ypos -= 1 - elif c == curses.KEY_DOWN and ypos<board.Y-1: ypos += 1 - elif c == curses.KEY_LEFT and xpos>0: xpos -= 1 - elif c == curses.KEY_RIGHT and xpos<board.X-1: xpos += 1 + elif c == curses.KEY_UP and ypos > 0: ypos -= 1 + elif c == curses.KEY_DOWN and ypos < board.Y-1: ypos += 1 + elif c == curses.KEY_LEFT and xpos > 0: xpos -= 1 + elif c == curses.KEY_RIGHT and xpos < board.X-1: xpos += 1 elif c == curses.KEY_MOUSE: - (mouse_id, mouse_x, mouse_y, mouse_z, button_state) = curses.getmouse() - if (mouse_x>0 and mouse_x<board.X+1) and (mouse_y>0 and mouse_y<board.Y+1): + mouse_id, mouse_x, mouse_y, mouse_z, button_state = curses.getmouse() + if (mouse_x > 0 and mouse_x < board.X+1 and + mouse_y > 0 and mouse_y < board.Y+1): xpos = mouse_x - 1 ypos = mouse_y - 1 board.toggle(ypos, xpos) @@ -245,6 +247,5 @@ def keyloop(stdscr): def main(stdscr): keyloop(stdscr) # Enter the main loop - if __name__ == '__main__': curses.wrapper(main) diff --git a/Tools/demo/markov.py b/Tools/demo/markov.py index 7c08bdb..7a0720f 100755 --- a/Tools/demo/markov.py +++ b/Tools/demo/markov.py @@ -1,4 +1,8 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 + +""" +Markov chain simulation of words or characters. +""" class Markov: def __init__(self, histsize, choice): diff --git a/Tools/demo/mcast.py b/Tools/demo/mcast.py index 6ce7c6d..924c7c3 100755 --- a/Tools/demo/mcast.py +++ b/Tools/demo/mcast.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 -# -# Send/receive UDP multicast packets. -# Requires that your OS kernel supports IP multicast. -# -# Usage: -# mcast -s (sender, IPv4) -# mcast -s -6 (sender, IPv6) -# mcast (receivers, IPv4) -# mcast -6 (receivers, IPv6) + +""" +Send/receive UDP multicast packets. +Requires that your OS kernel supports IP multicast. + +Usage: + mcast -s (sender, IPv4) + mcast -s -6 (sender, IPv6) + mcast (receivers, IPv4) + mcast -6 (receivers, IPv6) +""" MYPORT = 8123 MYGROUP_4 = '225.0.0.250' diff --git a/Tools/demo/queens.py b/Tools/demo/queens.py index ffd4bea..dcc1bae 100755 --- a/Tools/demo/queens.py +++ b/Tools/demo/queens.py @@ -1,12 +1,12 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 -"""N queens problem. +""" +N queens problem. The (well-known) problem is due to Niklaus Wirth. This solution is inspired by Dijkstra (Structured Programming). It is a classic recursive backtracking approach. - """ N = 8 # Default; command line overrides diff --git a/Tools/demo/redemo.py b/Tools/demo/redemo.py new file mode 100644 index 0000000..5286332 --- /dev/null +++ b/Tools/demo/redemo.py @@ -0,0 +1,171 @@ +"""Basic regular expression demostration facility (Perl style syntax).""" + +from tkinter import * +import re + +class ReDemo: + + def __init__(self, master): + self.master = master + + self.promptdisplay = Label(self.master, anchor=W, + text="Enter a Perl-style regular expression:") + self.promptdisplay.pack(side=TOP, fill=X) + + self.regexdisplay = Entry(self.master) + self.regexdisplay.pack(fill=X) + self.regexdisplay.focus_set() + + self.addoptions() + + self.statusdisplay = Label(self.master, text="", anchor=W) + self.statusdisplay.pack(side=TOP, fill=X) + + self.labeldisplay = Label(self.master, anchor=W, + text="Enter a string to search:") + self.labeldisplay.pack(fill=X) + self.labeldisplay.pack(fill=X) + + self.showframe = Frame(master) + self.showframe.pack(fill=X, anchor=W) + + self.showvar = StringVar(master) + self.showvar.set("first") + + self.showfirstradio = Radiobutton(self.showframe, + text="Highlight first match", + variable=self.showvar, + value="first", + command=self.recompile) + self.showfirstradio.pack(side=LEFT) + + self.showallradio = Radiobutton(self.showframe, + text="Highlight all matches", + variable=self.showvar, + value="all", + command=self.recompile) + self.showallradio.pack(side=LEFT) + + self.stringdisplay = Text(self.master, width=60, height=4) + self.stringdisplay.pack(fill=BOTH, expand=1) + self.stringdisplay.tag_configure("hit", background="yellow") + + self.grouplabel = Label(self.master, text="Groups:", anchor=W) + self.grouplabel.pack(fill=X) + + self.grouplist = Listbox(self.master) + self.grouplist.pack(expand=1, fill=BOTH) + + self.regexdisplay.bind('<Key>', self.recompile) + self.stringdisplay.bind('<Key>', self.reevaluate) + + self.compiled = None + self.recompile() + + btags = self.regexdisplay.bindtags() + self.regexdisplay.bindtags(btags[1:] + btags[:1]) + + btags = self.stringdisplay.bindtags() + self.stringdisplay.bindtags(btags[1:] + btags[:1]) + + def addoptions(self): + self.frames = [] + self.boxes = [] + self.vars = [] + for name in ('IGNORECASE', + 'LOCALE', + 'MULTILINE', + 'DOTALL', + 'VERBOSE'): + if len(self.boxes) % 3 == 0: + frame = Frame(self.master) + frame.pack(fill=X) + self.frames.append(frame) + val = getattr(re, name) + var = IntVar() + box = Checkbutton(frame, + variable=var, text=name, + offvalue=0, onvalue=val, + command=self.recompile) + box.pack(side=LEFT) + self.boxes.append(box) + self.vars.append(var) + + def getflags(self): + flags = 0 + for var in self.vars: + flags = flags | var.get() + flags = flags + return flags + + def recompile(self, event=None): + try: + self.compiled = re.compile(self.regexdisplay.get(), + self.getflags()) + bg = self.promptdisplay['background'] + self.statusdisplay.config(text="", background=bg) + except re.error as msg: + self.compiled = None + self.statusdisplay.config( + text="re.error: %s" % str(msg), + background="red") + self.reevaluate() + + def reevaluate(self, event=None): + try: + self.stringdisplay.tag_remove("hit", "1.0", END) + except TclError: + pass + try: + self.stringdisplay.tag_remove("hit0", "1.0", END) + except TclError: + pass + self.grouplist.delete(0, END) + if not self.compiled: + return + self.stringdisplay.tag_configure("hit", background="yellow") + self.stringdisplay.tag_configure("hit0", background="orange") + text = self.stringdisplay.get("1.0", END) + last = 0 + nmatches = 0 + while last <= len(text): + m = self.compiled.search(text, last) + if m is None: + break + first, last = m.span() + if last == first: + last = first+1 + tag = "hit0" + else: + tag = "hit" + pfirst = "1.0 + %d chars" % first + plast = "1.0 + %d chars" % last + self.stringdisplay.tag_add(tag, pfirst, plast) + if nmatches == 0: + self.stringdisplay.yview_pickplace(pfirst) + groups = list(m.groups()) + groups.insert(0, m.group()) + for i in range(len(groups)): + g = "%2d: %r" % (i, groups[i]) + self.grouplist.insert(END, g) + nmatches = nmatches + 1 + if self.showvar.get() == "first": + break + + if nmatches == 0: + self.statusdisplay.config(text="(no match)", + background="yellow") + else: + self.statusdisplay.config(text="") + + +# Main function, run when invoked as a stand-alone Python program. + +def main(): + root = Tk() + demo = ReDemo(root) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/Tools/demo/rpython.py b/Tools/demo/rpython.py index e965720..5e7bc0a 100755 --- a/Tools/demo/rpython.py +++ b/Tools/demo/rpython.py @@ -1,7 +1,9 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 -# Remote python client. -# Execute Python commands remotely and send output back. +""" +Remote python client. +Execute Python commands remotely and send output back. +""" import sys from socket import socket, AF_INET, SOCK_STREAM, SHUT_WR diff --git a/Tools/demo/rpythond.py b/Tools/demo/rpythond.py index fe24b3d..9ffe13a 100755 --- a/Tools/demo/rpythond.py +++ b/Tools/demo/rpythond.py @@ -1,9 +1,12 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 -# Remote python server. -# Execute Python commands remotely and send output back. -# WARNING: This version has a gaping security hole -- it accepts requests -# from any host on the Internet! +""" +Remote python server. +Execute Python commands remotely and send output back. + +WARNING: This version has a gaping security hole -- it accepts requests +from any host on the Internet! +""" import sys from socket import socket, AF_INET, SOCK_STREAM diff --git a/Tools/demo/sortvisu.py b/Tools/demo/sortvisu.py index 4173121..8447bc7 100644..100755 --- a/Tools/demo/sortvisu.py +++ b/Tools/demo/sortvisu.py @@ -1,6 +1,7 @@ -#! /usr/bin/env python3 +#!/usr/bin/env python3 -"""Sorting algorithms visualizer using Tkinter. +""" +Sorting algorithms visualizer using Tkinter. This module is comprised of three ``components'': @@ -15,13 +16,11 @@ to its annotation methods; - and a ``driver'' class which can be used as a Grail applet or as a stand-alone application. - """ from tkinter import * import random - XGRID = 10 YGRID = 10 WIDTH = 6 diff --git a/Tools/demo/ss1.py b/Tools/demo/ss1.py index 8b07489..2d1e490 100644..100755 --- a/Tools/demo/ss1.py +++ b/Tools/demo/ss1.py @@ -1,4 +1,8 @@ -"""SS1 -- a spreadsheet.""" +#!/usr/bin/env python3 + +""" +SS1 -- a spreadsheet-like application. +""" import os import re diff --git a/Tools/demo/Vec.py b/Tools/demo/vector.py index 787af69..da5b389 100644..100755 --- a/Tools/demo/Vec.py +++ b/Tools/demo/vector.py @@ -1,7 +1,13 @@ +#!/usr/bin/env python3 + +""" +A demonstration of classes and their special methods in Python. +""" + class Vec: - """ A simple vector class + """A simple vector class. - Instances of the Vec class can be constructed from numbers + Instances of the Vec class can be constructed from numbers >>> a = Vec(1, 2, 3) >>> b = Vec(3, 2, 1) |