Python复习 基础知识
什么是動態語言呢? 可以簡單的理解為直接被解釋執行的語言稱為動態語言。
而需要編譯環境將程序轉換為其它的編碼再執行的語言稱為靜態語言。
當前靜態語言有:java、C/C++、C#、DELPHI、VB等。
動態語言有:asp、php、cgi、lisp、Perl、python,Smalltalk、Ruby等。
?
數據類型:?
dict:無序,可變,大括號,唯一內置的映射類型,字典是作為哈希表(支持快速檢索的數據結構)來實現的。
tuple:有序,不可變,有in,無append、extend;無remove、pop;無index
list:有序,可變,小括號
?
字典 dict:?
2.7文檔:https://docs.python.org/2/library/stdtypes.html#mapping-types-dict
key,鍵必須獨一無二,可以hash,支持 __hash__方法。list無 __hash__ 方法,所以不能作為key.
values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.
Python中可哈希的對象有:
- 數值、字符串,以及只含有數值或字符串的元組
- 用戶自定義類的實例(默認是可哈希的,也可以通過實現__hash__()和__cmp__()來修改默認行為)
value,值可以取任何數據類型,但必須是不可變的,如字符串,數或元組。
dict:和list比較有以下幾個特點:
list:
所以,dict是用空間來換取時間的一種方法。
tuple:
用 Tuple 的好處
Tuple 比 list 操作速度快.如果您定義了一個值的常量集,并且唯一要用它做的是不斷地遍歷它,請使用 tuple 代替 list.
如果對不需要修改的數據進行 “寫保護”,可以使代碼更安全.使用 tuple 而不是 list 如同擁有一個隱含的 assert 語句,說明這一數據是常量.如果必須要改變這些值,則需要執行 tuple 到 list 的轉換
Tuple 與 list 的轉換
Tuple 可以轉換成 list,反之亦然.內置的 tuple 函數接收一個 list,并返回一個有著相同元素的 tuple
而 list 函數接收一個 tuple 返回一個 list.從效果上看,tuple 凍結一個 list,而 list 解凍一個 tuple.
Tuple 的其他應用
一次賦多值
>>> v = ('a','b','e')
>>> (x,y,z) = v
?
參數傳值
函數:Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去:
參數組合:
參數定義的順序必須是:必選參數、默認參數、可變參數和關鍵字參數。
Python的函數具有非常靈活的參數形態,既可以實現簡單的調用,又可以傳入非常復雜的參數。
默認參數一定要用不可變對象,如果是可變對象,運行會有邏輯錯誤!
要注意定義可變參數和關鍵字參數的語法:
*args是可變參數,args接收的是一個tuple;
**kw是關鍵字參數,kw接收的是一個dict。
以及調用函數時如何傳入可變參數和關鍵字參數的語法:
可變參數既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再通過*args傳入:func(*(1, 2, 3));
關鍵字參數既可以直接傳入:func(a=1, b=2),又可以先組裝dict,再通過**kw傳入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的習慣寫法,當然也可以用其他參數名,但最好使用習慣用法。
?
遞歸函數
防止棧溢出
解決遞歸調用棧溢出的方法是通過尾遞歸優化
def fact(n):return fact_iter(n, 1)def fact_iter(num, product):if num == 1:return productreturn fact_iter(num - 1, num * product)可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數本身,num - 1和num * product在函數調用前就會被計算,不影響函數調用。
尾遞歸事實上和循環是等價的,沒有循環語句的編程語言只能通過尾遞歸實現循環。
切片
L[0:3] //若第一個索引是0,還可以省略 L[:3]//取倒數第一個元素
L[-1]
迭代
dict默認迭代是key
可以用 for value in d.itervalues()
若要同時迭代 key和value
可以用 for k,v in d.iteritems()
判斷一個對象是否是可迭代對象(返回BOOL類型):isinstance('abc', Iterable)?
?
列表生成式(List Comprehensions)
[x*x for x in range(1,11)if x % 2 == 0][4,16,36,64,100]
[m + n for m in 'ABC' for n in 'XYZ']['AX','AY','AZ','BX','BY','BZ','CX','CY','CZ']
?
生成器(generator)
一邊循環一邊計算,把列表生成器 的[] 改為 ()
generator 保存的是算法
斐波那契數列 Fibonacci
遞歸
def fib2(n):if n == 0:return 0elif n ==1:return 1return fib2(n-1) + fib2(n-2)print(fib2(7))memo = {0:0, 1:1} def fib3(n):if not n in memo:memo[n] = fib3(n-1) + fib3(n-2)return memo[n]print(fib3(7))(迭代解法)
def fib(max):n, a, b = 0, 0, 1while n < max:print ba, b = b, a+bn = n + 1generator :
def fib(max):n, a, b = 0, 0, 1while n < max:yield b a, b = b, a + bn = n+1與函數區別:
函數是順序執行,遇到return或最后就返回。而變成generator的函數后,在調用 next()的時候執行,遇到 yield 語句 返回,再次執行時從上次返回的yield 語句處繼續執行
?
函數式編程(Functional Programming)
函數式編程的一個特點就是,允許把函數本身作為參數傳入另一個函數,還允許返回一個函數!? Python對函數式編程提供部分支持。由于Python允許使用變量,因此,Python不是純函數式編程語言。
?
高階函數(Higher -order function)
1,變量可以指向函數
2,函數名也是變量
3,傳入函數
函數可以接受另一個函數作為參數,稱為高階函數
map / reduce 函數
map(),高階函數,接收兩個參數,一個是函數,一個是序列,map將傳入的函數依次作用到序列的每個元素,并把結果作為新的list返回。
將list所有數字轉為字符串
>>>map(str, [1,2,3,4,5,6,7,8,9]) ['1', '2', '3', '4', '5', '6', '7', '8', '9']reduce 必須接收兩個參數
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 利用 map() 函數,把用戶輸入的不規范的英文名字,變為首字母大寫,其他小寫的規范名字。輸 入: ['adam', 'LISA', 'barT'] ,輸出: ['Adam', 'Lisa', 'Bart'] 。Python提供的 sum() 函數可以接受一個list并求和,請編寫一個 prod() 函數,可以接受一個list并利用 reduce() 求 積。
?
def capi(w):return w.capitalize() print map(capi,L)filter:
def is_odd(n):return n % 2 == 1filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]) # 結果: [1, 5, 9, 15]sorted:
高階函數,可以接受一個比較函數來實現自定義的排序。
比如倒序函數:
def rebversed_cmp(x, y):if x > y:return -1if x < y:return 1return 0傳入自定義的比較函數 reversed_cmp,就可以實現倒序排序:
sorted([36, 5, 12, 9, 21], reversed_cmp)?
返回函數 ,閉包
注意到返回的函數在其定義內部引用了局部變量args。
所以,當一個函數返回了一個函數后,其內部的局部變量還被新函數引用,所以,閉包用起來簡單,實現起來可不容易。
另一個需要注意的問題是,返回的函數并沒有立刻執行,而是直到調用了f()才執行。我們來看一個例子:
def count():fs = []for i in range(1, 4):def f():return i*ifs.append(f)return fs f1, f2, f3 = count()在上面的例子中,每次循環,都創建了一個新的函數,然后,把創建的3個函數都返回了。
你可能認為調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
>>> f1() 9 >>> f2() 9 >>> f3() 9全部都是9!(即都循環了三次)。原因就在于返回的函數引用了變量i,但它并非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,因此最終結果為9。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者后續會發生變化的變量。
如果一定要引用循環變量怎么辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變:
>>> def count(): ... fs = [] ... for i in range(1, 4): ... def f(j): ... def g(): ... return j*j ... return g ... fs.append(f(i)) ... return fs ... >>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9缺點是代碼較長,可利用lambda函數縮短代碼。:
f1, f2 = [(lambda j = i : j ** 2) for i in range(1, 3)]?for后面的i 是個全局變量,在傳遞進函數參數時已經被作為某個數字了,分解開就是
f1() = lambda j = 1 : j**2
f2() = lambda j = 2 : j**2
?
lambda:匿名函數
將一個List里的每個元素都平方,
map(lambda x: x*x, [y for y in range(10)])?
裝飾器:
?將函數賦值給變量
?函數對象有一個 __name__ 屬性 ,可以拿到函數的名字
?在代碼運行期間 動態 增加功能的方式,稱之為“裝飾器”(Decorator)
def log(func):def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper@log def now()print '123'調用now()函數
>>>now()call now():123 @log def now()print '123' 相當于: now = log(now)log()是一個decorator,所以,原來的now()函數仍然存在,只是現在同名的now變量指向了新的函數,于是調用now()將執行新函數,即在log()函數中返回的wrapper()函數。
wrapper()函數的參數定義是(*args, **kw),因此,wrapper()函數可以接受任意參數的調用。在wrapper()函數內,首先打印日志,再緊接著調用原始函數。
?
在面向對象(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數實現,也可以用類實現。
decorator可以增強函數的功能,定義起來雖然有點復雜,但使用起來非常靈活和方便。
?
偏函數(partial function)
屬于 functools 模塊里邊。把一個函數的某些參數給固定住(即設置默認值),返回一個新的函數。
>>> import functools >>> int2 = functools.partial(int, base = 2) >>> int2('1000000') 64 >>> int('1010101') 85?相當于:
kw = {base:2)
int('10010', **kw)
?
模塊:
一個 .py 文件就稱之為一個模塊(module)
#!/usr/bin/env python # -*- coding:utf-8 -*-'a test module'__author__='Michael Liao'import sysdef test():args = sys.argvif len(args)==1:print 'Hello, world!'elif len(args)==2:print 'Hello, %s!' % args[1]else:print 'Too many arguments!'if __name__=='__main__':test()第1行和第2行是標準注釋,第1行注釋可以讓這個hello.py文件直接在Unix/Linux/Mac上運行,第2行注釋表示.py文件本身使用標準UTF-8編碼;
第4行是一個字符串,表示模塊的文檔注釋,任何模塊代碼的第一個字符串都被視為模塊的文檔注釋;
第6行使用__author__變量把作者寫進去,這樣當你公開源代碼后別人就可以瞻仰你的大名;
以上就是Python模塊的標準文件模板
使用sys模塊的第一步,就是導入該模塊:import sys
sys模塊有一個argv變量,用list存儲了命令行的所有參數。argv至少有一個元素,因為第一個參數永遠是該.py文件的名稱。例如:
運行python hello.py獲得的sys.argv就是['hello.py'];
運行python hello.py Michael獲得的sys.argv就是['hello.py', 'Michael]。
if __name__=='__main__':test()當我們在命令行運行hello模塊文件時,Python解釋器把一個特殊變量__name__置為__main__,而如果在其他地方導入該hello模塊時,if判斷將失敗,因此,這種if測試可以讓一個模塊通過命令行運行時執行一些額外的代碼,最常見的就是運行測試。
?
?Python是動態語言,函數簽名一致接口就一樣,因此,無論導入哪個模塊后續代碼都能正常工作。
函數簽名就是函數的聲明信息,包括參數(個數,類型)、返回值、調用約定之類。
安裝第三方模塊:
例如圖片:
先安裝pip: sudo easy_install pip 然后安裝PIL: sudo -H pip install Pillow 最后導入Image模塊: import PIL.Image 或者 from PIL import Image
?
#!/usr/bin/python #coding:utf-8#獲取當前路徑 import os print os.getcwd()from PIL import Image im = Image.open('test.png') #打印格式、大小、類型 print im.format, im.size, im.mode #縮略圖 im.thumbnail((200,100)) im.save('thumb.jpg', 'JPEG')?
使用 __future__(python3.x 和 2.x 的區別)
1,編碼
python2.x里的字符串用'xxx'表示str,Unicode字符串用u'xxx'表示unicode
而在3.x中,所有字符串都被視為unicode,因此,寫u'xxx'和'xxx'是完全一致的,而在2.x中以'xxx'表示的str就必須寫成b'xxx',以此表示“二進制字符串”。
?為了適應Python 3.x的新的字符串的表示方法,在2.7版本的代碼中,可以通過unicode_literals來使用Python 3.x的新的語法:
?
#still running on Python 2.7from __future__ import unicode_literals print '\'xxx\' is unicode?', isinstance('xxx',unicode) print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode) print '\'xxx\' is str?', isinstance('xxx', str) print 'b\'xxx\' is str?', isinstance(b'xxx', str) 'xxx' is unicode? True u'xxx' is unicode? True 'xxx' is str? False b'xxx' is str? True2,除法
Python2.x 都是地板除
Python3.x 普通除法是精確除法。地板除用//
from __future__ import divisionprint '10 / 3 = ', 10 / 3 print '10.0 / 3 = ', 10.0 / 3 print '10 // 3 = ', 10 //310 / 3 =? 3.33333333333
10.0 / 3 =? 3.33333333333
10 // 3 =? 3
3,其他。如 Python3 中無xrange
http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/
?
?
面向對象編程
Object Oriented Programming, OOP。 數據封裝、繼承、多態
把對象作為程序的基本單元,一個對象包含了數據和操作數據段函數
OOP: 對象、消息
面向過程:一組函數的順序執行
給對象發消息實際上就是調用對象對應的關聯函數,我們稱之為對象的方法(Method)。面向對象的程序寫出來就像這樣:
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()類、實例、對象、方法
封裝
訪問限制:
要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線 __
變量名如果以 __ (兩個_) 開頭,就變成了一個私有變量 (private),? 只有內部可以訪問,外部不能訪問。
class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scoredef print_score(self):print '%s: %s' % (self.__name, self.__score)改完后,對于外部代碼來說,沒什么變動,但是已經無法從外部訪問實例變量.__name和實例變量.__score了
如果外部代碼要獲取 name 和 score 可以 給 Student 類增加 get_name 和 get_score 這樣的方法:
class Student(object):...def get_name(self):return self.__namedef get_score(self):return self.__score修改屬性,使用set
class Student(object):...def set_score(self, score):self.__score = score使用方法修改name的優點:可以對參數做檢查,避免傳入無效的參數
在Python中,以雙下劃線開頭并結尾的,是特殊變量。可以直接訪問,不是private變量。
繼承和多態
繼承:好處,獲得父類的全部功能
多態:具體調用的方法,由運行時該對象的確切類型決定。
開閉原則:
對擴展開放:允許新增Animal子類;
對修改封閉:不需要修改依賴Animal類型的run_twice()等函數。
有繼承才能有多態
?
獲取對象信息:
使用type
>>>type(123)<type 'int'>>>> type(abs)<type 'builtin_function_or_method'>>>> import types>>> type('abc') == types.StringTypeTrue使用dir(), 獲取一個對象的所有屬性和方法。
在len()內部,自動去調用該對象中的__len__()方法,下面代碼是等價的
>>> len('ABC') 3 >>> 'ABC'.__len__() 3使用 __slots__
限制Class的 屬性,只允許對Student 實例添加name和age屬性
>>> class Student(object): ... __slots__ = ('name', 'age') #用tuple定義允許綁定的屬性名稱 ...使用@property
在set_score里邊設置檢查參數
class Student(object):def get_score(self):return self._scoredef set_score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 and value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value以上復雜,可以使用 @property 裝飾器(即把函數當成參數傳入,增加方法),把一個方法變成屬性調用。
class Student(object)@property #負責把一個getter 方法變成屬性def score(self):return self._score@score.setter #負責把一個setter方法變成屬性賦值def score(self,value):def score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value>>> s = Student()
>>> s.score = 60 # OK,實際轉化為s.set_score(60)
>>> s.score # OK,實際轉化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
?
多重繼承
class Bat(Mammal, Flyable):passMixin
?非單獨的屬性,例如RunnableMixin
Python自帶了TCPServer和UDPServer這兩類網絡服務,而要同時服務多個用戶就必須使用多進程或多線程模型,這兩種模型由ForkingMixin和ThreadingMixin提供。通過組合,我們就可以創造出合適的服務來。
?
由于Python允許使用多重繼承,因此,Mixin就是一種常見的設計。
只允許單一繼承的語言(如Java)不能使用Mixin的設計。
?
定制類:
__str__ 方法
class Student (object)def __int__(self, name):self.name = namedef __str__(self):return 'Student object (name: %s)' % self.name>>> print Student('Michael')
Student object (name:Michael)
直接敲變量不用print,打印出來的實例還是不好看:
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310>這是因為直接顯示變量調用的不是__str__(),而是__repr__(),兩者的區別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發者看到的字符串,也就是說,__repr__()是為調試服務的。
解決辦法是再定義一個__repr__()。但是通常__str__()和__repr__()代碼都是一樣的,所以,有個偷懶的寫法:
__iter__ 迭代
__getitem__按照下標取出元素
__getattr__ 動態返回一個屬性
__call__
class Student(object):def __init__(self, name):self.name = namedef __call__(self):print('My name is %s.' % self.name)>>> s = Student('Michael')
>>> s()
My name is Michael.
使用元類,metaclass
可以使用 type() 動態創建類以外,還可以使用 元類
即當我們定義了類以后,就可以根據這個類創建出實例。所以先定義類,然后創建實例。
但是如果我們想創建出類呢?那就必須根據metaclass創建出類,所以:先定義metaclass,然后創建類。
?
連接起來就是:先定義metaclass,就可以創建類,最后創建實例。
?
錯誤處理:
try...except...finally
Python 所有的錯誤都是從 BaseException 類派生的
調用堆棧如果錯誤沒有被捕獲,它就會一直往上拋,最后被Python解釋器捕獲,打印一個錯誤信息,然后程序退出。
logging,可以記錄錯誤信息。打印錯誤后會繼續執行,并正常退出。
def main():try:bar('0')except StandardError, e:logging.exception(2)使用 raise 拋出錯誤。
?
調試:
使用assert
# err.pydef foo(s):n = int(s)assert n != 0, 'n is zero!'return 10/n def main():foo('0')
使用 -0 來關閉 assert
$ python -O err.py
使用 logging
使用pdb
pdb.set_trace()
# err.py import pdbs = '0' n = int(s) pdb.set_trace() # 運行到這里會自動暫停 print 10 / n運行代碼,程序會自動在pdb.set_trace()暫停并進入pdb調試環境,可以用命令p查看變量,或者用命令c繼續運行:
$ python err.py > /Users/michael/Github/sicp/err.py(7)<module>() -> print 10 / n (Pdb) p n 0 (Pdb) c Traceback (most recent call last):File "err.py", line 7, in <module>print 10 / n ZeroDivisionError: integer division or modulo by zero雖然用IDE調試起來比較方便,但是最后你會發現,logging才是終極武器。
單元測試:
測試驅動開發,TDD:Test-Driven Development
單元測試是是用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工作
編寫單元測試時,我們需要編寫一個測試類,從unittest.TestCase繼承。
運行單元測試:
$ python -m unittest mydict_test在命令行通過參數 -m unittest 直接運行單元測試
setUp與tearDown
可以在單元測試中編寫兩個特殊的setUp()和tearDown()方法。這兩個方法會分別在每調用一個測試方法的前后分別被執行。
setUp()和tearDown()方法有什么用呢?設想你的測試需要啟動一個數據庫,這時,就可以在setUp()方法中連接數據庫,在tearDown()方法中關閉數據庫,這樣,不必在每個測試方法中重復相同的代碼:
?
文檔測試
Python 內置的“文檔測試”(doctest)模塊可以直接提取注釋中的代碼并執行測試。
IO編程
異步IO與同步IO
異步性能好,效率高,復雜(包括輪詢模式和回調模式)
文件讀寫:
>>> f = open('/User/michael/test.txt', 'r')使用try。。finally
try:f = open('/path/to/file', 'r')print f.read() finally:if f:f.close()簡潔方法,使用with語句自動調用close() 方法:
with open('/path/to/file', 'r') as f:print f.read()調用read()會一次性讀取文件的全部內容,如果文件有10G,內存就爆了,所以,要保險起見,可以反復調用read(size)方法,每次最多讀取size個字節的內容。另外,調用readline()可以每次讀取一行內容,調用readlines()一次讀取所有內容并按行返回list。因此,要根據需要決定怎么調用。
如果文件很小,read()一次性讀取最方便;如果不能確定文件大小,反復調用read(size)比較保險;如果是配置文件,調用readlines()最方便:
for line in f.readlines():print(line.strip()) # 把末尾的'\n'刪掉讀文件
要以讀文件的模式打開一個文件對象,使用Python內置的open()函數,傳入文件名和標示符:
>>> f = open('/Users/michael/test.txt', 'r')標示符'r'表示讀,這樣,我們就成功地打開了一個文件。
如果文件不存在,open()函數就會拋出一個IOError的錯誤,并且給出錯誤碼和詳細的信息告訴你文件不存在:
>>> f=open('/Users/michael/notfound.txt', 'r') Traceback (most recent call last):File "<stdin>", line 1, in <module> IOError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'如果文件打開成功,接下來,調用read()方法可以一次讀取文件的全部內容,Python把內容讀到內存,用一個str對象表示:
>>> f.read() 'Hello, world!'最后一步是調用close()方法關閉文件。文件使用完畢后必須關閉,因為文件對象會占用操作系統的資源,并且操作系統同一時間能打開的文件數量也是有限的:
>>> f.close()由于文件讀寫時都有可能產生IOError,一旦出錯,后面的f.close()就不會調用。所以,為了保證無論是否出錯都能正確地關閉文件,我們可以使用try ... finally來實現:
try:f = open('/path/to/file', 'r')print f.read() finally: if f: f.close()但是每次都這么寫實在太繁瑣,所以,Python引入了with語句來自動幫我們調用close()方法:
with open('/path/to/file', 'r') as f:print f.read()這和前面的try ... finally是一樣的,但是代碼更佳簡潔,并且不必調用f.close()方法。
調用read()會一次性讀取文件的全部內容,如果文件有10G,內存就爆了,所以,要保險起見,可以反復調用read(size)方法,每次最多讀取size個字節的內容。另外,調用readline()可以每次讀取一行內容,調用readlines()一次讀取所有內容并按行返回list。因此,要根據需要決定怎么調用。
如果文件很小,read()一次性讀取最方便;如果不能確定文件大小,反復調用read(size)比較保險;如果是配置文件,調用readlines()最方便:
for line in f.readlines():print(line.strip()) # 把末尾的'\n'刪掉file-like Object
像open()函數返回的這種有個read()方法的對象,在Python中統稱為file-like Object。除了file外,還可以是內存的字節流,網絡流,自定義流等等。file-like Object不要求從特定類繼承,只要寫個read()方法就行。
StringIO就是在內存中創建的file-like Object,常用作臨時緩沖。
二進制文件
前面講的默認都是讀取文本文件,并且是ASCII編碼的文本文件。要讀取二進制文件,比如圖片、視頻等等,用'rb'模式打開文件即可:
>>> f = open('/Users/michael/test.jpg', 'rb') >>> f.read() '\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六進制表示的字節字符編碼
要讀取非ASCII編碼的文本文件,就必須以二進制模式打開,再解碼。比如GBK編碼的文件:
>>> f = open('/Users/michael/gbk.txt', 'rb') >>> u = f.read().decode('gbk') >>> u u'\u6d4b\u8bd5' >>> print u 測試如果每次都這么手動轉換編碼嫌麻煩(寫程序怕麻煩是好事,不怕麻煩就會寫出又長又難懂又沒法維護的代碼),Python還提供了一個codecs模塊幫我們在讀文件時自動轉換編碼,直接讀出unicode:
import codecs with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f: f.read() # u'\u6d4b\u8bd5'寫文件
寫文件和讀文件是一樣的,唯一區別是調用open()函數時,傳入標識符'w'或者'wb'表示寫文本文件或寫二進制文件:
>>> f = open('/Users/michael/test.txt', 'w') >>> f.write('Hello, world!') >>> f.close()你可以反復調用write()來寫入文件,但是務必要調用f.close()來關閉文件。當我們寫文件時,操作系統往往不會立刻把數據寫入磁盤,而是放到內存緩存起來,空閑的時候再慢慢寫入。只有調用close()方法時,操作系統才保證把沒有寫入的數據全部寫入磁盤。忘記調用close()的后果是數據可能只寫了一部分到磁盤,剩下的丟失了。所以,還是用with語句來得保險:
with open('/Users/michael/test.txt', 'w') as f:f.write('Hello, world!')要寫入特定編碼的文本文件,請效仿codecs的示例,寫入unicode,由codecs自動轉換成指定編碼。
?
操作文件和目錄
# 查看當前目錄的絕對路徑: >>> os.path.abspath('.') '/Users/michael' # 在某個目錄下創建一個新目錄, # 首先把新目錄的完整路徑表示出來: >>> os.path.join('/Users/michael', 'testdir') '/Users/michael/testdir' # 然后創建一個目錄: >>> os.mkdir('/Users/michael/testdir') # 刪掉一個目錄: >>> os.rmdir('/Users/michael/testdir')拆分路徑:
>>> os.path.split('/Users/michael/testdir/file.txt') ('/Users/michael/testdir', 'file.txt')過濾文件:
>>> [x for x in os.listdir('.') if os.path.isdir(x)] ['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Adlm', 'Applications', 'Desktop', ...]要列出所有的.py文件,也只需一行代碼:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] ['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']?
序列化:(pickling)
把變量從內存中變成可存儲或傳輸的過程稱之為序列化
把變量從內存中變成可存儲或傳輸的過程稱之為反序列化, unpickling
序列化,兩個模塊,cPickle、pickle,cPickle是C語言寫的pickle是Python寫的
序列化后就可以寫入磁盤,或者網絡傳輸。
>>> d = dict(name='Bob', age=20, score=88) >>> pickle.dumps(d) # 把任意一個對象序列化成一個 str,然后,就可以把這個str寫入文件。或者用 pickle.dump() >>> f = open('dump.txt', 'wb') >>> pickle.dump(d, f) #把一個對象序列化后寫入一個 file-like Object: >>> f.close()當我們要把對象從磁盤讀到內存時,可以先把內容讀到一個str,然后用pickle.loads()方法反序列化出對象,也可以直接用pickle.load()方法從一個file-like Object中直接反序列化出對象。我們打開另一個Python命令行來反序列化剛才保存的對象:
>>> f = open('dump.txt', 'rb') >>> d = pickle.load(f) >>> f.close() >>> d {'age': 20, 'score': 88, 'name': 'Bob'}JSON
JSON不僅是標準格式,并且比XML更快,而且可以直接在Web頁面中讀取,非常方便。
JSON表示的對象就是標準的JavaScript語言的對象
JSON和Python內置的數據類型對應如下:
| JSON類型 | Python類型 |
| {} | dict |
| [] | list |
| "string" | 'str'或u'unicode' |
| 1234.56 | int或float |
| true/false | True/False |
| null | None |
?
dumps()方法還提供了一大堆的可選參數:
https://docs.python.org/2/library/json.html#json.dumps
將對象轉為 JSON
?
進程和線程
多任務的實現有3種方式:
- 多進程模式;
- 多線程模式;
- 多進程+多線程模式。
多進程(multiprocessing):
Unix/Linux操作系統提供了一個fork()系統調用,它非常特殊。普通的函數調用,調用一次,返回一次,但是fork()調用一次,返回兩次,因為操作系統自動把當前進程(稱為父進程)復制了一份(稱為子進程),然后,分別在父進程和子進程內返回。
子進程永遠返回0,而父進程返回子進程的ID。這樣做的理由是,一個父進程可以fork出很多子進程,所以,父進程要記下每個子進程的ID,而子進程只需要調用getppid()就可以拿到父進程的ID。
#Python的os模塊封裝了常見的系統調用,其中就包括fork,可以在Python程序中輕松創建子進程:# multiprocessing.py import osprint 'Process (%s) start...' % os.getpid() pid = os.fork() if pid==0:print 'I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()) else:print 'I (%s) just created a child process (%s).' % (os.getpid(), pid)#運行結果如下: Process (876) start... I (876) just created a child process (877). I am child process (877) and my parent is 876.#有了fork調用,一個進程在接到新任務時就可以復制出一個子進程來處理新任務,常見的Apache服務器就是由父進程監聽端口,每當有新的http請求時,
就fork出子進程來處理新的http請求。
multiprocessing
multiprocessing模塊提供了一個Process類來代表一個進程對象,
下面的例子演示了啟動一個子進程并等待其結束:
from multiprocessing import Process import os# 子進程要執行的代碼 def run_proc(name):print 'Run child process %s (%s)...' % (name, os.getpid())if __name__=='__main__':print 'Parent process %s.' % os.getpid()p = Process(target=run_proc, args=('test',))#創建子進程時,只需要傳入一個執行函數和函數的參數,print 'Process will start.'p.start()#創建一個Process實例,用start()方法啟動,這樣創建進程比fork()還要簡單。p.join() #join() 方法可以等待子進程結束后再繼續往下運行,通常用于進程間的同步。print 'Process end.' Parent process 928. Process will start. Run child process test (929)... Process end.?
Pool
如果要啟動大量的子進程,可以用進程池的方式批量創建子進程:
# -*- coding:utf-8 -*-from multiprocessing import Pool import os, time, randomdef long_time_task(name):print 'Run task %s (%s)...' % (name, os.getpid())start = time.time()time.sleep(random.random() * 3)end = time.time()print 'Task %s runs %0.2f seconds.' % (name, (end - start))if __name__=='__main__':print 'Parent process %s.' % os.getpid()p = Pool()for i in range(5):p.apply_async(long_time_task, args=(i,))print 'Waiting for all subprocesses done...'p.close()p.join() //print 'All subprocesses done.' Parent process 862. Waiting for all subprocesses done... Run task 0 (864)... Run task 1 (865)... Run task 2 (866)... Run task 3 (867)... Task 1 runs 0.42 seconds. Run task 4 (865)... Task 0 runs 1.41 seconds. Task 4 runs 1.65 seconds. Task 3 runs 2.42 seconds. Task 2 runs 2.64 seconds. All subprocesses done.***Repl Closed***解讀:
對Pool對象調用join()方法會等待所有子進程執行完畢,調用join()之前必須先調用close(),調用close()之后就不能繼續添加新的Process了。
請注意輸出的結果,task 0,1,2,3是立刻執行的,而task 4要等待前面某個task完成后才執行,這是因為Pool的默認大小在我的電腦上是4,因此,最多同時執行4個進程。這是Pool有意設計的限制,并不是操作系統的限制。如果改成:
p = Pool(5)就可以同時跑5個進程。
由于Pool的默認大小是CPU的核數,如果你不幸擁有8核CPU,你要提交至少9個子進程才能看到上面的等待效果。
多線程
Python的標準庫提供了兩個模塊:thread和threading,thread是低級模塊,threading是高級模塊,對thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。
啟動一個線程就是把一個函數傳入并創建Thread實例,然后調用start()開始執行:
?
import time, threading#新線程執行的代碼: def loop():print 'thread %s is running...' % threading.current_thread().namen = 0while n < 5:n = n + 1print 'thread %s >>> %s' % (threading.current_thread().name, n)time.sleep(1)print 'thread %s ended.' % threading.current_thread().nameprint 'thread %s is running...' % threading.current_thread().name t = threading.Thread(target = loop, name = 'LoopThread') t.start() t.join() #join作用是阻塞主進程(擋住,無法執行join以后的語句 print 'thread %s ended.' % threading.current_thread().name thread MainThread is running... thread LoopThread is running... thread LoopThread >>> 1 thread LoopThread >>> 2 thread LoopThread >>> 3 thread LoopThread >>> 4 thread LoopThread >>> 5 thread LoopThread ended. thread MainThread ended.***Repl Closed***join ()方法:
主線程A中,創建了子線程B,并且在主線程A中調用了B.join(),那么,
主線程A會在調用的地方等待,直到子線程B完成操作后,才可以接著往下執行,
那么在調用這個線程時可以使用被調用線程的join方法。
?原型join([timeout]) 里面的參數時可選的,代表線程運行的最大時間,
即如果超過這個時間,不管這個此線程有沒有執行完畢都會被回收,
然后主線程或函數都會接著執行的。
?
由于任何進程默認就會啟動一個線程,我們把該線程稱為主線程,主線程又可以啟動新的線程,
Python的threading模塊有個current_thread()函數,它永遠返回當前線程的實例。
主線程實例的名字叫MainThread,子線程的名字在創建時指定,我們用LoopThread命名子線程。
名字僅僅在打印時用來顯示,完全沒有其他意義,
如果不起名字Python就自動給線程命名為Thread-1,Thread-2……
?
Lock
多線程和多進程最大的不同在于,
多進程中,同一個變量,各自有一份拷貝存在于每個進程中,互不影響,
多線程中,所有變量都由所有進程共享,所以,任何一個變量都可以被任何一個線程修改,
因此,線程之間共享數據最大的危險在于多個線程同時改一個變量,把內容改亂了。
?
鎖,當某個線程開始執行change_it()時,我們說,該線程因為獲得了鎖,
因此其他線程不能同時執行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。
由于鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,
所以,不會造成修改的沖突。創建一個鎖就是通過threading.Lock()來實現:
balance = 0 lock = threading.Lock()def run_thread(n):for i in range(100000):# 先要獲取鎖: lock.acquire()try:# 放心地改吧: change_it(n)finally:# 改完了一定要釋放鎖:lock.release()鎖的好處就是確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行,壞處當然也很多,首先是阻止了多線程并發執行,包含鎖的某段代碼實際上只能以單線程模 式執行,效率就大大地下降了。其次,由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能會造成死鎖,導致多個線程全部掛起,既 不能執行,也無法結束,只能靠操作系統強制終止。
?
用C、C++或Java來改寫相同的死循環,直接可以把全部核心跑滿,4核就跑到400%,8核就跑到800%,為什么Python不行呢?
因為Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先獲得GIL鎖,然后,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把所有線程的執行代碼都給上了鎖,所以,多線程在Python中只能交替執行,即使100個線程跑在100核CPU上,也只能用到1個核。
GIL是Python解釋器設計的歷史遺留問題,通常我們用的解釋器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。
所以,在Python中,可以使用多線程,但不要指望能有效利用多核。如果一定要通過多線程利用多核,那只能通過C擴展來實現,不過這樣就失去了Python簡單易用的特點。
不過,也不用過于擔心,Python雖然不能利用多線程實現多核任務,但可以通過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。
?
ThreadLocal
目的:
在多線程環境下,每個線程都有自己的數據。一個線程使用自己的局部變量比使用全局變量好,因為局部變量只有線程自己能看見,不會影響其他線程,而全局變量的修改必須加鎖。
但是局部變量也有問題,就是在函數調用的時候,傳遞起來很麻煩
# -*- coding:utf-8 -*-import threading#創建全局ThreadLocal對象: local_school = threading.local()def process_student():print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)def process_thread(name):#綁定ThreadLocal的student:local_school.student = nameprocess_student()t1 = threading.Thread(target = process_thread, args = ('Alice',), name = 'Thread-A') t2 = threading.Thread(target = process_thread, args = ('Bob',), name = 'Thread-B') t1.start() t2.start() t1.join() t2.join() Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)全局變量local_school就是一個ThreadLocal對象,每個Thread對它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內部會處理。
可以理解為全局變量local_school是一個dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。
ThreadLocal最常用的地方就是為每個線程綁定一個數據庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調用到的處理函數都可以非常方便地訪問這些資源。
?
多線程vs多進程
多線程:優點,效率高,但是任何一個線程掛掉都有可能直接造成整個進程奔潰,
因為所有線程共享進程的內存。
多進程:優點,穩定,創建進程的代價大
?
計算密集型 vs. IO密集型
是否采用多任務的第二個考慮是任務的類型。我們可以把任務分為計算密集型和IO密集型。
對于計算密集型任務,最好用C語言編寫。
涉及到網絡、磁盤IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低于CPU 和內存的速度)。對于IO密集型任務,任務越多,CPU效率越高,但也有一個限度。常見的大部分任務都是IO密集型任務,比如Web應用。
對于IO密集型任務,最合適的語言就是開發效率最高(代碼量最少)的語言,腳本語言是首選,C語言最差。
?
現代操作系統對IO操作已經做了巨大的改進,最大的特點就是支持異步IO。如果充分利用操作系統提供的異步IO支持,就可以用單進程單線程模型來執行多任 務,這種全新的模型稱為事件驅動模型,Nginx就是支持異步IO的Web服務器,它在單核CPU上采用單進程模型就可以高效地支持多任務。在多核CPU 上,可以運行多個進程(數量與CPU核心數相同),充分利用多核CPU。由于系統總的進程數量十分有限,因此操作系統調度非常高效。用異步IO編程模型來 實現多任務是一個主要的趨勢。
?
分布式進程
# -*- coding:utf-8 -*- taskmanager.pyimport random, time, Queue from multiprocessing.managers import BaseManger#發送任務的隊列: task_queue = Queue.Queue() #接受結果的隊列: result_queue = Queue.Queue()#從BaseManager繼承的QueueManager: class QueueManager(BaseManger):pass #把兩個Queue都注冊到網絡上,callable參數關聯了Queue對象: QueueManager.register('get_task_queue', callable=lambda: task_queue) QueueManager.register('get_result_queue', callable=lambda: result_queue) # 綁定端口5000, 設置驗證碼'abc': manager = QueueManager(address=('', 5000), authkey='abc') # 啟動Queue: manager.start() manager.start() # 獲得通過網絡訪問的Queue對象: task = manager.get_task_queue() result = manager.get_result_queue() # 放幾個任務進去: for i in range(10):n = random.randint(0, 10000)print('Put task %d...' % n)task.put(n) # 從result隊列讀取結果: print('Try get results...') for i in range(10):r = result.get(timeout=10)print('Result: %s' % r) # 關閉: manager.shutdown()
請注意,當我們在一臺機器上寫多進程程序時,創建的Queue可以直接拿來用,但是,在分布式多進程環境下,添加任務到Queue不可以直接對原始的task_queue進行操作,那樣就繞過了QueueManager的封裝,必須通過manager.get_task_queue()獲得的Queue接口添加。
然后,在另一臺機器上啟動任務進程(本機上啟動也可以):
# taskworker.pyimport time, sys, Queue from multiprocessing.managers import BaseManager# 創建類似的QueueManager: class QueueManager(BaseManager):pass# 由于這個QueueManager只從網絡上獲取Queue,所以注冊時只提供名字: QueueManager.register('get_task_queue') QueueManager.register('get_result_queue')# 連接到服務器,也就是運行taskmanager.py的機器: server_addr = '127.0.0.1' print('Connect to server %s...' % server_addr) # 端口和驗證碼注意保持與taskmanager.py設置的完全一致: m = QueueManager(address=(server_addr, 5000), authkey='abc') # 從網絡連接: m.connect() # 獲取Queue的對象: task = m.get_task_queue() result = m.get_result_queue() # 從task隊列取任務,并把結果寫入result隊列: for i in range(10):try:n = task.get(timeout=1)print('run task %d * %d...' % (n, n))r = '%d * %d = %d' % (n, n, n*n)time.sleep(1)result.put(r)except Queue.Empty:print('task queue is empty.') # 處理結束: print('worker exit.')任務進程要通過網絡連接到服務進程,所以要指定服務進程的IP。
現在,可以試試分布式進程的工作效果了。先啟動taskmanager.py服務進程:
$ python taskmanager.py Put task 3411... Put task 1605... Put task 1398... Put task 4729... Put task 5300... Put task 7471... Put task 68... Put task 4219... Put task 339... Put task 7866... Try get results...taskmanager進程發送完任務后,開始等待result隊列的結果。現在啟動taskworker.py進程:
$ python taskworker.py 127.0.0.1 Connect to server 127.0.0.1... run task 3411 * 3411... run task 1605 * 1605... run task 1398 * 1398... run task 4729 * 4729... run task 5300 * 5300... run task 7471 * 7471... run task 68 * 68... run task 4219 * 4219... run task 339 * 339... run task 7866 * 7866... worker exit.taskworker進程結束,在taskmanager進程中會繼續打印出結果:
Result: 3411 * 3411 = 11634921 Result: 1605 * 1605 = 2576025 Result: 1398 * 1398 = 1954404 Result: 4729 * 4729 = 22363441 Result: 5300 * 5300 = 28090000 Result: 7471 * 7471 = 55815841 Result: 68 * 68 = 4624 Result: 4219 * 4219 = 17799961 Result: 339 * 339 = 114921 Result: 7866 * 7866 = 61873956這個簡單的Manager/Worker模型有什么用?其實這就是一個簡單但真正的分布式計算,把代碼稍加改造,啟動多個worker,就可以把任務分布到幾臺甚至幾十臺機器上,比如把計算n*n的代碼換成發送郵件,就實現了郵件隊列的異步發送。
Queue對象存儲在哪?注意到taskworker.py中根本沒有創建Queue的代碼,所以,Queue對象存儲在taskmanager.py進程中:
而Queue之所以能通過網絡訪問,就是通過QueueManager實現的。由于QueueManager管理的不止一個Queue,所以,要給每個Queue的網絡調用接口起個名字,比如get_task_queue。
authkey有什么用?這是為了保證兩臺機器正常通信,不被其他機器惡意干擾。如果taskworker.py的authkey和taskmanager.py的authkey不一致,肯定連接不上。
?
?正則表達式
?
- [0-9a-zA-Z\_]可以匹配一個數字、字母或者下劃線;
-
[0-9a-zA-Z\_]+可以匹配至少由一個數字、字母或者下劃線組成的字符串,比如'a100','0_Z','Py3000'等等;
-
[a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下劃線開頭,后接任意個由一個數字、字母或者下劃線組成的字符串,也就是Python合法的變量;
-
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精確地限制了變量的長度是1-20個字符(前面1個字符+后面最多19個字符)。
A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'。
^表示行的開頭,^\d表示必須以數字開頭。
$表示行的結束,\d$表示必須以數字結束。
你可能注意到了,py也可以匹配'python',
但是加上^py$就變成了整行匹配,就只能匹配'py'了
re模塊
Python提供re模塊,包含所有正則表達式的功能。
>>> import re >>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345') <_sre.SRE_Match object at 0x1026e18b8> >>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345') >>>match()方法判斷是否匹配,如果匹配成功,返回一個Match對象,否則返回None。常見的判斷方法就是
import retest = '用戶輸入的字符串'if re.match(r'正則表達式',test):print 'ok' else:print 'failed'切分字符串
用正則表達式切分字符串比用固定的字符更靈活,請看正常的切分代碼:
>>> 'a b c'.split(' ') ['a', 'b', '', '', 'c']嗯,無法識別連續的空格,用正則表達式試試:
>>> re.split(r'\s+', 'a b c') ['a', 'b', 'c']無論多少個空格都可以正常分割。加入,試試:
>>> re.split(r'[\s\,]+', 'a,b, c d') ['a', 'b', 'c', 'd']再加入;試試:
>>> re.split(r'[\s\,\;]+', 'a,b;; c d') ['a', 'b', 'c', 'd']?分組(group)
?
編譯
當我們在Python中使用正則表達式時,re模塊內部會干兩件事情:
編譯正則表達式,如果正則表達式的字符串本身不合法,會報錯;
用編譯后的正則表達式去匹配字符串。
re_email = re.compile(r'[a-z\.]{3,20})@([a-z]+\.[a-z]{3})')
?
常用內建模塊
batteried included
?
collection是Python內建的一個集合摸塊,提供了許多有用的集合類。
namedtuple
tuple 可以表示不變集合,例如,一個點的二維坐標可以表示為:
>>> p = (1, 2)
nametuple 是一個函數,它用來創建一個自定義的tuple對象
并且規定了tuple元素的個數,并可以用屬性而不是索引來引用tuple的某個元素。
這樣一來,我們用namedtuple可以很方便地定義一種數據類型,它具備tuple的不變性,又可以根據屬性來引用,使用十分方便。
>>> from collections import namedtuple >>> Point = namedtuple('Point', ['x', 'y']) >>> p = Point(1, 2) >>> p.x 1 >>> p.y 2?deque
?list 訪問快,但是插入和刪除慢,因為List是線性存儲。
deque是雙向鏈表,適合用于隊列和棧
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
deque 可以實現List的:append(), pop(), appendleft(), popleft()
defultdict
使用dict時,如果引用的Key不存在,就會拋出KeyError。如果希望key不存在時,返回一個默認值,就可以用defaultdict:
>>> from collections import defultdit
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1']
?'abc'
>>> dd['key2']
?'N/A'
OrderedDict
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) >>> od # OrderedDict的Key是有序的 OrderedDict([('a', 1), ('b', 2), ('c', 3)])Counter
Counter是一個簡單的計數器,例如,統計字符出現的個數:
>>> from collections import Counter >>> c = Counter() >>> for ch in 'programming': ... c[ch] = c[ch] + 1 ... >>> c Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})Counter實際上也是dict的一個子類,上面的結果可以看出,字符'g'、'm'、'r'各出現了兩次,其他字符各出現了一次。
Base64
base64是一種用64 個字符來表示任意二進制數據的方法。
Base64編碼會把3字節的二進制數據編碼為4字節的文本數據,長度增加33%,好處是編碼后的文本數據可以在郵件正文、網頁等直接顯示。
對二進制數據進行處理,每3個字節一組,一共是3x8=24bit,劃為4組,每組正好6個bit
如果要編碼的二進制數據不是3的倍數,最后會剩下1個或2個字節怎么辦?Base64用\x00字節在末尾補足后,再在編碼的末尾加上1個或2個=號,表示補了多少字節,解碼的時候,會自動去掉。
?
由于=字符也可能出現在Base64編碼中,但=用在URL、Cookie里面會造成歧義,所以,很多Base64編碼后會把=去掉
自動去掉=的:
def b64decode_self(str):
return base64.b64decode(str+'='*(4-len(str)%4))
?
struct
在Python中,比方說要把一個32位無符號整數變成字節,也就是4個長度的str
1bit:表示1比特
1byte:表示1字節
1byte=8bit
>>> import struct >>> struct.pack('>I', 10240099) '\x00\x9c@c'pack的第一個參數是處理指令,'>I'的意思是:>表示字節順序是big-endian,也就是網絡序,I表示4字節無符號整數。后面的參數個數要和處理指令一致。unpack把str變成相應的數據類型:>>> struct.unpack('>IH', '\xf0\xf0\xf0\xf0\x80\x80') (4042322160, 32896)Windows的位圖文件(.bmp)是一種非常簡單的文件格式,我們來用struct分析一下。
首先找一個bmp文件,沒有的話用“畫圖”畫一個。
讀入前30個字節來分析:
>>> s = '\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'BMP格式采用小端方式存儲數據,文件頭的結構按順序如下:
兩個字節:'BM'表示Windows位圖,'BA'表示OS/2位圖; 一個4字節整數:表示位圖大小; 一個4字節整數:保留位,始終為0; 一個4字節整數:實際圖像的偏移量; 一個4字節整數:Header的字節數; 一個4字節整數:圖像寬度; 一個4字節整數:圖像高度; 一個2字節整數:始終為1; 一個2字節整數:顏色數。
所以,組合起來用unpack讀取:
>>> struct.unpack('<ccIIIIIIHH', s) ('B', 'M', 691256, 0, 54, 40, 640, 360, 1, 24)結果顯示,'B'、'M'說明是Windows位圖,位圖大小為640x360,顏色數為24。
請編寫一個bmpinfo.py,可以檢查任意文件是否是位圖文件,如果是,打印出圖片大小和顏色數。
import struct def test(img_dir):with open(img_dir, 'rb')as f:tu = struct.unpack('<ccIIII\IIHH', f.read(30))if tu[0] == 'B' and tu[1] == 'M':print 'windows bitmap,size:%s, width:%s,\height:%s' %(tu[2],tu[6], tu[7])elif tu[0] == 'B' and tu[1] == 'A':print 'OS\2:, width:%s, height:%s' %(tu[6], tu[7])else:print 'sorry'f.close()test('/Users/Y1Y/Desktop/1.bmp')?
?socket
# 導入socket庫: import socket # 創建一個socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立連接: s.connect(('www.sina.com.cn', 80))創建Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6。SOCK_STREAM指定使用面向流的TCP協議,這樣,一個Socket對象就創建成功,但是還沒有建立連接。
客戶端要主動發起TCP連接,必須知道服務器的IP地址和端口號。新浪網站的IP地址可以用域名www.sina.com.cn自動轉換到IP地址,但是怎么知道新浪服務器的端口號呢?
答案是作為服務器,提供什么樣的服務,端口號就必須固定下來。由于我們想要訪問網頁,因此新浪提供網頁服務的服務器必須把端口號固定在80端口,因為80端口是Web服務的標準端口。其他服務都有對應的標準端口號,例如SMTP服務是25端口,FTP服務是21端口,等等。端口號小于1024的是Internet標準服務的端口,端口號大于1024的,可以任意使用。
因此,我們連接新浪服務器的代碼如下:
s.connect(('www.sina.com.cn', 80))注意參數是一個tuple,包含地址和端口號。
建立TCP連接后,我們就可以向新浪服務器發送請求,要求返回首頁的內容:
# 發送數據: s.send('GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')?
轉載于:https://www.cnblogs.com/IDRI/p/5741333.html
總結
以上是生活随笔為你收集整理的Python复习 基础知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab遗传算法外卖配送优化(新的约
- 下一篇: pythontkinter图片_Pyth