前言
本文是笔者作为一个java程序员,对《python基础教程》的笔记。
python是类似linux shell的解释型语言(其实与javascript更像),只不过使用shell操作多是为了操作计算机,使用python则是为了实现业务逻辑。感觉上的不同在于,如果要创建一个文件夹
- linux,
mkdir dirname
- python,
import os os.mkdir(path,mode)
java是面向对象的,所以对象是第一元素,对任何实例方法的应用都必须通过对象。python不是面向对象的,对于非复杂业务,python代码基本就是函数 + 函数调用
。
基本语法
对于任何一门编程语言来说,数据都分为基本类型和复合类型。
- 基本类型
- 对于java,基本类型是int,float等,复合类型是list、set等
- 对于python,类型不用声明,基本类型和一般语言差不多,包括整数、浮点数、字符串、布尔值。
- 语言自带的复合类型一般是为数据的存取方便,主要有两种形式:
根据索引存取(数组和list)和根据键值存取
。有以下不同点- 有可修改和不可修改两种。类似于java的数组和list
- 一般不要求元素是同一类型
类型转换
- 对于java,
String a = 1 + ""
,float a = (float)1
- 对于python,类型转换与 go 类似,比如
int('8')
将字符串8 转为8,a = str(1)
,注意此处str是一个类型,不是一个函数。
解释型语言一般写起来比较简便,(),[],{}
就可以创建元组(Tuple)、列表和字典。
- 字符串的基本操作:in(对应java 的List.contains),序列加序列,序列*整数(重复几次),序列按下标取数,格式化
string.formt
,python比较早的版本 也用% 符号来进行格式化 - 元组(tuple)是静态的, 小括号。 元组可以比较 大小 ,比如
(1, 5) < (2, 3) # True
。 - 列表是动态的, 中括号,可以append和remove。
- 字典,赋值就像是 json 字符串,大括号。
for xx in xx.keys()
列表和元组,都是一个可以放置任意数据类型的有序集合,都支持负数索引,都支持切片操作(l[1:3]
返回列表中索引从1到2的子列表),都可以随意嵌套(列表的元素可以是列表),内部实现都是array的形式,两者也可以通过 list() 和 tuple() 函数相互转换
字典本身只有键是可迭代的,如果我们要遍历它的值或者是键值对,就需要通过其内置的函数 values() 或者 items() 实现。其中,values() 返回字典的值的集合,items() 返回键值对的集合。
d = {'name': 'jason', 'dob': '2000-01-01', 'gender': 'male'}
for k in d: # 遍历字典的键
print(k)
name
dob
gender
for v in d.values(): # 遍历字典的值
print(v)
jason
2000-01-01
male
for k, v in d.items(): # 遍历字典的键值对
print('key: {}, value: {}'.format(k, v))
key: name, value: jason
key: dob, value: 2000-01-01
key: gender, value: male
控制语句
- python 不支持 switch 语句,所以多个条件判断,只能用 elif 来实现。
if a
会首先去调用a的__nonzero__()
去判断a是否为空,并返回True/False,若一个对象没有定义__nonzero__()
,就去调用它的__len__()
来进行判断(这里返回值为0代表空),若某一对象没有定义以上两种方法,则if a
的结果永远为True。在实际写代码时,我们鼓励,除了 boolean 类型的数据,条件判断最好是显性的,少来if a
这种形式 。- for 一般用于遍历 集合 使用
for ... in ..
,如果纯粹是循环次数的话,可以for .. in range(1,10)
,死循环while True
,条件循环可以while 条件
。通常来说,如果你只是遍历一个已知的集合,找出满足条件的元素,并进行相应的操作,那么使用 for 循环更加简洁。但如果你需要在满足某个条件前,不停地重复某些操作,并且没有特定的集合需要去遍历,那么一般则会使用 while 循环。PS:也就是while 之后通常是条件,for 之后通常是集合。 - 可以 用内建函数
iter(list)
返回一个迭代器 来遍历 list,类似于java 的Iterator,可以用生成器 生成一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。 - 推导式是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。
variable = [out_exp_res for out_exp in input_list if out_exp == 2]
。有点类似 jdk8 的stream 。
函数
- 不用声明参数类型(其实就是多态了,必要时请在函数开头加入数据类型的检查)、返回值类型
- 传参可以带名字,此时传参不用按顺序。
- 参数可以设定 是可选的,对于
def f(a, *b)
参数b 是可选的 - lambda函数没有名字,是一种简单的、在同一行中定义函数的方法。lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的运算结果就是函数的返回值。将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。
f=lambda a,b,c,d:a*b*c*d print(f(1,2,3,4)) #相当于下面这个函数 list1 = [1,2,3,4,5,6,7] list(filter(lambda i:i>2, list1))
- 内建函数
- filter(funcion,iterator), 过滤得到 iterator 中符合funcion 的 元素
- map(funciton,iterator), 对iterator 每个元素 进行function 转换
- 魔法函数。允许你在类中自定义函数(函数名格式一般为
__xx__
),并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()
函数,并返回相应的结果。Python中的魔法函数可以大概分为以下几类(看到魔法函数 就可以去查表):- 类的构造、删除:
object.__new__(self, ...)
object.__init__(self, ...)
object.__del__(self)
- 二元操作符: 加减乘除等,比如 + 对应
object.__add__(self, other)
- 扩展二元操作符:+=/-=
- 一元操作符:取负等
- 比较函数:<=/>
- 类的表示、输出:
str()
len()
- 类容器:in 操作等
- 类的构造、删除:
with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源。类似于java的 try(xx){...}
with open('path','读写模式‘) as f:
do something
等价于
f = open('path','读写模式')
do something
f.close()
要使用 with 语句,首先要明白上下文管理器 以及 上下文管理协议(Context Management Protocol):包含方法 __enter__()
和__exit__()
,__init__()
、__enter__()
方法会在with的代码块执行之前执行,__exit__()
会在代码块执行结束后执行。如果使用了 as 子句,则将 enter() 方法的返回值赋值给 as 子句中的 target。
面向对象
传统的命令式语言有无数重复性代码,虽然函数的诞生减缓了许多重复性,但随着计算机的发展,只有函数依然不够,需要把更加抽象的概念引入计算机才能缓解(而不是解决)这个问题,于是 OOP 应运而生。
class Player():
id # 类变量
def __init__(self,name,hp):
self.name = name # 成员变量
self.hp = hp # 成员变量
def print_role(self):
print('%s %s' %{self.name,self.hp})
def play(self): # 利用异常机制抛出错误,强制子类实现该方法
raise NotImplemetnedError('play')
user1 = Player('tom',100)
user1.print_role()
# MalePlayer 集成 Player
class MalePlayer(Player):
...
mp = MalePlayer("zhangsan",100)
print('mp的类型 %s' %type(mp))
print(isinstance(mp,Player))
- id 为类变量,类和类的实例都可以访问类变量,但只有类可以修改类变量;如果使用类的实例来修改类变量,那么python会自动给生成一个与类变量同名的成员变量,之后所有通过类的实例来访问和修改类变量,实际上访问和修改的是同名的成员变量。
- name 和hp 是成员变量,一般成员变量都是
self.xxx
,但并不是有的self.xxx
都是成员变量。只有类__init__
内包含的self.xxx
变量,还有__init__
所调用函数中的self.xxx
变量,以上两类才是真正意义上的成员变量。除此之外的类内其它self.xxx
变量只能看作是类内的全局变量。成员变量如果不想被直接访问,需要使用__
前缀修饰 - 所有的类 都继承自 object
- 可以用
@dataclass
注解 修饰class,类似java lombok 的@Data
- 类装饰器:类中一个非常特殊的实例方法,即
__call__()
。该方法的功能类似于在类中重载()
运算符,使得类实例对象可以像调用普通函数那样,以对象名()
的形式使用。 - 每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的,因此你必须在 init() 函数中显式调用父类的构造函数。
模块
层次从小到大
- 语句
- 函数 def
- 类 class
- 模块 module, 物理上是一个python文件
- 包 package, 物理上是一个文件夹, 包中可以含有模块和包
不可能所有java代码都写到一个xx.java
文件里,自然也不可能所有的python代码都写到xx.py
里,所以要分模块/文件。这就有一个如何引用其它 模块/文件 对象/函数 的问题。
- 在python里面,导入一个模块使用的是
import 文件名
,python会在sys.path(可以在python里面打印sys.path是些什么目录)里面寻找匹配名称的文件。import 模块名称 import 模块名称 as 别名 from 模块名称 import 方法名 from your_file import function_name, class_name
- 模块本质就是一个*.py文件,在模块的内部,可以通过一个全局变量
__name__
来获得模块名。 - 模块可以包含可执行的语句,这些语句在模块初始化的时候执行——当所在模块被import导入时,它们有且只有执行一次。
- 当一个模块首次被导入时,Python 会搜索该模块,如果找到就创建一个 module 对象并初始化它。python加载后的模块都会保存在sys.modules里面。
- 给python文件xx.py起名时,尽量不要用一些公共的包名,比如json等。假设有一个json.py,那么同一个包下的另一个python文件
import json
时就会找到自己写的json.py。(类似java classpath的搜索优先级问题) - Python 是脚本语言,和 C++、Java 最大的不同在于,不需要显式提供 main() 函数入口。import 在导入文件的时候,会自动把所有暴露在外面的代码全都执行一遍。因此,如果你要把一个东西封装成模块,又想让它可以执行的话,你必须将要执行的代码放在
if __name__ == '__main__'
下面(避开import 时执行)。其实,name 作为 Python 的魔术内置参数,本质上是模块对象的一个属性。我们使用 import 语句时,name 就会被赋值为该模块的名字,自然就不等于 __main__了
包
- 为了帮助组织模块并提供名称层次结构,Python 还引入了包的概念。包通过层次结构进行组织,在包之内除了一般的模块,还可以有子包。
- Python 定义了两种类型的包,常规包 和 命名空间包。常规包通常以一个包含
__init__.py
文件的目录形式实现。当一个常规包被导入时,这个__init__.py
文件会隐式地被执行,它所定义的对象会被绑定到该包命名空间中的名称。不过,事实上,这是 Python 2 的规范。在 Python 3 规范中,init.py 并不是必须的
安装第三方包
现在变成语言,大都会有一些对应的库,比如java可以使用第三方jar(通过配置maven依赖),go可以引入第三方包(go get xx
),python安装第三方包有以下方法:
- 使用pip工具(
pip install xx
) - 下载第三方包文件,执行其对应的安装脚本,
python setup.py install
(跟python安装pip的方式一样)
其本质都是将第三方包文件放在约定目录。