Python 機器學習基礎(一)——Python 篇

NO IMAGE

本文是 Python 機器學習基礎系列文章的第一篇——Python 篇。

Python

任何一門程式語言,入門學習的基礎知識包括:資料型別、控制流、函式、模組化、類,以及一些常用的零碎語法。Python 亦不例外。

資料型別

基本資料型別

包括布林型(bool)、整型(int)、長整型(long)、浮點型(float)、複數(complex)五種。

  • 內建常量:False, True, None, NotImplemented, Ellipsis, __debug__

  • 布林型(bool):只有兩個值 TrueFalse,每個 Python 物件都天生具有布林值,None, False, 0, 0L, 0.0, 0.0 0.0j, '', [], (), {} 值為 False,自定義類在方法 __nonzero__()__len__() 中返回布林值,此外其他物件的布林值都為 True,在數值運算中 TrueFalse 值為 10,布林操作包括 and, or, not, !=, is, is not

  • 整型(int):等價於 C 語言中的 long 型別,與系統能表示的最大整型一致,包括十進位制、八進位制(數字 0 開頭)、十六進位制(0x0X 開頭),可通過 bin, oct, hex 轉換進位制,可進行 /, //, ** 等運算

  • 長整型(long):是整型的超集,可以表示無限大的整數,在整數後加 Ll 來表示如 a = 999 ** 8, pow(2L, 5L)

  • 浮點型(float):等價於 C 中的 double,其精度資訊和內部表示可以從 sys.float_info 中獲得,可以用十進位制或科學計數法(1e-2 等)表示

  • 複數(complex):c = 08.333-1.47j, c.real, c.imag, c.conjugate(), abs(c)

容器

容器是資料的集合。Python 的容器,除字串(string)外,主要包括四種:元組(tuple),列表(list),字典(dict)和集合(set)。其中,字典和集合是無序容器,而元組、列表和字串都是有序容器,支援索引(index)和切片(slicing)操作。此外,字串、元組和集合是不可變容器,而列表和字典是可變容器。不可變容器通常比可變容器(如 list)更為高效。

下面介紹這五種容器:

  • 字串(str):使用單引號('hello')、雙引號("hello")或三引號('''hello''')定義。三引號可定義多行字串。包含 startswith 等方法。

  • 元組(tuple):圓括號與逗號分隔定義(也可以不帶括號),如 t = (0, "two", 3.0, "four", (5,6))。必須在定義時初始化且內容和長度不可變。0 個元素的元組這麼定義 a = (),1 個元素的元組這麼定義 a = (2 ,),元素可以是不同型別。tuple 也常用來打包一組變數如 t = (i, s, f), ii, ss, ff = tprint 函式 % 後的部分其實就是元組,如 print '%s is %d years old' % (name, age)

  • 列表(list):方括號與逗號分隔定義,如 l = [1, (2,3), "hello"]。也可以通過 list 函式來構造,此時需要傳入一個元組或字串,如 l = list((1,2,3)), a = list("hello")。list 與 tuple 的區別在於其內容和長短可變,通過 append 來新增元素,通過 del 命令刪除,如 del l[0]。list 包含 append, sort, pop, count, reverse 等成員函式。

  • 字典(dict):花括號與逗號分隔來定義,如 d = { 'name': 'John', 'age': 18, 'feat': (1,3,'tall')},每個字典項用冒號隔開 key 與 value。可用索引操作符 [] 來定址一個鍵,或者對其賦值,也可用 del d['key'] 將某項刪除。

  • 集合(set):集合是無序不重複元素集,通過 set() 函式來定義,傳入一個字串、元組、列表或字典,如 s = set([1, 2, "hello"]),傳入字串時構建不重複字符集合,傳入字典時預設用 keys 構建集合。集合支援並集(|)、交集(&)、差集(-)、對稱差集(^)運算子,包含 add, update, remove, issubset, isuperset, copy, clear 等子函式。

索引與切片

  • 索引(index):序列容器(tuple, list, string, unicoe, frozenset)都可以通過索引操作符 [] 來存取元素。索引從 0 開始,也可以是負數,表示倒數第幾個元素。如 t[0], t[-1], t[-2] 分別表示第一個、最後一個與倒數第二個元素。

  • 切片(slice):切片是同時取容器內某個索引範圍的多個元素。形式為 t[start:end:step],注意 end結束位置索引 1,如 t[0:-3:2]。可以省略三個值裡的一個或多個,如 a[2:7], l[0::2], t[:-1] 等。

  • 連線:序列容器(string, tuple, list)的加法( )表示序列的連線而非容器求和。如 a = (1, 2, 3) (4, 5, 6)a 的值為 (1, 2, 3, 4, 5, 6)。序列容器的乘法(*,只能乘以非負整數)表示重複多次。此外,可以用 extend 成員函式來連線兩個序列容器。

