python基礎語法筆記

python筆記

參數

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

定義可變參數和定義一個list或tuple參數相比,僅僅在參數前面加了一個*號。在函數內部,參數numbers接收到的是一個tuple,因此,函數代碼完全不變。但是,調用該函數時,可以傳入任意個參數,包括0個參數:

>>> calc(1, 2)
5
>>> calc()
0

如果已經有一個list或者tuple,要調用一個可變參數怎麼辦?
Python允許你在list或tuple前面加一個*號,把list或tuple的元素變成可變參數傳進去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

定義默認參數要牢記一點:默認參數必須指向不變對象!

def add_end(L=[]):
    L.append('END')
    return L

>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

Python函數在定義的時候,默認參數L的值就被計算出來了,即[],因爲默認參數L也是一個變量,它指向對象[],每次調用該函數,如果改變了L的內容,則下次調用時,默認參數的內容就變了,不再是函數定義時的[]了。

要修改上面的例子,我們可以用None這個不變對象來實現:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L

關鍵字參數:

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra這個dict的所有key-value用關鍵字參數傳入到函數的**kw參數,kw將獲得一個dict,注意kw獲得的dict是extra的一份拷貝,對kw的改動不會影響到函數外的extra。

如果要限制關鍵字參數的名字,就可以用命名關鍵字參數,例如,只接收city和job作爲關鍵字參數。這種方式定義的函數如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

命名關鍵字參數必須傳入參數名,如果沒有傳入參數名,調用將報錯。

在Python中定義函數,可以用必選參數、默認參數、可變參數、關鍵字參數和命名關鍵字參數,這5種參數都可以組合使用。但是請注意,參數定義的順序必須是:必選參數、默認參數、可變參數、命名關鍵字參數和關鍵字參數。

尾遞歸是指,在函數返回的時候,調用自身本身,並且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優化,使遞歸本身無論調用多少次,都只佔用一個棧幀,不會出現棧溢出的情況。

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

上面的fact(n)函數由於return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函數中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)    

遺憾的是,大多數編程語言沒有針對尾遞歸做優化,Python解釋器也沒有做優化,所以,即使把上面的fact(n)函數改成尾遞歸方式,也會導致棧溢出。

切片
Python提供了切片(Slice)操作符,能大大簡化這種操作。

>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
>>> L[-2:]
['Bob', 'Jack']
#前10個數,每兩個取一個:
>>> L[:10:2]
[0, 2, 4, 6, 8]
#所有數,每5個取一個:
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
#只寫[:]就可以原樣複製一個list:
>>> L[:]
[0, 1, 2, 3, ..., 99]
>>> 'ABCDEFG'[:3]
'ABC'

迭代
默認情況下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同時迭代key和value,可以用for k, v in d.items()。

如何判斷一個對象是可迭代對象呢?方法是通過collections模塊的Iterable類型判斷:

>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整數是否可迭代
False

Python內置的enumerate函數可以把一個list變成索引-元素對,這樣就可以在for循環中同時迭代索引和元素本身:

>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C

循環太繁瑣,而列表生成式則可以用一行語句循環生成list:

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

