關於Python的小小分享

一次在字節內部的新人分享🤣  ,初衷是關於官網不一樣SDK版本下ApiDiff數據的數據獲取java

關於Python的幾個優點

更簡單的編程方式

打印Hello World

Java:python

public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

Python:web

print("Hello World")

數組打印

Java:編程

String s = "i wanna print";
String[] strs = s.split(" ");
// 方案1
System.out.println(strs);
// 方案2
for (String res : strs) System.out.print(res + " ");

Python:數組

s = "i wanna print"
print(s.split())

MapList等的使用

Java:微信

// List
List<String> list = new ArrayList<>();
list.add("Mike");
list.add("Lily");
// Map
Map<String, String> map = new HashMap<>();
map.put("Mike","First");
map.put("Lily","Second");

Python:網絡

// List
list = ["Mike", "Lily"]
list.append("Jack")
// Map
map = {"Mike":"First",
"Lily":"Second"}
map["Jam"] = "third"
del map["Jam"]

PythonListMap用更像數組的方式存在,並使用。多線程

以上只是Python中一小部分的簡便用法,還有不少能夠探索。。併發

強大的開源社區

  1. Py社區:http://www.python88.com
  2. PythonTab:https://www.pythontab.com
  3. StackOverflow:https://stackoverflow.com/questions/tagged/python
  4. PyChina:https://pychina.org

還有不少不少尚待你去發現的社區。。app

可是爲了論證Python背後社區的強大性,顯然還須要一個對比,這裏拿StackOverflow上的不一樣tag來進行比較。

從一個Q&A的數量級上,想必已經足以支撐這一點了。

來自語言自己的優點

腳本語言又被稱爲擴建的語言,或者動態語言,是一種編程語言,用來控制軟件應用程序,腳本一般以文本(如ASCII)保存,只在被調用時進行解釋或編譯。

Java / C這種解釋 / 編譯型語言,在沒有Idea / Clion這類工具時,咱們使用命令行去須要通過這樣的步驟:

  1. javac xxx.java,編譯生成 class文件
  2. java xxx,運行 class文件

做爲腳本語言,咱們能夠經過一些方式來直接使用已經寫好的命令並在終端上進行運行,而且從代碼的可讀性上比JavaScript更加好。

JavaScript.min Python

由於Python的代碼分層是經過Tab來確立層級關係,即使在很是糟糕的狀況的,代碼的層次感依舊的。

豐富多彩的庫🌟

  • 若是你但願像 Java同樣去構建一個 Web程序, DjangoFlask兩個框架給了你這個機會。
  • 若是你但願用它去作一些關於機器學習和數據科學庫的任務, TensorFlowKerasPandas等等
  • 若是你想本身去玩一些爬蟲項目, BeautifulSoup4selenium外加 requests庫,基本能夠完成你想要的基礎功能。
  • 還有更多的庫等待你去挖掘。。。

入門Python開發

整個開發應該來講圍繞這幾個問題來進行展開。

##如何進行庫的導入 通常來講庫的導入會分爲幾種形式:

  1. 將整個模塊導入,格式爲: import module
  2. 從某個模塊中導入某個類,格式爲: from module import a
  3. 從某個模塊中導入多個類,格式爲: from module import a,b,c
  4. 將某個模塊中的所有類導入,格式爲: from module import *

BeautifulSoup4requests爲例,一樣都是import module的方式來進行導入。

import requests
import bs4

requests.get("URL")
BeautifulSoup(indexHtml.text, "lxml") // 報錯

requests通常使用的函數就直接是get()post(),經過上述代碼調用是直接可使用的。可是對於bs4來講,咱們通常使用的是內部的類,若是直接使用上述寫法會報錯,有兩種寫法。

// 方案1
from bs4 import BeautifulSoup
// 方案2
bs4.BeautifulSoup

這裏就上面的內容給出一個比較好的記法,推類到Java時,你能夠將requests.get("URL")中的get()方法認爲是一個Java類中的一個靜態方法,而像BeautifulSoup這樣的類,你能夠認爲是Java中的一個內部類。

若是獲取命令行參數,並規範使用?