其他語法

強制型別轉換(工廠函式)

bool(5.0), int(5.0), complex(1, 2.5), float(5), long(5.0), str(1.24)

自動型別轉換

  • 運算元有一個是複數,另一個也被轉成複數
  • 否則,有一個是浮點數,另一個也被轉換成浮點數
  • 否則,如果有一個是長整數,另一個也被轉換成長整數
  • 否則,兩者必然都是整型,無需轉換
  • coerce(x, y) 函式:將變數 xy 按照上述規則轉換,並返回轉換後的 xy 組成的元組

輔助函式

len() 可獲得元組、列表等的長度,type() 可獲得變數型別,help() 可獲取函式幫助,id() 可檢視物件或函式的唯一識別符號

表示式與控制流

運算子

同樣有 , -, *, /,此外 ** 表示冪,// 表示取整除(只取商的整數部分),% 求餘數,~ 表示按位翻轉,!= 表示不等於,位運算子有 &, |, ^, ~, <<, >> 等,not, andor 分別表示布林“與”、“非”、“或”。

in, not in 是成員運算子,用於成員測試,is, is not 是標識運算子,用於同一性測試,

控制流

控制流改變語句的執行順序。包括條件(if-elif-else)、迴圈(while-else, for-in-else)、異常(try-except-finally)和上下文管理器(with)。

Python 用 : 操作符來新開一個語句塊,並且新的語句塊通過縮排和結束縮排來表示語句塊的開始和結束。if, for, while, else, try, except, def 等都是如此。

條件語句

條件語句 if 涉及的關鍵詞包括:elif, else, pass, continue, break。其中 pass 表示不需要任何操作。

if not x: pass
elif x > 100:
print 'big number'
else:
print 'small number'

迴圈語句

迴圈語句 while 與 for 和其他程式語言類似,不同點在於 while 和 for 後可選擇性地接 else 語句,當迴圈正常結束時會執行 else 裡的動作,而當迴圈通過 break 跳出時會跳過 else 裡的程式碼。此外 for 迴圈語句的形式為 for x in s

i = 0
while i < 10:
print i
if i % 5 == 0:
break
i  = 1
else:
print 'The else statement'
for i in range(5):
print i
for i,x in numerate(s):
print i,':',x
for x,y,z in s:
print x,y,z

異常

try:
raise RuntimeError("error")
except RuntimeError as e:
pass
except (IOError, TypeError, NameError) as e:
pass

其中 RuntimeError 是異常型別,raise 語句用於丟擲異常,在 except 中捕獲異常。如果在異常出現後必須進行某些動作,用 finally 語句,finally 裡的內容,無論異常是否發生,都會執行。此外 try 也支援 else 子句,表示當異常沒有發生時要做什麼事情。

try:
f = open('hello.txt')
except IOError as e:
pass
else:
data = f.read()
finally:
f.close()

上下文管理器

Python 中用 with 語句來表示上下文管理器,上下文管理器可以正確地管理各種資源,不需要時自動釋放資源,如檔案的開啟和關閉,執行緒鎖等等。

with open('hello.txt', 'r') as f:
f.read()
import threading
lock = threading.Lock()
with lock:
pass

執行 with obj 時,它會先執行 obj.__enter__() 函式來進入一個新的上下文,離開則呼叫 obj.__exit__(type, value, traceback) 方法。該方法返回一個布林值,指示被引發的異常是否得到處理。

with obj 後接受一個可選的 as var 說明符,obj.__enter()__ 的返回值將儲存在 var 中。有了這些,我們可以自己定義一個可以 with 的物件:

class MyList():
def __enter__(self):
self.theList = list()
return self.theList
def __exit__(self, type, value, tb):
for i in self.theList:
print i
return False
with MyList() as l:
l.append(10)
l.append('woman')
l.append('man')

零碎語法

  • 註釋:單行註釋用 # your comment,多行註釋用 ''' your comments '''
  • Python 裡的所有賦值只會複製其引用而非物件本身,修改會相互影響(注意重新賦值不算修改)。要完整複製,必須使用切片或 copy 成員函式操作符來拷貝
  • print 語句末尾新增逗號 , 可以阻止換行
  • c = [i for i in a] 可以轉換為一個列表,c = {i for i in a} 可以轉換為一個集合
  • 使用繼續字元(\)可以在多行分割語句
  • Python 可以用 A <= x <= B 來比較變數範圍
  • Python 支援多點賦值:x, y, z = 15, 10, 7,因此交換兩個變數可以使用 x, y = y, x
  • a == b 用於比較兩個變數值是否相等,a is b 用於檢查兩個名字是否引用同一個物件
  • print 函式可接收多個引數,會自動在輸入之間加上空格
  • del 任意一個物件時,實際上是刪除了其對物件的引用,僅當引用計數為零時才真正刪除指向的資料
  • dir() 函式可以列出當前名稱空間下的所有變數和函式
  • dir 函式可以列出一個模組中的所有子模組,如 import math; dir(math)
  • a[::-1] 形式的索引可以反轉一個序列容器