>>> [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']

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

匿名函數

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

#lambda x: x * x 實際上就是:
def f(x):
    return x * x

匿名函數有個限制,就是只能有一個表達式,不用寫return,返回值就是該表達式的結果。

裝飾器
函數對象有一個name屬性,可以拿到函數的名字:

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25

>>> now.__name__
'now'
>>> f.__name__
'now'

定義一個能打印日誌的decorator,可以定義如下:

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

我們要藉助Python的@語法,把decorator置於函數的定義處:

@log
def now():
    print('2015-3-25')
>>> now()
call now():
2015-3-25

偏函數
假設要轉換大量的二進制字符串,每次都傳入int(x, base=2)非常麻煩,於是,我們想到,可以定義一個int2()的函數,默認把base=2傳進去:

def int2(x, base=2):
    return int(x, base)

>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial就是幫助我們創建一個偏函數的,不需要我們自己定義int2(),可以直接使用下面的代碼創建一個新的函數int2:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

模塊

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv
    if 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模塊後,我們就有了變量sys指向該模塊,利用sys這個變量,就可以訪問sys模塊的所有功能。

sys模塊 有一個argv變量,用list存儲了命令行的所有參數。argv至少有一個元素,因爲第一個參數永遠是該.py文件的名稱:

$ python3 hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!

作用域
正常的函數和變量名是公開的(public),可以被直接引用,比如:abc,x123,PI等;

類似__xxx__這樣的變量是特殊變量,特殊變量是可以直接訪問的,不是private變量,但是有特殊用途,比如上面的__author__,__name__就是特殊變量,hello模塊定義的文檔註釋也可以用特殊變量__doc__訪問,我們自己的變量一般不要用這種變量名;

類似_xxx和__xxx這樣的函數或變量就是非公開的(private),不應該被直接引用,比如_abc,__abc等。

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

注意:特殊方法“__init__”前後分別有兩個下劃線!!!
__init__方法的第一個參數永遠是self,表示創建的實例本身,因此,在__init__方法內部,就可以把各種屬性綁定到self,因爲self就指向創建的實例本身。

有了__init__方法,在創建實例的時候,就不能傳入空的參數了,必須傳入與__init__方法匹配的參數,但self不需要傳,Python解釋器自己會把實例變量傳進去。
和普通的函數相比,在類中定義的函數只有一點不同,就是第一個參數永遠是實例變量self,並且,調用時,不用傳遞該參數。除此之外,類的方法和普通函數沒有什麼區別。

如果要讓內部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線 __,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問,所以,我們把Student類改一改:

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

單個下劃線前綴,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視爲私有變量,不要隨意訪問”。

雙下劃線開頭的實例變量是不是一定不能從外部訪問呢?其實也不是。不能直接訪問__name是因爲Python解釋器對外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量:

>>> bart._Student__name
'Bart Simpson'

注:但是強烈建議你不要這麼幹,因爲不同版本的Python解釋器可能會把__name改成不同的變量名

錯誤例子:

>>> bart = Student('Bart Simpson', 59)
>>> bart.get_name()
'Bart Simpson'
>>> bart.__name = 'New Name' # 設置__name變量!
>>> bart.__name
'New Name'
#並未成功修改內部__name
>>> bart.get_name() # get_name()內部返回self.__name
'Bart Simpson'

繼承

class Dog(Animal):
    pass

class Cat(Animal):
    pass

靜態語言VS動態語言
對於靜態語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調用run()方法。

對於Python這樣的動態語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了。

判斷類型

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

#判斷繼承關係的類
>>> isinstance(h, Dog)
True
#也可判斷基本類型
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> 
#判斷一個對象是否能被調用,能被調用的對象就是一個Callable對象,比如函數和定義的帶有__call__()的類實例
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

獲得對象屬性、方法
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數,它返回一個包含字符串的list,比如,獲得一個str對象的所有屬性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態:

>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設置一個屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19
>>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認值404
404

__slots__
如果我們想要限制實例的屬性怎麼辦?比如,只允許對Student實例添加name和age屬性。

爲了達到限制的目的,Python允許在定義class的時候,定義一個特殊的slots變量,來限制該class實例能添加的屬性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱

>>> s = Student() # 創建新的實例
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

@property

class Student(object):

    def get_score(self):
         return self._score

    def set_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

Python內置的@property裝飾器就是負責把一個方法變成屬性調用的:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    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 Dog(Mammal, Runnable):
    pass
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

定製類
__str__

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

__getattr__

class Chain(object):

    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__

>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

枚舉類

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

value屬性則是自動賦給成員的int常量,默認從1開始計數。

如果需要更精確地控制枚舉類型,可以從Enum派生出自定義類:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被設定爲0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True

@unique裝飾器可以幫助我們檢查保證沒有重複值。

IO

>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')

try:
    f = open('/path/to/file', 'r')
    print(f.read())
finally:
    if f:
        f.close()
#可用with open
with open('/path/to/file', 'r') as f:
    print(f.read())
#讀二進制文件
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六進制表示的字節

StringIO和BytesIO是在內存中操作str和bytes的方法,使得和讀寫文件具有一致的接口。

#StringIO
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> print(f.getvalue())
hello 

>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!
#BytesIO
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())

序列化

>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
>>> f = open('dump.txt', 'wb')
#序列化
>>> pickle.dump(d, f)
>>> f.close()
>>>> f = open('dump.txt', 'rb')
#反序列化
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

#json序列化
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'

class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student('Bob', 20, 88)

def student2dict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }
>>> print(json.dumps(s, default=student2dict))
{"age": 20, "name": "Bob", "score": 88}

正則
建議使用Python的r前綴,就不用考慮轉義的問題了。

>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
#切割
>>> re.split(r'[\s\,]+', 'a,b, c  d')
['a', 'b', 'c', 'd']

#分組
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

match()方法判斷是否匹配,如果匹配成功,返回一個Match對象,否則返回None。

如果一個正則表達式要重複使用幾千次,出於效率的考慮,我們可以預編譯該正則表達式,接下來重複使用時就不需要編譯這個步驟了,直接匹配:

>>> import re
# 編譯:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')

內置模塊
urllib的request模塊可以非常方便地抓取URL內容,也就是發送一個GET請求到指定的頁面,然後返回HTTP的響應:

from urllib import request

with request.urlopen('https://api.douban.com/v2/book/2129650') as f:
    data = f.read()
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', data.decode('utf-8'))

#結果
Status: 200 OK
Server: nginx
Date: Tue, 26 May 2015 10:02:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2049
Connection: close
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
X-DAE-Node: pidl1
Data: {"rating":{"max":10,"numRaters":16,"average":"7.4","min":0},"subtitle":"","author":["廖雪峯編著"],"pubdate":"2007-6",...}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章