這一步的開展,主要是爲了將Python在命令行中的使用更加趨近於ShellJava其實一樣能夠完成這項任務。

咱們熟知的Javamain(String args[])其中所包含的就是從命令行中抓取到的數據。

其實Python自己已經提供了這樣的庫,他會對在命令行中已經傳入的數據進行獲取,而後經過既定的庫來進行數據的抓取和使用。

import sys,argparse

對於上述的內容,也就是命令行數據的抓取,使用sys這個庫就已經可以知足要求了,能夠經過這樣的命令循環去直接查看。

sys.argv.pop() // 彈出最頂層的數據
sys.argv.__len__ // 抓取的數據數量
// 。。。。。

可是若是說像是-h、-v。。。等等諸多一系列的高級使用可以存在呢?

固然咱們徹底是能夠本身實現的,但顯然是一個費時不討好的活兒。這就讓argparse這個庫有了用武之地了,他就是一個參數解析的工具。

經過相似上述代碼的設定,對命令行中的數據獲取就有了必定的diy空間了。

args = parser.parse_args() // 用於進行數據的解析
args.v // 輸入的數據經過參數來進行數據獲取

常見的循環的幾種寫法

這裏提for循環的緣由是,寫法確實和Java有比較不一樣的點。

fruits = ['banana', 'apple',  'mango']
# 第一個實例,對字符串的字符進行遍歷
for letter in 'Python':
print(letter)
# 第二個實例,像Java中的String s: strs同樣對數據進行遍歷
for fruit in fruits:
print(fruit)
# 第三個實例,區間遍歷,對標Java:for(int i=0; i<fruits.length; i++)
for fruit in range(0, len(fruits)):
print(fruit)
# 第四個實例,加入了一個[e lse]
for fruit in range(0, len(fruits)):
print(fruit)
else: # 運行會發生在for循環非break跳出的狀況下
print("final")

可是是否有發現,這其中的實例三和Java的形態有所不一樣,Java的寫法for(int i=0; i<fruits.length; i++)中的int i其實很天然的給出了下標的概念,可是若是從Python剛剛的幾種遍歷手段下來觀察,只有一種方案咱們可以拿到下標,那就是在外層定義一個index來用做下標位。可是在Python中其實提供了另一種方案能夠參考 —— 轉化爲枚舉,也就是如下的代碼。

for index,fruit in enumerate(fruits):
print(index)

經過上述的寫法,就能夠直接獲取下標和數據。

做爲面向對象的語言,三大特性如何用代碼進行展示

從上文的代碼中,咱們能夠感知到到其實一些方面和Java仍是很是相似的,可是請注意Python一樣是一門面向對象的語言,那這就須要從三大方面來進行論證。

封裝

class FileWriter:

def __init__(self, version):
self.writeToFilePath = "Deprecated_Method_SDK_%d.xlsx" % version

'''
暫時只作了excel類型的文件輸出
'''

def outputFile(self, datas):
fileData = pd.DataFrame(index=datas)
writer = pd.ExcelWriter(self.writeToFilePath)
fileData.to_excel(writer)
writer.save()
print("**********************************************")
print("Scratch is Over,the file name is %s" % self.writeToFilePath)
print("**********************************************")

繼承

多態

Java中的多態通常咱們能夠這樣去進行實現。

public void a(String a){}

public void a(int a){}

public void a(String a, int b){}

可是若是咱們將它引入到Python中進行使用,會出現這樣的狀況。

def swim(self):
print("this is Son's swim")

def swim(self, s):
print("this is Son's Another swim")

函數的調用會之後者爲準,能夠將兩個函數經過調換的方式來進行驗證。

那要如何才能驗證他的多態特性呢?

兩種方案進行論述:

  1. 向上轉型,使用代碼來完成解釋
class Person:
def __init__(self, name):
self.name = name

def drink(self):
print("Person:", self.name)

class Father(Person):
def __init__(self, name):
super(Father, self).__init__(name)

def drink(self):
print("Father:", self.name)

class Son(Person):
def __init__(self, name):
super(Son, self).__init__(name)

def drink(self):
print("Son:", self.name)

class Mom(Person):
def commandDrink(self, person):
person.drink()