函式

普通函式

通過 def 來定義,如:

def printme( str ):
print str
return
printme('hello')

按引用傳遞引數

所有引數在 Python 裡都是按引用傳遞。如果你在函式裡修改了引數(注意重新賦值不算修改,因為重新賦值其實會刪除原始引用,並新建指向新值的引用),原始引數也會被改變。如:

def changeme( mylist ):
mylist.append([1,2,3,4])
print mylist
return
mylist = [10,20,30]
changeme( mylist )
print mylist

會輸出 [10,20,30,[1,2,3,4]]

引數型別

包括必備引數、關鍵字引數、預設(預設)引數和不定長引數四種。

def printinfo(name, age = 35):
print "Name:", name
print "Age:", age
return
printinfo(50, 'chris')
printinfo(age=50, name='chris')

其中 name 是必備引數,age 是預設引數,printinfo(age=50, name='chris') 是以關鍵字方式傳參。

不定長引數定義方式如下:

def functionname([formal_args,] *var_args_tuple ):
code_here
return [expression]

加了星號(*)的變數名會存放所有未命名的變數引數。如:

def printstr(arg1, *vartuple):
print arg1
for var in vartuple:
print var
return
printstr(10)

return 語句

函式最後用 return [expression] 形式(選擇性地)返回一個或多個結果。如:

def maxmin(l):
return max(l), min(l)

返回多個結果時,實際上是把多個結果打包在一個元組裡返回。

匿名函式

Python 使用 lambda 表示式來建立匿名函式。

lambda 表示式實際上用於定義一個閉包,有自己的名稱空間,切不能訪問自有引數以外的全域性引數。lambda 只是一個表示式,函式體比 def 簡單很多,只能在其中封裝有限的邏輯進去。lambda 函式定義形式如下:

lambda [arg1 [,arg2,...,argn]]:expression

如下例項:

sum = lambda arg1, arg2: arg1   arg2;
print "10   20 = ", sum(arg1, arg2) 

模組

C 語言中想要呼叫 sqrt 函式時必須通過 #include <math.h> 引入相關標頭檔案。同樣,在 Python 中,如果需要引用一些內建或寫在其它檔案裡的函式,需要用到一個概念叫做模組(module)。Python 通過 import 關鍵字來引入模組。

import math
print math.sqrt(2)

呼叫模組中的函式時,必須通過 模組名.函式名 的形式。如果只需要用到某幾個函式,可以通過 from 模組名 import 函式1, 函式2 的形式。當多個模組包含同一個函式時,後引用的模組會覆蓋掉前面的模組。

在 Python 中,每個 Python 檔案都可以作為一個模組,模組名就是檔名。在 Python 引入模組時實際上是將對應檔案中的程式碼執行一遍(僅在第一次引入時才會執行一遍)。

我們可以測試一下。新建一個 test.py 檔案,包含如下程式碼:

# test.py
def display():
print 'hello python'
display()

再編寫一個 test1.py 檔案,輸入:

import test
import test

會發現輸出 hello python,且僅輸出一次。所以首次 import 一個模組時實際上是執行一遍對應檔案中的程式碼。

Pythohn 在設計之初就定義為一門物件導向的語言。正因如此,在 Python 中建立一個類或物件是非常容易的事情。

Python 物件導向技術的關鍵詞有:

  • 類(class):具有相同屬性方法的物件集合。類定義了集合中每個物件所共有的屬性和方法,物件是類的例項。
  • 類變數:類變數獨立於物件存在,在不同例項中是公用的。類變數通常不作為例項變數使用。
  • 例項變數:定義在方法之內,只作用於當前例項。
  • 資料成員:類變數或者例項變數,也叫欄位,用於儲存類或例項物件的相關資料。
  • 方法:類中定義的函式。
  • 例項化:建立一個類的例項,類的具體物件。
  • 物件:通過類定義的資料結構例項,物件包括類變數和例項變數以及方法。
  • 繼承:即一個派生類(derived class)繼承基類(basis class)的欄位和方法。
  • 方法重寫:如果從父類繼承的方法不能滿足子類需求,可以在子類中對其進行改寫,這個過程叫方法的覆蓋(override),也叫重寫。

類的定義

通常你需要在單獨的檔案中定義一個類。

