University of London / MSc Computer Science: Principles of programming(前半)

University of London / MSc Computer Science: Principles of programming(前半)

May 22, 2023

ロンドン大学で MSc Computer Science: Principles of programming モジュールを履修中。

講義内容に関して記録した個人的なスタディノートです。

全 12 週のうち 1〜5 週目の内容を記録します。(1 週目開始:2023 年 4 月 17 日 / 5 週目終了:2023 年 5 月 21 日)

モジュール概要 #

Content

This module introduces programming concepts and techniques, as well as elementary software development principles. Both for absolute beginners and for those with prior programming experience, the module introduces the fundamentals of programming, including: variables and assignment, primitive and complex types, methods, control structures, collections, iteration, recursion, as well as classes and objects in object-oriented programming. The module also introduces basic software development issues such as design, testing, debugging.

Aims

The primary aims of this module are:

  • to provide the student with a comprehensive grounding in programming.
  • familiarise students with a modern programming language such as Python.

Weeks

講義は Week 10 まで。Week 11, 12 は最終課題を作成する期間。

  • Week 1: Just enough Python to get started
  • Week 2: Program control structures
  • Week 3: Collection
  • Week 4: Memory and references
  • Week 5: Files, modules, exceptions and type hints
  • Week 6: Software development principles
  • Week 7: More on object-oriented programming
  • Week 8: More on object-oriented programming
  • Week 9: Functional programming
  • Week 10: Recursion
  • Week 11: Preparation for final assessment
  • Week 12: Preparation for final assessment

参考文書 #

Essential reading

  • Adapted readings from Downey, A.B. “Think Python: how to think like a computer scientist”. (Needham, MA: Green Tea Press, 2015) 2nd edition.
  • Lubanovic, B. “Introducing Python”. (Sebastopol, CA: O’Reilly Media, Inc, 2019) 2nd edition.
  • Lutz, M. “Learning Python”. (Sebastopol, CA: O’Reilly Media, Inc, 2013) 5th edition.

Further reading

  • Horstmann, C. and R. Necaise. Python for everyone. (Hoboken, NJ: John Wiley & Sons, 2018) 3rd edition.

Week 1: Just enough Python to get started #

データ型、演算子、変数、if 文による分岐処理を学習。

(基礎的な内容だったため特に記録すべきことなし)

Week 2: Program control structures #

ループ処理と関数定義(変数のスコープの概念など含む)を学習。

(基礎的な内容だったため特に記録すべきことなし)

Week 3: Collection #

リスト型データを用いた処理(List, Set, Tuple, Dictionary, and String)、オブジェクトとメソッド、ミュータブルとイミュータブルについて学習。

リスト型データ #

  • List: Value だけを持つ(Key にはインデックスが使われる)。重複した要素もその数だけ持つ。順番を持つ。ミュータブル。
  • Set: Value だけを持つ。順番を持たない。重複した要素は持たない。ミュータブル。
  • Dictionary: Key/Value のペアを持つ。順番を持たない。Key が重複した要素は持たない重複した要素もその数だけ持つ。順番を持つ。ミュータブル。
  • Tuple: Value だけを持つ(Key にはインデックスが使われる)。重複した要素もその数だけ持つ。順番を持つ。イミュータブル。
"""LIST"""
my_lst = ['alice', 'bob', 'charlie']
empty_lst1 = []
empty_lst2 = list()

"""SET"""
my_set = {'alice', 'bob', 'charlie'}
empty_set = set()
# CAUTION: just writing {} create empty dictionary.

"""DICTIONARY"""
my_dict = {'alice': 20, 'bob': 30, 'charlie': 40}
empty_dict1 = {}
empty_dict2 = dict()

"""TUPLE"""
my_tpl1 = ('alice', 'bob', 'charlie', 'alice')
my_tpl2 = 'alice', 'bob', 'charlie', 'alice' # is short hand. () can be omitted.
empty_tpl1 = ()
empty_tpl2 = tuple()

my_single_str1 = 'alice'
my_single_str2 = ('alice')
type(my_single_str1) # Of course this is a String 'alice'.
type(my_single_str2) # CAUTION: This is not a Tuple but also a String 'alice'.

