这是一次在字节内部的新人分享🤣 ,初衷是关于官网不同
SDK
版本下ApiDiff
数据的数据获取
关于Python的几个优势
更简单的编程方式
打印Hello World
Java:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Python:
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())
Map
、List
等的使用
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"]
Python
让List
和Map
用更像数组的方式存在,并使用。
以上只是Python
中一小部分的简便用法,还有很多可以探索。。
强大的开源社区
Py社区
:www.python88.comPythonTab
:www.pythontab.comStackOverflow
:stackoverflow.com/questions/t…PyChina
:pychina.org
还有很多很多尚待你去发现的社区。。
但是为了论证Python
背后社区的强大性,显然还需要一个对比,这里拿StackOverflow
上的不同tag
来进行比较。
从一个Q&A
的数量级上,想必已经足以支撑这一点了。
来自语言本身的优势
脚本语言又被称为扩建的语言,或者动态语言,是一种编程语言,用来控制软件应用程序,脚本通常以文本(如ASCII
)保存,只在被调用时进行解释或编译。
像Java / C
这种解释 / 编译型语言,在没有Idea / Clion
这类工具时,我们使用命令行去需要经过这样的步骤:
javac xxx.java
,编译生成class
文件java xxx
,运行class
文件
作为脚本语言,我们可以通过一些方式来直接使用已经写好的命令并在终端上进行运行,并且从代码的可读性上比JavaScript
更加好。
JavaScript.min | Python |
因为Python的代码分层是通过Tab来确立层级关系,即便在非常糟糕的情况的,代码的层次感依旧的。
丰富多彩的库🌟
- 如果你希望像
Java
一样去构建一个Web
程序,Django
和Flask
两个框架给了你这个机会。 - 如果你希望用它去做一些关于机器学习和数据科学库的任务,
TensorFlow
,Keras
,Pandas
等等 - 如果你想自己去玩一些爬虫项目,
BeautifulSoup4
、selenium
外加requests
库,基本可以完成你想要的基础功能。 - 还有更多的库等待你去挖掘。。。
入门Python
开发
整个开发应该来说围绕这几个问题来进行展开。
##如何进行库的导入 一般来说库的导入会分为几种形式:
- 将整个模块导入,格式为:
import module
- 从某个模块中导入某个类,格式为:
from module import a
- 从某个模块中导入多个类,格式为:
from module import a,b,c
- 将某个模块中的全部类导入,格式为:
from module import *
以BeautifulSoup4
和requests
为例,同样都是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
在命令行中的使用更加趋近于Shell
,Java
其实同样可以完成这项任务。
我们熟知的Java
的main(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")
函数的调用会以后者为准,可以将两个函数通过调换的方式来进行验证。
那要如何才能验证他的多态特性呢?
两种方案进行论述:
- 向上转型,使用代码来完成解释
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
就是多态的一种体现。
- 动态语言本身就拥有的特征,传入不同的值已经能够实现多态的特性。
Python 2 -> 3
和很多产品一样,我们会把开发分为两个分支也就是stable
和beta
,前者稳定,而后者有着更丰富的功能。Python 2 和 3
也是同样的道理。
但是需要注意这样一条消息。
Python2
从今年的4月起就已经停止维护了,与此同时NumPy
、Requests
和TensorFlow
等库在2020年也将对Python2
停止更新。这就意味着将来如果你手头的项目出了什么和Python
基础库相关的问题,那就不会再有官方为你兜底了,三方库的新功能你也没机会再看到了。所以升级转型为Python3
势在必行。
升级Python3
可以,但是对于屎山工程而言,怎么样去有效的进行升级就是一个非常严肃的问题。总不会要直接重构把,以下将给出两个比较简单的案例对比:
- 最常见的就是
print
了,一个加括号一个不加。 - 编码方式:
在
Python2
中,我们常见的一种情况就是中文乱码等奇奇怪怪的错误,这是由于Python2
本身使用的编码是ASCII
导致的,这就督促我们在写到中文时,要记得加上这样一段代码作为注释。
#coding=utf-8
而Python3
默认以utf-8
作为编码格式,绝大多数时候可以忽视这个问题的存在。
当然还有很多很多问题也是存在的。
如何比较有效的完成版本的更新迭代呢?
官方给出了四种库,为尽可能多的代码移植,以及错误检查提供了保障。
- 兼容性库
six
: 为不同版本间的兼容提供可能 - 自动修复程序
python-modernize
:Python 2-> 3
的代码移植工具 - C扩展的兼容性标头和指南
py3c
: 这是项目中如果涉及了C / C++
编写模块时用于兼容的工具 - 自动检查器
pylint --py3k
: 一种静态代码分析器,可以捕获诸如初始化变量,未使用的导入和重复的代码之类的错误,并且能够标记与Python3不兼容的代码。
最后给出的一份关于如何进行Python 2 -> 3
的迁移指南:portingguide.readthedocs.io/en/latest/
几个需要注意的大坑
版本号兼容问题
我们经常会使用pip来进行库的安装、卸载、查询当前的python环境中已经包含的库等操作,但是其实PyCharm给我们提供了更加快捷的手段。
- pip方法来完成一系列操作
使用pip查看当前已装好的库 | 安装卸载之类的操作 |
- PyCharm -> Preferences
查看当前Python环境下已有的库 | 比pip更直观的安装库方式,并且能够进行版本选择 |
查看当前Python 环境下已有的库比pip 更直观的安装库方式,并且能够进行版本选择 |
以Tensorflow
和Numpy
为例。
下图是使用了Version 1.19.0 Numpy
以及Version 1.14.0 Tensorflow
后会产生的报错,原因就是版本不兼容
而将Numpy的版本修改为Version 1.14.0
时就能解决。
环境污染
这是和Java
一个非常不同的地方,像Android
工程中我们会通过gradle
来引入各种第三方库,但是这个时候引入的库是对于当前工程而言进行使用的,如果换一个工程,就需要重新设定依赖关系。用两张图来展示我个人理解中Java
和Python
的区别。
对于Java
,引入第三方库的方案会通过Gradle / Maven
等工具来完成集成,而这些第三方框架的单独引入运行情况时都是正常的,并且关于Java
的配置,一般本地会配置不同的Version
,可能是Java 7
,Java 8
,Java 11
等等,并不会在同一版本下重复配置。
但是在Python
中,同一版本重复配置是非常常见的,不然非常容易造成环境的污染,为了证明我的说法,下面是一张配图。还是以Tensorflow
和Numpy
,因为前者依赖后者使用,所以我们删去Numpy
的库。
能够发现使用Tensorflow
因为缺失Numpy
的库而报错。
下方是使用Tensorflow
的代码,可以用于之后复现使用。
import tensorflow as tf
matrix1 = tf.constant([3., 3.])
但是还是要回到说一个环境污染到底是怎么一回事。
一个案例:当我们的Project A
明确只能用Version 1.14.0 Numpy
,但是同时我们把环境同样的去给了Project B
去进行使用,但是Project B
的Numpy Version
要求使用1.19.0
,那这个时候如果我们升级了话,对Project A
的兼容就不复存在了,这就是一个环境污染的问题。
对于这些问题,我们一般给的解法想必你也有所遇见,就是在每一个工程中都放一个新的环境。我们有两种工具去进行创建:
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
库来完成。
下面给出的一个项目可以用于抓取
API
变化数据,并导出为表格。
项目传送门 |
---|