class Employee:
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount  = 1
def displayCount(self):
print 'Total employee %d' % Employee.empCount
def displayEmployee(self):
print 'Name :", self.name, ", Salary :", self.salary
def __del__(self):
class_name = self.__class__.__name__
print class_name, "is destroyed"
emp = Employee("Chris", 10000)

上例中通過 class 關鍵字定義了一個類 Employee,其中 empCount 就是類變數,在所有物件之間共享,而 name 和 salary 就是例項變數,為某個例項獨有。__init__ 是 Python 的一種特殊方法,叫建構函式或初始化方法,在建立例項時會呼叫。而 __del__ 是解構函式,在物件被銷燬時被呼叫。

注意上述所有方法的第一個引數都是 self,表示定義類內方法時都需要把例項本身的引用傳進來。

你可以新增、修改或刪除類的屬性:

emp.school = 'BIT'
emp.age = 7
del emp.age

也可以通過如下函式來訪問或檢查例項的屬性:

hasattr(emp, 'age')
getattr(emp, 'age')
setattr(emp, 'age', 8)
delattr(emp, 'age')

Python 的內建類屬性有:

  • __name__:類名
  • __doc__:類的文件,即類定義下方的註釋
  • __dict__:類和例項都有,包含一個字典,由類的資料屬性組成
  • __module__:類定義所在的模組
  • __bases__:類的所有父類組成的元組

Python 垃圾回收

與 Java 語言一樣,Python 使用了引用計數這一簡單規則來追蹤記憶體中的物件。

a = 40    # 建立物件 <40>
b = a     # 增加對 <40> 引用的計數,為 2
c = [b]   # 增加對 <40> 引用的計數,為 3
del a     # 減少對 <40> 引用的計數,為 2
b = 100   # 減少對 <40> 引用的計數,為 1
c[0] = -1 # 減少對 <40> 引用的計數,為 0,刪除資料

除引用計數以外,Python 還會處理迴圈引用的情況。如兩個物件相互引用,但沒有其他變數引用它們,這種情況也需要清理,但僅僅引用計數是不夠的。Python 的垃圾回收機制實際上是一個引用計數和一個迴圈垃圾收集器。

類的繼承

Python 中繼承的一些特點:

  • 子類不會自動呼叫基類的建構函式(__init__),需要顯式地呼叫。
  • 呼叫基類的方法時,需要加上基類的類名字首,且要加上 self 引數,區別於類中呼叫普通函式時並不需要帶上 self 引數。
  • 現在本類中查詢方法,找不到再去基類中找。

語法:

class SubClassName (ParentClass1[, ParentClass2, ...]):
'Optional class documentation string'
class_suite

直接在子類中定義與父類同名的函式即可重寫方法。一些通用的功能包括 __init__, __del__, __repr__, __str__(轉化為適合人閱讀的形式), __cmp__(物件比較)可以在自己的類中重寫,通過重寫 __add__, __len__, __lt__, __eq__, __iter__ 等可以實現運算子的過載。

私有屬性與方法

Python 類中的資料成員與方法預設為公有。通過在欄位或方法名前新增兩個下劃線如 __var 來定義私有屬性或方法。如下例項:

class Counter:
__secretCount = 0   # 私有變數
publicCount = 0     # 公開變數
def count(self):
self.__secretCount  = 1
self.publicCount  = 1
print self.__secretCount
counter = Counter()
counter.count()
counter.count()
print counter.publicCount
print counter.__secretCount   # 報錯,例項不能訪問私有變數

但是,你可以使用 object.__className__attrName 訪問屬性(我靠,這設計,真奇葩!),如:

print counter._Counter__secretCount

裝飾器

Python 特點

Python 與其他語言資料型別的比較

程式語言之間比較:

  • 靜態型別語言
    一種在編譯期間就確定資料型別的語言。大多數靜態型別語言是通過要求在使用任一變數之前宣告其資料型別來保證這一點的。Java 和 C 是靜態型別語言。

  • 動態型別語言
    一種在執行期間才去確定資料型別的語言,與靜態型別相反。VBScript 和 Python 是動態型別的,因為它們確定一個變數的型別是在您第一次給它賦值的時候。

  • 強型別語言
    一種總是強制型別定義的語言。Java 和 Python 是強制型別定義的。您有一個整數,如果不明確地進行轉換 ,不能將把它當成一個字串。

  • 弱型別語言
    一種型別可以被忽略的語言,與強型別相反。VBScript 是弱型別的。在 VBScript 中,您可以將字串 ‘12’ 和整數 3 進行連線得到字串’123’,然後可以把它看成整數 123 ,所有這些都不需要任何的顯示轉換。

所以說 Python 既是動態型別語言 (因為它不使用顯示資料型別宣告),又是強型別語言 (因為只要一個變數獲得了一個資料型別,它實際上就一直是這個型別了)。(本段摘自這裡