my_single_tpl1 = 'alice', # <- add ',' to the end.
my_single_tpl2 = ('alice',) # <- add ',' to the end.
type(my_single_tpl1) # is Tuple ('alice').
type(my_single_tpl2) # is Tuple ('alice').

List Operations

l = [0, 10, 20, 30, 40, 50]

print(l) # [0, 10, 20, 30, 40, 50]
print(l[2:4]) # [20, 30]
print(l[2:]) # [20, 30, 40, 50]
print(l[:4]) # [0, 10, 20, 30]
print(l[:]) # [0, 10, 20, 30, 40, 50]

l[2:4] = ['a', 'b']
print(l) # [0, 10, 'a', 'b', 40, 50]

print('a' in l) # True
del l[2]
print(l) # [0, 10, 'b', 40, 50]
print('a' in l) # False

Tuple assignment

x, y = 1, 2

"""REAL USE CASE"""

b, a = a, b # Swap a and b

name, domain = 'hello@world.com'.split('@')

quotient, remainder = divmod(7, 3)

def min_max(t):
  return min(t), max(t)

result = min_max(42, 99, 256, 1, 72)

Gather/Scatter tuple argument

# A parameter name that begins with * gathers arguments into a tuple.
def printall(*args):
  print(args)

# To scatter tuple to multiple arguments, use the * operator.
t = (7, 3)
divmod(t) # TypeError: divmod expected 2 arguments, got 1
divmod(*t) # (2, 1)

Dictionary

It is common to use tuples as keys in dictionaries (primarily because you can’t use lists). For example, a telephone directory might map from last-name, first-name pairs to telephone numbers. Assuming that we have defined last, first and number, we could write:

directory[last, first] = number

The expression in brackets is a tuple. We could use tuple assignment to traverse this dictionary.

for last, first in directory:
  print(first, last, directory[last,first])

その他 #

等価比較

リスト型データに対する等価比較は、型、長さ、要素が等価であれば True を返す(JavaScript とは大きく異なる点)。

オブジェクトの同一性を比較したいときは is 演算子を使う。 オブジェクトの同一性は id() 関数を使って判定される。

print([1, 2] == [1, 2]) # True

print([1, 2] == [1.0, 2.0]) # True <- こちらも True

print([1, 2] == [2, 1]) # False <- 順番が異なるので False

print([1, 2] is [1, 2]) # False

print((1, 2) == (1, 2)) # True

print((1, 2) == (2, 1)) # False

print({1, 2} == {2, 1}) # True <- 順番の概念がないため True

print({'a': 1, 'b': 2} == {'b': 2, 'a': 1}) # True <- 順番の概念がないため True

Week 4: Memory and references #

プログラム実行時のメモリの動きを視覚化するには次のサイトが良いとのこと。

https://pythontutor.com/

データ型とメモリ(スタック、ヒープ) #

データ型によってメモリ上の保存場所が異なる。

  • 単純なデータ型(Integer, String, Float, Boolean)はスタックに値が直接保管される。
  • 複雑なデータ型(List, Dictionary, Set, Class)はヒープに値が保管され、スタックにはヒープへのポインタ(参照)が保管される。

これは Python での話であり、プログラミング言語によって異なるので注意。例えば Java では String は後者に分類される。また Java においては、単純なデータ型はプリミティブ型、複雑なデータ型はオブジェクトそしてクラスと呼ばれる。

エイリアス、コピー、ディープ・コピー #

エイリアス

b = 10
c = b
b = 5

print(c) # 10
b = ['MacBook', 'Toaster', 'Toilet Paper']
c = b
b[0] = 'PC'

print(c) # ['PC', 'Toaster', 'Toilet Paper']

コピー

b = ['MacBook', 'Toaster', 'Toilet Paper']
c = b[:]
b[0] = 'PC'

print(c) # ['MacBook', 'Toaster', 'Toilet Paper']

スタンダードライブラリの copy を使っても同じことができる。

import copy

b = ['MacBook', 'Toaster', 'Toilet Paper']
c = copy.copy(b)
b[0] = 'PC'

print(c) # ['MacBook', 'Toaster', 'Toilet Paper']

ディープ・コピー

import copy

m = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
n = copy.copy(m) # Shallow Copy