son = Son("Tom")
mom = Mom("Ane")
mom.commandDrink(son)

Mom類中commandDrink()函數中的proson做爲一個泛化的變量,可以接受下他的子類Son就是多態的一種體現。

  1. 動態語言自己就擁有的特徵,傳入不一樣的值已經可以實現多態的特性。

Python 2 -> 3

和不少產品同樣,咱們會把開發分爲兩個分支也就是stablebeta,前者穩定,然後者有着更豐富的功能。Python 2 和 3也是一樣的道理。

可是須要注意這樣一條消息。

Python2從今年的4月起就已經中止維護了,與此同時NumPyRequestsTensorFlow等庫在2020年也將對Python2中止更新。這就意味着未來若是你手頭的項目出了什麼和Python基礎庫相關的問題,那就不會再有官方爲你兜底了,三方庫的新功能你也沒機會再看到了。因此升級轉型爲Python3勢在必行。

升級Python3能夠,可是對於屎山工程而言,怎麼樣去有效的進行升級就是一個很是嚴肅的問題。總不會要直接重構把,如下將給出兩個比較簡單的案例對比:

  1. 最多見的就是print,一個加括號一個不加。
  2. 編碼方式:Python2中,咱們常見的一種狀況就是中文亂碼等奇奇怪怪的錯誤,這是因爲 Python2自己使用的編碼是 ASCII致使的,這就督促咱們在寫到中文時,要記得加上這樣一段代碼做爲註釋。
#coding=utf-8

Python3默認以utf-8做爲編碼格式,絕大多數時候能夠忽視這個問題的存在。

固然還有不少不少問題也是存在的。

如何比較有效的完成版本的更新迭代呢?

官方給出了四種庫,爲儘量多的代碼移植,以及錯誤檢查提供了保障。

  1. 兼容性庫 six 爲不一樣版本間的兼容提供可能
  2. 自動修復程序 python-modernize Python 2-> 3的代碼移植工具
  3. C擴展的兼容性標頭和指南  py3c 這是項目中若是涉及了 C / C++編寫模塊時用於兼容的工具
  4. 自動檢查器 pylint --py3k 一種靜態代碼分析器,能夠捕獲諸如初始化變量,未使用的導入和重複的代碼之類的錯誤,而且可以標記與Python3不兼容的代碼。

最後給出的一份關於如何進行Python 2 -> 3的遷移指南:https://portingguide.readthedocs.io/en/latest/

幾個須要注意的大坑

版本號兼容問題

咱們常常會使用pip來進行庫的安裝、卸載、查詢當前的python環境中已經包含的庫等操做,可是其實PyCharm給咱們提供了更加快捷的手段。

  1. pip方法來完成一系列操做
使用pip查看當前已裝好的庫 安裝卸載之類的操做
  1. PyCharm -> Preferences
查看當前Python環境下已有的庫 比pip更直觀的安裝庫方式,而且可以進行版本選擇

查看當前Python環境下已有的庫比pip更直觀的安裝庫方式,而且可以進行版本選擇

TensorflowNumpy爲例。

下圖是使用了Version 1.19.0 Numpy以及Version 1.14.0 Tensorflow後會產生的報錯,緣由就是版本不兼容

而將Numpy的版本修改成Version 1.14.0時就能解決。

環境污染

這是和Java一個很是不一樣的地方,像Android工程中咱們會經過gradle來引入各類第三方庫,可是這個時候引入的庫是對於當前工程而言進行使用的,若是換一個工程,就須要從新設定依賴關係。用兩張圖來展現我我的理解中JavaPython的區別。

對於Java,引入第三方庫的方案會經過Gradle / Maven等工具來完成集成,而這些第三方框架的單獨引入運行狀況時都是正常的,而且關於Java的配置,通常本地會配置不一樣的Version,多是Java 7Java 8Java 11等等,並不會在同一版本下重複配置。

可是在Python中,同一版本重複配置是很是常見的,否則很是容易形成環境的污染,爲了證實個人說法,下面是一張配圖。仍是以TensorflowNumpy,由於前者依賴後者使用,因此咱們刪去Numpy的庫。

可以發現使用Tensorflow由於缺失Numpy的庫而報錯。

