python基础语法

很早之前,曾学过一段时间Python;恰好最近工作需要,重新拾起并整理Python相关知识。

<!--more-->

参考

1. Python2与Python3的抉择

参考

JavaScript社区热衷于接收新事物,当出现新的语法规范甚至仅仅只是提案时,尽管是浏览器还未支持的语法,就已经可以使用Babel等工具提前体验了。

但Python3已经出现了很多年,整个Python社区仍有大部分项目使用Python2,但Python2Python3是不兼容的,因此在学习Python时,需要面对的第一个问题就是应该选择哪个Python版本进行学习?

官方宣布2020年不再支持Python2.7,因此现在学习Python的话直接从Python3开始吧。

2. 基础语法

2.1. 数据类型

字符串

可以使用三引号跨行输出,常用于做注释

'''comment
hello comment
'''

布尔值

使用TrueFalse表示布尔值

a = True
b = False

函数

使用def自定义函数,函数体的第一行语句可以是可选的字符串文本(使用三个引号可以不需要转义符),这个字符串是函数的文档字符串,或者称为 docstring,经常写注释是一个很好的习惯.

# 支持默认参数
def test(a, b = 10):
    """just for test
    """
    print(a, b)

test(1)

# 支持非定长参数,用于获取所有传入的参数
def test3(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n
    return sum

print(test3(1, 2))
print(test3(1, 2, 3))

# 支持rest参数
def test4(sum = 0, *numbers):
    # *numbers 获取剩余参数
    for n in numbers:
        sum = sum + n
    return sum

print(test4(1, 2))
print(test4(1, 2, 3))

关于函数参数默认值需要注意的一点是:默认参数只能指向字面值,而不能指向引用对象

# 在函数定义的时候,L的默认值就被计算出来了,每次使用默认参数调用时都会指向同一个L
def add_end(L = []):
    L.append('END')
    return L

print(add_end()) # ['END']
print(add_end()) # ['END', 'END'] 使用了上一次的默认参数

此外python还支持命名参数


# 支持命名参数
def test2(a, b):
    print(a, b) # (1, 10)

test2(b=10, a=1)

# 通过字典接收额外传入的命名参数
def test2(a, b, **other):
    print(a, b)
    print(other) #  输出字典{'c': 100, 'd': 1000}

test2(b=10, a=1, c=100, d=1000)

与JavaScript一样,Python是按值传参的,

  • 对于基础类型的参数而言,传递的是字面量
  • 对于引用类型的参数而言,传递的是对象引用(内存地址)

函数的默认参数需要遵循下面规则

  • 需要从右往左书写默认参数,否则无法被解析
  • 默认值只被赋值一次,也就是说,如果默认参数是一个引用对象(比如数组),在后面的函数调用中都会在这一个对象上进行操作(而不是每个函数都具备独立的引用对象参数)

2.2. 代码语句

赋值

Python支持连续赋值,且支持多重赋值

x = y = 1
a,b = 1,2

条件语句

Python是以缩进和冒号来表示语句块的,在条件语句中可以使用if...elif..else的语法

a = 9
if a < 10:
    print("less")
elif a == 10:
    print("equal")
else:
    print("more")

循环语句

与其他语言类似,可以使用while语句

i = 0
while i < 10:
    print(i*i)

python中不支持++操作符,因此使用for...in...循环有一些特殊的技巧

for i in range(1,10):
    print(i)

2.3. 变量作用域

函数的作用域:函数调用会为函数局部变量生成一个新的符号表。 确切地说,所有函数中的变量赋值都是将值存储在局部符号表。变量引用首先在局部符号表中查找,然后是包含函数的局部符号表,然后是全局符号表,最后是内置名字表。 因此,全局变量不能在函数中直接赋值(除非用 global 语句命名),尽管他们可以被引用。

a = 100

def test():
    global a # 声明此处使用全局变量a
    a = 10 # 对全局变量a进行修改

print(a) # 100
test()
print(a) # 10

在python3中增加了nonlocal关键字

a = 100

def test():
    a = 10 # 如果此处为定义a,则使用nonlocal会报错
    def foo():
        nonlocal a # 声明使用外层作用于的变量a
        a = 20
    print(a) # 10
    foo()
    print(a) # 20

print(a) # 100
test()
print(a) # 100 

2.4. 切片

切片是Python语法中一个比较有趣的地方。

a = "hello"
print(a[1:2])
  • 切片是一个左闭右开区间
  • 切片时的索引是在两个字符 之间 。左边第一个字符的索引为0,,而长度为 n 的字符串其最后一个字符的右界索引为 n,第三个参数可指定切片移动步长

字符串

Python的字符串不允许单独更改某个字符,但是可以使用灵活使用切片和字符串拼接获得新的字符串

a = "hello"
print(a[:(len(a)-1)] + "xx") # hellxx
print(a) # hello

字符串切片后,并未修改之前的字符串,而是返回了一个新的字符串

列表

列表与JS中的数组类似,其中的元素类型不必一致,可以通过下标获取列表元素的值,并可以直接对其进行修改。

对列表进行切片,返回的也是一个新的列表,对原列表没有影响。但是,如果是对切片进行赋值操作,则会修改原列表的值

a = [100,1,3,"ssds"]
a[:1] = ["xxx"] 
print(a) # ['xxx', 1, 3, 'ssds']

需要注意的是,对列表切片进行赋值,该值必须是一个可以进行切片的值(比如字符串,列表),并且在赋值之前会对该值先进行切片,再赋值到原列表切片上

a = [100,1,3,"ssds"]
a[:1] = "xxx"
print(a) # ['x', 'x', 'x', 1, 3, 'ssds']

因此,如果直接为切片赋值一个数值是会报错的,因为数字不能直接切片

2.5. 输入与输出

在print后加入一个逗号可以禁止输出换行

print "How old are you",
age = input()

# 等价于
age = input("how old are you")

获取程序参数

# 1.py
from sys import argv

script, first = argv
print(script)
print(first)

# 在运行时传入参数
python3 1.py 1
# 输出 1.py 1

3. 类

参考

Python是一门面向对象的语言,如果了解其他面向对象的语言,则只需要掌握python一些特定的语法即可

class Test:
      # 封装
    x = 0 # 公有变量
    __y = 100 # 私有变量,只能在当前类的方法中使用,无法在实例对象或子类中使用

    # __init__为构造函数名称,在实例化对象时调用
    def __init__(self, x):
        print('hello init')
        self.x = x
    # 类方法第一个参数均为self
    def hello(self):
        print(self.x, self.__y)

t = Test(10)
t.hello()

# 继承
class Sub(Test):
    z = 20
        # 多态
    def hello(self):
        super(Sub, self).hello() # 调用父类的同名方法
        print("sub hello")

    def greet(self):
        print(self.__y) # 报错,无法访问父类的私有属性

s = Sub(20)
s.hello()

除了__init__之外,还有很多类的专有方法,如__add____sub__等等,可以用来实现运算符重载。

4. 模块和包

参考

4.1. 模块

模块是一个*.py文件,一个模块对外暴露一个或多个接口。

我们通过例子来学习模块,首先新建一个mod1.py文件

# mod1.py
def test():
    print("hello test module")

然后在该文件同级目录新建测试文件main.py

import mod1
mod1.test() # 调用模块中的方法

# 可以替换模块别名
import mod1 as mod 
mod.test()

此时的目录结构为

-- mod1.py
-- main.py

运行main.py后会发现在当前目录自动生成了一个__pycache__的目录,其内部包含一个mod1.cpython-37.pyc的文件。import mod1的本质是将mod1.py文件中的所有代码加载到内存中,并在当前文件main.py中声明并定义与模块同名的变量。

此外,还可以实现局部引入

# 导入模块的部分方法
from mod1 import test
test()

# 导入模块的全部方法
from mod1 import * 
test() # 无需添加moduleName

4.2. 引入其他目录的模块

接下来,在该级目录下新建一个目录module,然后将mod1.py文件移动至module下,此时的目录结构为

-- module
    -- mod1.py
-- main.py

然后重新运行代码,就会出现错误提示

ModuleNotFoundError: No module named 'mod1'

为了解决这个问题,我们首先需要了解python中的模块查找机制,在模块导入的时候

  • 默认先在当前目录下查找
  • 然后再在系统中查找,系统查找的范围是:sys.path下的所有路径,按顺序查找。

因此为了引入module/mod1.py模块,我们可以通过sys.path实现

import sys,os

# 追加sys.path
pwd = os.popen('pwd') # 获取当前main.py脚本运行的目录
dir = pwd.readline().strip()
sys.path.append(dir + '/module')

# 引入 ./module/ 目录下的模块
from mod1 import test
test()

通过pip install安装第三方模块时,默认安装目录在Python安装路径/Lib/site-packages下,该路径默认在sys.path中,因此可以直接在代码中通过import导入

4.3. 包

在上面的例子中展示了通过修改sys.path来引入其他目录模块的方式。除此之外,我们还可以通过的形式来引入。

module目录下新增一个__init__.py的文件,这个文件可以不用写任何东西,然后修改一下main.py中的代码

from module import mod1
mod1.test() # 正常调用mod1模块中的方法

此时module目录变成了一个,导入包的本质就是执行该包下的__init__.py文件,换言之,一个包可以理解为一个包含__init__.py文件的目录。

在执行文件后会在module目录下自动生成__pycache__目录,然后就可以通过from packageName import moduleName的形式引入包了。

4.4. 包管理工具pip

除开python内置十分强大的标准库,丰富的第三方库也是python一个非常大的优势:大部分功能都可以在pypi的某个包中找到,而无需我们重新实现一遍。

npm类似,python使用pip管理第三方库, Python 3 版本 >=3.4 开始已内置了pip。

requirements.txt

在多人合作开发等跨机器的项目中,我们需要保证该项目的依赖环境是一致的,与package.json类似,python使用requirements.txt来声明当前项目依赖的包列表,该文件可以通过下面命令生成

pip freeze > requirements.txt 

然后将该文件提交到代码仓库中;就当在新的开发环境中拉取代码后,就可以通过下面命令安装依赖

cd 项目目录
pip install -r requirements.txt # 根据当前项目的requirements.txt安装依赖

4.5. venv虚拟环境

由于python的模块查找机制,pip安装的模块是全局安装的,这导致在同一个开发环境的两个python项目使用的依赖包都是安装在全局site-packages下。

如果要想实现两个项目使用不同版本的包,则可以通过venv搭建虚拟的python运行环境来实现,安装在虚拟环境中的包,不会对环境外的其他包造成影响。

# 创建虚拟环境
python3 -m venv venv # 会在当前目录新增一个venv的文件夹,里面保存着虚拟环境相关的数据

# 进入venv环境 bash模式
. venv/bin/activate
# 然后安装依赖
pip3 install xxx
# ...在虚拟环境执行其他操作

# 退出虚拟环境
deactivate

结合虚拟环境venv和依赖描述文件requirements.txt,我们就可以在虚拟环境中生成只与当前项目相关的描述文件,从而避免初始化项目依赖时污染全局模块。

5. 小结

本文主要整理了Python的基本语法知识,用以备忘。