m[0][0] = ['a']

# .copy は Shallow Copy のためネストされた第2層以下は引き続き同じものを参照している
print(m) # [[['a'], 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
print(n) # [[['a'], 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
import copy

m = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
n = copy.deepcopy(m) # Deep Copy

m[0][0] = ['a']

# .deepcopy は Deep のためネストされている場合でもすべてを値としてコピーする
print(m) # [[['a'], 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
print(n) # [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]

メモリの使用量は エイリアス < コピー < ディープ・コピー の順に増加する。加えて copy() やそれ以上に重い deepcopy() メソッドの処理速度を考慮すると、効率面からは「1.エイリアス、2.コピー、3.ディープ・コピー」の順で使用を検討すべき。つまりコピーの必要がなければ素直にエイリアスを使うべき。

関数とメモリ #

値渡し

def reduce_by_1(n):
  n = n - 1

a = 5
reduce_by_1(a)
print(a) # 5

参照渡し

def reduce_by_1(pair):
  pair[0] = pair[0] - 1
  pair[1] = pair[1] - 1

a = [5, 20]
reduce_by_1(a)
print(a) # [4, 19]

その他 #

リストでの * 演算子の注意事項

オブジェクトを要素とするリストでの * 演算子は同じオブジェクトへの参照の複製となるため注意。それぞれを個別のオブジェクトとするには、例えばリスト内包表記を使用すると良い。

l1 = [[1, 2, 3]] * 3
print(l1) # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
l1[0][0] = 'a'
print(l1) # [['a', 2, 3], ['a', 2, 3], ['a', 2, 3]]

'''リスト内包表記(List Comprehensions)'''
l2 = [[1, 2, 3] for i in range(3)]
print(l2) # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
l2[0][0] = 'b'
print(l2) # [['b', 2, 3], [1, 2, 3], [1, 2, 3]]

'''リスト内包表記は以下を1行で書いているのと同じ'''
l3 = []
for i in range(3):
  l3.append([1, 2, 3])
print(l3) # [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
l3[0][0] = 'c'
print(l3) # [['c', 2, 3], [1, 2, 3], [1, 2, 3]]

Week 5: Files, modules, exceptions and type hints #

モジュール #

module 型オブジェクトとしてインポート

import math

math.ceil(1.23)

特定の関数や変数をインポート

from math import ceil

ceil(1.23)

すべての関数や変数をインポート(この書き方は使わないほうが良い気がする)

from math import *

ceil(1.23)

別名でインポートしたい場合

import math as m

m.ceil(1.23)

例外 #

例外をキャッチする

try:
  print(1 / 0) # 0 除算
except:
  print('Something is wrong.')
try:
  print(1 / 0) # 0 除算
except Exception as e:
  print('Something is wrong. The error is', e)
try:
  print(1 / 0) # 0 除算
except ZeroDivisionError:
  print('ZeroDivisionError is happened.')

例外を投げる

def my_divide(m, n):
  if n == 0:
    raise Exception
  return m / n

タイプヒント(型ヒント) #

https://docs.python.org/ja/3/library/typing.html

def greet(name: str) -> str:
  return 'Hello ' + name

def print_number(n: int) -> None:
  print(n)

def double_number(n: int) -> int:
  m : int
  m = 2*n
  return m

def concatenate_list(l1: list, l2: list) -> list:
  return l1 + l2

def concatenate_integer_list(l1: list[int], l2: list[int]) -> list[int]:
  return l1 + l2

my_dict: dict[str, float] = {'field': 2.0}

my_ fixed_size_tuple: tuple[int, str, float] = (3, 'yes', 7.5)

my_variable_size_tuple: tuple[int, ...] = (1, 2, 3)

my_nested_tuple: tuple[int, dict[str, integer]] = (2, {'Mon': 5, 'Tue': 6, 'Wed': 7, 'Thu': 8, 'Fri': 9, 'Sat': 10, 'Sun': 11})
# type aliases
NumberedWeek = tuple[int, dict[str, integer]]

second_week: NumberedWeek = (2, {'Mon': 5, 'Tue': 6, 'Wed': 7, 'Thu': 8, 'Fri': 9, 'Sat': 10, 'Sun': 11})