下方是使用Tensorflow的代碼,能夠用於以後復現使用。

import tensorflow as tf

matrix1 = tf.constant([3., 3.])

可是仍是要回到說一個環境污染究竟是怎麼一回事。

一個案例:當咱們的Project A明確只能用Version 1.14.0 Numpy,可是同時咱們把環境一樣的去給了Project B去進行使用,可是Project BNumpy Version要求使用1.19.0,那這個時候若是咱們升級了話,對Project A的兼容就不復存在了,這就是一個環境污染的問題。

對於這些問題,咱們通常給的解法想必你也有所碰見,就是在每個工程中都放一個新的環境。咱們有兩種工具去進行建立:

  1. Anaconda

2. Virtualenv,這個方案已經集成在了Pycharm

多線程和GIL鎖

什麼是GIL鎖?其實他就是一個用於控制多線程併發的同步機制。

關於這點,舉兩個案例用來論證,GIL鎖,何時是成功的,何時又是失敗的。

from threading import Thread
import time

def counter():
i = 0
for _ in range(100000000):
i += 1
return True

def main():
start_time = time.time()
t = Thread(target=counter)
t.start()
t.join()
end_time = time.time()
print("total time of single is: {}".format(end_time - start_time))

def multi_main():
thread_all = []
start_time = time.time()
for tid in range(2):
t = Thread(target=counter)
t.start()
thread_all.append(t)
for i in range(2):
thread_all[i].join()
end_time = time.time()
print("total time of multi is: {}".format(end_time -start_time))

if __name__ == '__main__':
main()
multi_main()

這個案例是經過分別使用單線程和多線程下完成加法操做所用時間的統計,若是想放大實驗結果能夠改變counter函數中的range內的值來影響結果。若是按照正常預估,像Java在保證數據同步的前提下,通常都是多線程執行的確定更加迅速。可是Python呢?

可以發現速度反而是沒有單線程執行來的快的,這就是GIL鎖帶來的反作用,在觀察它們最後的數據是什麼樣的?100000000,沒錯在沒有任何操做下,自覺的完成了同步操做,這就是GIL鎖在搞鬼了。

從這個角度看,是否是GIL鎖就一無可取了?可是換個角度思考,若是一無可取,這個線程確定是沒有它存在的必要的,也就沒有人會在用它了,從存在即合理的角度看,須要找到一個合適的理由去證實,那就是IO密集型來進行論證。

咱們用線程去讀取必定數量的文件來完成任務。

def find_all_files(dir):
all_files = []
for root, dirs, files in os.walk(dir):
for file in files:
all_files.append(root + "/" + file)

return all_files

def main_file_read(files):
start_time = time.time()
pool = threadpool.ThreadPool(1)
requests = threadpool.makeRequests(file_read, files)
[pool.putRequest(request) for request in requests]
pool.wait()
end_time = time.time()
print("total time of single is: {}".format(end_time - start_time))

def multi_main_file_read(files):
start_time = time.time()
pool = threadpool.ThreadPool(20)
requests = threadpool.makeRequests(file_read, files)
[pool.putRequest(request) for request in requests]
pool.wait()
end_time = time.time()
print("total time of multi is: {}".format(end_time - start_time))


if __name__ == '__main__':
files = find_all_files("/Users/admin/Desktop/1")

main_file_read(files)
multi_main_file_read(files)

從復現的角度上看其實有必定的難度,文件內存在數量級比較大的數據,且文件數量極多時才比較容易顯示出差距,正常狀況下可能你看到都是這樣的狀況。

主要緣由就是文件內容過小了,致使從線程切換上耗時佔比較大,而比單線程跑慢一點,可是IO密集型時總體下來,正常狀況和單線程拉不開差距。

換成一個網絡IO操做的項目進行比較能夠比較明顯的發現差距。

  • CPU密集型任務
  • IO密集型任務

綠色: 喚醒狀態;紅色: 阻塞狀態,等待CPU調度;白色: 等待IO狀態

由於Python語言自己的特性,正常的運行只會是一個核來進行處理。若是想突破單核的限制,可使用multiprocess庫來完成。


本文分享自微信公衆號 - 告物(ClericYi_Android)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索