340 likes | 559 Views
CSC 310 – Procedural Programming Languages, Spring, 2009. Chapters 7 and 8: Python Data Types, Subroutines and Control Abstraction. Definition of Types. Denotational type system A type is a set of possible values. Constructive – type system is a union of:
E N D
CSC 310 – Procedural Programming Languages, Spring, 2009 Chapters 7 and 8: Python Data Types, Subroutines and Control Abstraction
Definition of Types • Denotational type system • A type is a set of possible values. • Constructive – type system is a union of: • Built-in (primitive or predefined) types; • Composite, constructed types. • Abstraction-based • Interface consisting of a set of constants and operations
Python’s Dynamic Type System • Python uses dynamictyping. There is no type for parameters, variables and fields. Each value has a typetag. >>> a = 3 >>> type(a) <type 'int'> >>> a = 3.0 >>> type(a) <type 'float'> >>> a = 'this is a string‘ >>> type(a) <type 'str'>
Types are run-time values • Compare types for equality. >>> type(a) == str # Some built-in types have built-in names. True >>> import types # See 'types' module in PY Ch. 13 >>> type(a) == types.StringType # all built-in object types True >>> t = type(type(a)) >>> t <type 'type'> >>> type(t) <type 'type'>
Dynamic versus static type checking Weak versus strong type checking • Python is dynamically and strongly typed. • A value (a.k.a. object) carries a type tag. • Values must be type compatible within operations. • The source-to-bytecode compiler checks syntax, but it does not check type. • Lack of static type checking puts a heavier burden on testing to verify type compatibility.
Type equivalence • Structuralequivalence compares the composite construction of two types. • Nameequivalence is based on lexicaloccurrence of type definitions. Python uses name equivalence. >>> type(3) <type 'int'> >>> type(3) == type(4) True >>> from types import * >>> type(3) == IntType True
Aliased types • Some languages have strictnameequivalence for aliased types. Each name is a distinct type. • Python has loosenameequivalence, because what matters is the type’s value. >>> int <type 'int'> >>> mytype = int >>> mytype == int True >>> mytype(3.7) 3 >>> mytype("5") 5
Explicit type conversion • Conversion uses a type name to cast a value from one type to another. >>> i = 3 >>> type(i) <type 'int'> >>> i = float(i) >>> type(i) <type 'float'>
Implicit type coercion • Mixed type arithmetic promotes to float. >>> value = 3 + 4 * 2.0 >>> value 11.0 Integer overflow promotes to extended precision long. >>> i = 1 << 30 ; i 1073741824 >>> i = i << 1 ; i 2147483648L
Most conversions are explicit • Most conversions must be explicit type casts >>> value = 3.0 + ' units' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'float' and 'str' >>> value = str(3.0) + ' units' ; value '3.0 units' >>> value = 3.0 + float("-4.5") ; value -1.5
No C-like non-converting types casts • C/C++ supports non-converting casts float fval = 1.3125 ; int ival = *((int *) &fval); printf("float value %f gives bit pattern 0x%x\n", fval, ival); float value 1.312500 gives bit pattern 0x3fa80000 • Python’s struct module can convert to a bit pattern format, then back to Python. from struct import pack, unpack >>> '%x' % (unpack('i', pack('f', 1.3125))[0]) '3fa80000'
Polymorphism • A single body of code works with objects of multiple types. • Implicitparametricpolymorphismin Python. >>> def summer(termlist): ... mysum = termlist[0] ... for term in termlist[1:]: ... mysum = mysum + term ... return mysum ... >>> summer([3, 5, 7]) 15 >>> summer(('cat ', 'dog ', 'beaver ')) 'cat dog beaver '
Abstraction-based types • Python type compatibility is determined largely by the operators that combine values. • Subtypepolymorphism takes the form of related derived classes with alternative method definitions. The methods determine type compatibility. • Operator overloading and make methods look syntactically like operators.
Subtypepolymorphism in Python >>> class baseclass(object): ... def f(self, param): ... raise Exception, "not implemented" >>> class derived1(baseclass): ... def f(self, param): ... return int(param) >>> class derived2(baseclass): ... def f(self, param): ... return float(param) >>> d1 = derived1() ; d2 = derived2() >>> d1.f(3) RETURNS 3 >>> d2.f(3) RETURNS 3.0 >>> d1.f(-3.2) RETURNS -3 >>> d2.f(-3.2) RETURNS -3.2000000000000002
Additional explicit type check operations for objects >>> isinstance(d1, derived1) True >>> isinstance(d1, derived2) False >>> issubclass(derived1, baseclass) True >>> issubclass(derived1, derived1) True >>> issubclass(derived1, derived2) False >>> baseclass <class '__main__.baseclass'> >>> derived1 <class '__main__.derived1'> >>> type(derived1) <type 'type'> >>> type(int) <type 'type'> >>> isinstance <built-in function isinstance> >>> isinstance(d1, baseclass) True
Python composite types • Strings are a built-in type with extensive support. • List type supports heterogeneous sequences. • >>> l = [1, 'a', None] ; print l • [1, 'a', None] • Tuple acts like an immutable list. • >>> t = (1, 'a', None, l) ; print t • (1, 'a', None, [1, 'a', None]) • Dictionary is a mapping. Key is immutable. • >>> d = {'a' : 1, 'b' : 2} ; print d['b'] • 2 • Set or frozenset is an unordered collection. • >>> s = set([1, 'a', None]) ; print s • set(['a', 1, None])
No support for explicit parametric polymorphism • No C++ or Java-like “list of int” generictypes. • >>> print type(l) ; print type(t) ; print type(d) ; print type(s) • <type 'list'> • <type 'tuple'> • <type 'dict'> • <type 'set'> • A Python container can hold a mix of types.
Missing types • No standard array type. • List indexing is similar to arrays. • Modulearray (PY 15) stores primitive type objects. • No subranges or enumeration types. • No fixed or variant contiguous records. • No struct or union as in C/C++. • Class objects can be used as records. • No private, etc. access protection on fields. • __field__ naming convention denotes private. • Programmers can create their own types via the C/C++ or Java (for Jython) extension APIs.
Class objects as records • >>> r = record() • print r.a ; r.b = "a value" ; print r.b • None • a value • Methods use self.a or self.b to access fields. • Parameter self is an explicit, first parameter to object methods. • Equivalent to this in C++ or Java.
Values and References • Python uses values for primitives, references for composite types. • Every datum is an object. • >>> from copy import copy, deepcopy ; print l • [1, 'a', None] • >>> l2 = l • >>> l3 = copy(l) # shallow copy • >>> l4 = deepcopy(l) • >>> l2 == l • True >>> l3 == l True >>> l4 == l True >>> l2 is l True >>> l3 is l False >>> l4 is l False
Pointers and Recursive Types • Object references are notational. • There are no explicit pointers. A reference is a pointer. • >>> l • [1, 'a', None] • >>> l.append(l) • >>> l • [1, 'a', None, [...]] • >>> l[3] == l • True • >>> l[3] is l • True • >>> l[3][3][3][3] is l • True
Binary tree in a list >>> t = [] >>> def insert_into_binary_tree(tree, value): ... if (not tree): # 'null pointer' beyond a leaf ... tree.extend([value, [], []]) ... return tree ... elif (tree[0] == value): ... return tree ... elif (value < tree[0]): ... return(insert_into_binary_tree(tree[1], value)) ... else: ... return(insert_into_binary_tree(tree[2], value)) >>> insert_into_binary_tree(t, 5) >>> insert_into_binary_tree(t, 4) >>> insert_into_binary_tree(t, 1) >>> insert_into_binary_tree(t, 2) >>> insert_into_binary_tree(t, 17) >>> insert_into_binary_tree(t, -5) >>> t [5, [4, [1, [-5, [], []], [2, [], []]], []], [17, [], []]] >>> t [5, [4, [1, [-5, [], []], [2, [], []]], []], [17, [], []]]
No dangling references or uncollected objects (no garbage) • Python uses a garbage collector. • Reference counting tracks most objects. • Every variable binding adds a reference. • Additional garbage collection for circular structures. • May be mark and sweep. • May be stop and copy. • May be generational collection. • Details not specified by the language.
Subroutines and Control Abstraction • Python supports functions / procedures, object methods, generators (iterators) and closures (function + static environment). • Primitive parameters are values. • Object parameters are references. • Immutable objects (e.g. tuples) cannot be changed. • Functions and methods are first class objects. • Reflection supports interactive inspection of functions.
Python’s call stack • The call stack occupies contiguous memory. • >>> from sys import getrecursionlimit, setrecursionlimit • getrecursionlimit() • 1000 • Each stack frame points to dynamic data structures. • Both static (lexical) and dynamic bindings resides in dynamic data structures.
Parameters are values for primitives, references for objects >>> def modl(l): ... l = [1, 2, 3] ... print "modl sees " + str(l) >>> gl = [4, 5, 6] >>> modl(gl) modl sees [1, 2, 3] >>> print gl [4, 5, 6] >>> def modelem(l): ... l[1] = 111 ... l.append('appended string') ... print "modelem sees " + str(l) >>> gl = [4, 5, 6] >>> modelem(gl) modelem sees [4, 111, 6, 'appended string'] >>> print gl [4, 111, 6, 'appended string'] >>> modelem(tuple(gl)) Traceback (most recent call last): TypeError: 'tuple' object does not support item assignment
A closure both sees its lexical environment and saves it as well! >>> def outer(vala): ... def inner(valb): ... return vala + valb ... return inner ... >>> f = outer(5) >>> f <function inner at 0x9ad70> >>> f(6) 11
Closures in Python >>> dir(outer) ['__call__', … 'func_closure', …] >>> dir(f) ['__call__', … 'func_closure', …] >>> print outer.func_closure None >>> print f.func_closure (<cell at 0xa4430: int object at 0x668c0>,)
Uncovering the static link in a closure • >>> print f.func_closure[0] • <cell at 0xa4430: int object at 0x668c0> • >>> print type(f.func_closure[0]) • <type 'cell'> • >>> dir(f.func_closure[0]) • ['__class__', '__cmp__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'cell_contents'] • >>> f.func_closure[0].cell_contents • 5
Both positional and keyword parameters supported • Positional, if any, come first in a call. • Default values come last in a definition. • >>> def sum(a, b=3): • ... return a + b • >>> sum(2) • 5 • >>> sum(2,7) • 9 • >>> sum(b=8, a=9) • 17 • >>> sum(4, a=9) • Traceback (most recent call last): • File "<stdin>", line 1, in <module> • TypeError: sum() got multiple values for keyword argument 'a'
Apply allows processes to construct function calls at run time. >>> f = sum >>> keywordargs = {'a': 3, 'b': 4} >>> apply(f,(),keywordargs) 7 >>> apply(f,(7, 6), {}) 13 >>> eval("sum(4,5)") # eval() evaluates text 9
Variable number of *positional and **keyword arguments • A trailing *param is a tuple of variable-length positional parameters. A trailing **param is a dictionary of keyword parameters. • >>> def sum(*terms): • ... mysum = terms[0] • ... for t in terms[1:]: • ... mysum += t • ... return mysum • ... • >>> sum(1, 2, 3, 4, 5) • 15
Exception handling • Exceptions are class objects, similar to C++ and Java. • Finally and except clauses cannot appear in the same try statement. Use nested try. >>> try: ... i = int('dog') ... except ValueError, estring: ... print "exception says: " + str(estring) ... exception says: invalid literal for int() with base 10: 'dog‘ • Use raise to throw an exception.
Generators are Pythons iterators >>> def g(start, end): ... for v in range(start,end): ... yield v ... >>> gen = g(1,4) >>> gen.next() 1 >>> gen.next() 2 >>> gen.next() 3 >>> gen.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration