走近 Python (類比 JS)

本文首發在 我的博客html

Python 是一門運用很普遍的語言,自動化腳本、爬蟲,甚至在深度學習領域也都有 Python 的身影。做爲一名前端開發者,也瞭解 ES6 中的不少特性借鑑自 Python (好比默認參數、解構賦值、Decorator等),同時本文會對 Python 的一些用法與 JS 進行類比。不論是提高本身的知識廣度,仍是更好地迎接 AI 時代,Python 都是一門值得學習的語言。前端

數據類型

在 Python 中,最經常使用的可以直接處理的數據類型有如下幾種:python

  • 數字[整數(int)、浮點型(float)、長整型(long)、複數(complex)]
  • 字符串(str)
  • 布爾值(bool)
  • 空值(None)

除此以外,Python 還提供了列表[list]、字典[dict] 等多種數據類型,這在下文中會介紹。es6

類型轉換與類型判斷

與 JS 十分相似,python 也能實現不一樣數據類型間的強制與隱式轉換,例子以下:編程

強制類型轉換:數組

int('3') # 3
str(3.14) # '3.14'
float('3.14') # 3.14
# 區別於 JS 只有 Number 一種類型,Python 中數字中的不一樣類型也能相互強制轉換
float(3) # 3.0
bool(3) # True
bool(0) # False複製代碼

隱式類型轉換:數據結構

1 + 1.0 # 2.0
1 + False # 1
1.0 + True # 2.0
# 區別於 JS 的 String + Number = String, py 中 str + int 會報錯
1 + '1' # TypeError: cannot concatenate 'str' and 'int' objects複製代碼

此外寫代碼的時候常常會須要判斷值的類型,能夠 使用 python 提供的 type() 函數獲取變量的類型,或者使用 isinstance(x, type) 來判斷 x 是否屬於相應的 type 類型。閉包

type(1.3) == float # True
isinstance('a', str) # True
isinstance(1.3, int) # False
isinstance(True, bool) # True
isinstance([], list) # True
isinstance({}, dict) # True複製代碼

有序集合類型

集合是指包含一組元素的數據結構,有序集合即集合裏面的元素是是按照順序排列的,Python 中的有序集合大概有如下幾類:list, tuple, str, unicode。app

list 類型

Python 中 List 類型相似於 JS 中的 Array,async

L = [1, 2, 3]
print L[-1] # '3'

L.append(4) # 末尾添加元素
print L # [1, 2, 3, 4]

L.insert(0, 'hi') # 指定索引位置添加元素
print L # ['hi', 1, 2, 3, 4]

L.pop() # 末尾移除元素 L.pop(2) ?????? 2 ???
print L # ['hi', 1, 2, 3]複製代碼

tuple 類型

tuple 類型是另外一種有序的列表,中文翻譯爲「 元組 」。tuple 和 list 很是相似,可是,tuple 一旦建立完畢,就不能修改了。

t = (1, 2, 3)
print t[0] # 1
t[0] = 11 # TypeError: 'tuple' object does not support item assignment

t = (1)
print t # 1 t 的結果是整數 1

t = (1,) # 爲了不出現如上有歧義的單元素 tuple,因此 Python 規定,單元素 tuple 要多加一個逗號「,」
print t # (1,)複製代碼

無序集合類型

dict 類型

Python 中的 dict 類型相似於 JS 中的 {} (最大的不一樣是它是沒有順序的), 它有以下特色:

  • 查找速度快 (不管 dict 有 10 個元素仍是 10 萬個元素,查找速度都同樣)
  • 佔用內存大 (與 list 類型相反)
  • dict 中的 key 不能重複
  • dict 中存儲的 key-value 序對是沒有順序的
d = {
    'a': 1,
    'b': 2,
    'c': 3
}

print d # {'a': 1, 'c': 3, 'b': 2} 能夠看出打印出的序對沒有按正常的順序打出

# 遍歷 dict
for key,value in d.items():
    print('%s: %s' % (key,value))
# a: 1
# c: 3
# b: 2複製代碼

set 類型

有的時候,咱們只想要 dict 的 key,不關心 key 對應的 value,並且要保證這個集合的元素不會重複,這時,set 類型就派上用場了。set 類型有以下特色:

  • set 存儲的元素和 dict 的 key 相似,必須是不變對象
  • set 存儲的元素也是沒有順序的
s = set(['A', 'B', 'C', 'C'])
print s # set(['A', 'C', 'B'])

s.add('D')
print s # set(['A', 'C', 'B', 'D'])

s.remove('D')
print s # set(['A', 'C', 'B'])複製代碼

Python 中的迭代

在介紹完 Python 中的有序集合和無序集合類型後,必然存在遍歷集合的 for 循環。可是和其它語言的標準 for 循環不一樣,Python 中的全部迭代是經過 for ... in 來完成的。如下給出一些經常使用的迭代 demos:

索引迭代:

L = ['apple', 'banana', 'orange']
for index, name in enumerate(L):  # enumerate() 函數把 ['apple', 'banana', 'orange'] 變成了相似 [(0, 'apple), (1, 'banana'), (2, 'orange')] 的形式
    print index, '-', name

# 0 - apple
# 1 - banana
# 2 - orange複製代碼

迭代 dict 的 value:

d = { 'apple': 6, 'banana': 8, 'orange': 5 }
print d.values() # [6, 8, 5]
for v in d.values()
    print v
# 6
# 8
# 5複製代碼

迭代 dict 的 key 和 value:

d = { 'apple': 6, 'banana': 8, 'orange': 5 }
for key, value in d.items()
    print key, ':', value
# apple : 6
# banana: 8
# orange: 5複製代碼

切片操做符

Python 提供的切片操做符相似於 JS 提供的原生函數 slice()。有了切片操做符,大大簡化了一些原來得用循環的操做。

L = ['apple', 'banana', 'orange', 'pear']
L[0:2] # ['apple', 'banana'] 取前 2 個元素
L[:2] # ['apple', 'banana'] 若是第一個索引是 0,能夠省略
L[:] # ['apple', 'banana', 'orange', 'pear'] 只用一個 : ,表示從頭至尾
L[::2] # ['apple', 'orange'] 第三個參數表示每 N 個取一個,這裏表示從頭開始,每 2 個元素取出一個來複製代碼

列表生成器

若是要生成 [1x1, 2x2, 3x3, ..., 10x10] 怎麼作?方法一是循環:

L = []
for x in range(1, 11):
    L.append(x * x)複製代碼

可是循環太繁瑣,而列表生成式則能夠用一行語句代替循環生成上面的 list:

# 把要生成的元素 x * x 放到前面,後面跟 for 循環,就能夠把 list 建立出來
[x * x for x in range(1, 11)]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]複製代碼

列表生成式的 for 循環後面還能夠加上 if 判斷(相似於 JS 中的 filter() 函數),示例以下:

[x * x for x in range(1, 11) if x % 2 == 0]
# [4, 16, 36, 64, 100]複製代碼

for 循環能夠嵌套,所以,在列表生成式中,也能夠用多層 for 循環來生成列表。

[m + n for m in 'ABC' for n in '123']
# ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']複製代碼

Python 函數

默認參數

JS 中 ES6 的 默認參數正是借鑑於 Python,用法以下:

def greet(name='World'):
    print 'Hello, ' + name + '.'

greet() # Hello, World.
greet('Python') # Hello, Python.複製代碼

可變參數

相似於 JS 函數中自動識別傳入參數的個數,Python 也提供了定義可變參數,即在可變參數的名字前面帶上個 * 號。

def fn(*args):
    print args

fn()  # ()
fn('a') # ('a',)
fn('a', 'b') # ('a', 'b')複製代碼

Python 解釋器會把傳入的一組參數組裝成一個 tuple 傳遞給可變參數,所以,在函數內部,直接把變量 args 當作一個 tuple 就行了。

經常使用高階函數

Python 中經常使用的函數 (map、reduce、filter) 的做用和 JS 中一致,只是用法稍微不一樣。

  • map 函數: 接收一個函數 f 和一個 list,並經過把函數 f 依次做用在 list 的每一個元素上,獲得一個新的 list 並返回。
def f(x):
    return x * x
print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # [1, 4, 9, 16, 25, 36, 49, 64, 81]複製代碼
  • reduce 函數: 接收一個函數 f 和一個 list(能夠接受第三個值做爲初始值),reduce() 對 list 的每一個元素反覆調用函數 f,並返回最終結果值。
def f(x, y):
    return x * y

reduce(f, [1, 3, 5]) # 15複製代碼
  • filter 函數: 接收一個函數 f 和一個list,這個函數 f 的做用是對每一個元素進行判斷,返回 True或 False,filter() 根據判斷結果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新 list。
def is_odd(x):
    return x % 2 == 1

filter(is_odd, [1, 4, 6, 7, 9, 12, 17]) # [1, 7, 9, 17]複製代碼

匿名函數

和 JS 的匿名函數不一樣的地方是,Python 的匿名函數中只能有一個表達式,且不能寫 return。拿 map() 函數爲例:

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 表示函數參數,能夠看出匿名函數 lambda x: x* x 實際上就是:

def f(x):
    return x * x複製代碼

閉包

以前寫過一些關於 JS 閉包的文章,好比 深刻淺出JavaScript之閉包(Closure)、以及 讀書筆記-你不知道的 JavaScript (上),Python 中閉包的定義和 JS 中的是一致的即:內層函數引用了外層函數的變量,而後返回內層函數。下面來看下 Py 中閉包之 for 循環經典問題:

# 但願一次返回3個函數,分別計算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i * i
        fs.append(f)
    return fs

f1, f2, f3 = count() # 這種寫法至關於 ES6 中的解構賦值
print f1(), f2(), f3() # 9 9 9複製代碼

老問題了,f1(), f2(), f3() 結果不該該是 1, 4, 9 嗎,實際結果爲何都是 9 呢?

緣由就是當 count() 函數返回了 3 個函數時,這 3 個函數所引用的變量 i 的值已經變成了 3。因爲 f一、f二、f3 並無被調用,因此,此時他們並未計算 i*i,當 f1 被調用時,i 已經變爲 3 了。

要正確使用閉包,就要確保引用的局部變量在函數返回後不能變。代碼修改以下:

方法一: 能夠理解爲建立了一個封閉的做用域,i 的 值傳給 j 以後,就和 i 沒任何關係了。每次循環造成的閉包都存進了內存中。

def count():
    fs = []
    for i in range(1, 4):
        def f(j):
            def g(): # 方法一
                return j * j
            return g
        r = f(i)
        fs.append(r)
    return fs

f1, f2, f3 = count()
print f1(), f2(), f3() # 1 4 9複製代碼

方法二:思路比較巧妙,用到了默認參數 j 在函數定義時能夠獲取到 i 的值,雖然沒有用到閉包,可是和方法一有殊途同歸之處。

def count():
    fs = []
    for i in range(1, 4):
        def f(j = i): # 方法二
            return j * j
        fs.append(f)
    return fs

f1, f2, f3 = count()
print f1(), f2(), f3() # 1 4 9複製代碼

decorator 裝飾器

ES6 的語法中的 decorator 正是借鑑了 Python 的 decorator。decorator 本質上就是一個高階函數,它接收一個函數做爲參數,而後返回一個新函數

那裝飾器的做用在哪呢?先上一段平常項目中用 ts 寫的網關代碼:

@Post('/rider/detail')  // URL 路由
@log()                   // 打印日誌
  @ResponseBody
  public async getRiderBasicInfo(
    @RequestBody('riderId') riderId: number,
    @RequestBody('cityId') cityId: number,
  ) {
    const result = await this.riderManager.findDetail(cityId, riderId)
    return result
  }複製代碼

能夠看出使用裝飾器能夠極大地簡化代碼,避免每一個函數(好比日誌、路由、性能檢測)編寫重複性代碼。

回到 Python 上,Python 提供的 @ 語法來使用 decorator,@ 等價於 f = decorate(f)。下面來看看 @log() 在 Python 中的實現:

# 咱們想把調用的函數名字給打印出來
@log()
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

# 來看看 @log() 的定義
def log():
    def log_decorator(f):
        def fn(x):
            print '調用了函數' + f.__name__ + '()'
            return f(x)
        return fn
    return log_decorator

# 結果
# 調用了函數 factorial()
# 3628800複製代碼

class

面向對象編程

面向對象編程是一種程序設計範式,基本思想是:用類定義抽象類型,而後根據類的定義建立出實例。在掌握其它語言的基礎上,仍是比較容易理解這塊知識點的,好比從下面兩種寫法能夠看出不一樣語言的語言特性間居然有如此多的共性。

es6: (附:本文的主題是 python,因此只是初略展現下 js 中類的定義以及實例的建立,爲了說明寫法的類似性)

class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
}

const child1 = new Person('Xiao Ming', 10)複製代碼

Python: (核心要點寫在註釋中)

# 定義一個 Person 類:根據 Person 類就能夠形成不少 child 實例
class Person(object):
    address = 'Earth' # 類屬性 (實例公有)
    def __init__(self, name, age): # 建立實例時,__init__()方法被自動調用
        self.name = name
        self.age = age
    def get_age(self): # 定義實例方法,它的第一個參數永遠是 self,指向調用該方法的實例自己,其餘參數和普通函數是同樣的
        return self.age

child1 = Person('Xiao Ming', 10)
child2 = Person('Xiao Hong', 9)

print child1.name # 'Xiao Ming'
print child2.get_age() # 9
print child1.address # 'Earth'
print child2.address # 'Earth'複製代碼

繼承

child 屬於 Student 類,Student 類屬於 People 類,這就引出了繼承: 即得到了父類的方法屬性後又能添加本身的方法屬性。

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

class Student(Person):
    def __init__(self, name, age, grade):
        super(Student, self).__init__(name, age) # 這裏也能這樣寫 Person.__init__(self, name, age)
        self.grade = grade

s = Student('Xiao Ming', 10, 90)
print s.name # 'Xiao Ming'
print s.grade # 90複製代碼

能夠看到子類在父類的基礎上又增長了 grade 屬性。咱們能夠再來看看 s 的類型。

isinstance(s, Person)
isinstance(s, Student)複製代碼

能夠看出,Python 中在一條繼承鏈上,一個實例能夠當作它自己的類型,也能夠當作它父類的類型。

相關文章
相關標籤/搜索