Python 学习笔记(基础篇)

背景:今年开始搞 Data science ,学了 python 小半年,但一直没时间整理整理。这篇文章很基础,就是根据廖雪峰的 python 教程 整理了一下基础知识,再加上自己的一些拓展,方便自己以后查阅。

一、基础


1、简介

(1) 历史

Python是著名的“龟叔”Guido van Rossum(荷兰人)在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言。

2021-04-06-23-41-59

(2) 解释型语言

Python是解释型语言,跟 C 这种编译型语言相比:

(1)第一个缺点就是运行速度慢,在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。而C程序是运行前直接编译成CPU能执行的机器码,所以非常快。

但是大量的应用程序不需要这么快的运行速度,因为用户根本感觉不出来。

(2)第二个缺点就是代码不能加密。如果要发布你的Python程序,实际上就是发布源代码,这一点跟C语言不同,C语言不用发布源代码,只需要把编译后的机器码(也就是你在Windows上常见的xxx.exe文件)发布出去。要从机器码反推出C代码是不可能的,所以,凡是编译型的语言,都没有这个问题,而解释型的语言,则必须把源码发布出去。

但目前的互联网时代,靠卖软件授权的商业模式越来越少了,靠网站和移动应用卖服务的模式越来越多了,后一种模式不需要把源码给别人。

2、安装

以 MacOS 为例:

方法一: brew install python3

Mac系统若是OS X>=10.9,那么自带就有Python,版本为2.7。但还是推荐安装python3,如版本3.7.0

方法二【推荐】:直接安装 Anaconda (内置 python)。详情可看我之前的一篇: Anaconda / Conda 实践

下面都以 python 版本为 3.7 为例展开。

3、运行

# 进入交互命令行 [ ctrl + D / exit() 退出命令行 ]
python 


# 命令行运行一个.py文件
python hello.py

在安装 Anaconda 后,python 和 python3 这两个命令其实调用的都是同一个 python 3.7 文件。所以没有差别。

4、Python 解释器

(1)CPython

CPython是使用最广的Python解释器。当我们从Python官方网站下载并安装好Python 3.x后,我们就直接获得了一个官方版本的解释器:CPython。这个解释器是用C语言开发的,所以叫CPython。在命令行下运行python就是启动CPython解释器。

(2)IPython

IPython是基于CPython之上的一个交互式解释器,也就是说,IPython8只是在交互方式上有所增强,但是执行Python代码的功能和CPython是完全一样的。好比很多国产浏览器虽然外观不同,但内核其实都是调用了IE。

比方说CPython用>>>作为提示符,而IPython用In [序号]:作为提示符。

5、注意点

Python使用4个空格的缩进来组织代码块。

vscode 里会自动把 1个 tab 会转化成4个空格。

Python 没有 ===全等符,只有==双等符。

Python 没有 ++自增符和 --自减符。

if 和 function 语句都不能为空,若真想为空,需要加上pass

二、数据类型


1、布尔值

在 Python2 中是没有布尔型的,它用数字 0 表示 False,用 1 表示 True。到 Python3 中,把 True 和 False 定义成关键字了,但它们的值还是 1 和 0,它们可以和数字相加。

print(False+True+True)     # 2  

支持 andornot运算。

类似于 JavaScript 的 && || !

print(5 > 3 and 3 > 1)     # True

2、字符串

# 单引号
print('abc')
# 双引号
print("abc")

# 多行内容 (会保留格式)
print('''line1

                line2

line3''')

# 转义
print('I\'m ok.')        # I'm ok.

# 拼接(用 + 号)
print("123" + "123")    # 123123

# 查找(-1表示找不到)
'product_name'.find("product")    # 0 

3、None - 空值

Python中None是唯一的。即:print(None == None) 为 True。

类似于 JavaScript 的 null ,python 没有 undefiend的概念,所以这样是非法的:

abc
print(abc) # 报错

4、数字

没啥好说的,需注意除法:

# / 普通除法,不管如何,结果都是浮点数
9 / 3        # 3.0

# // 地板除法,只有包含了浮点数,结果才是浮点数
10 // 3     # 3
10.0 // 3     # 3.0

5、无限

float("inf")   # 正无穷
float("-inf")  # 负无穷

6、NaN(Not a Number)

float("nan")        

math.isnan(float("nan"))    # True

7、list 和 tuple

(1)list

类似于 JavaScript 的 数组

# 建
classmates = ['Michael', 'Bob', 'Tracy']
classmates = []                    # []
classmates = list()                # []
classmates = list(range(5))     # [0, 1, 2, 3, 4]

# 属性
len(classmates)        # 3

# 取(会抛错)
classmates[0]        # Michael
classmates[-1]        # Tracy

# 取(不会抛错)
# 暂不支持,建议用 try...catch 来设置默认值
try:
    a = classmates[4]
except IndexError:
    a = 'default'

# 存
classmates.append('Adam')
classmates.insert(1, 'Jack')  

# 删
classmates.pop()
classmates.pop(1)

# ------

# 查 (会抛错)
try:
    thing_index = [1, 2, 3].index(12)
except ValueError:
    thing_index = -1

# 查 (不会抛错)   
4 in [1, 2, 3]       # False

# ------

# 遍历
for item in classmates:
    print(item)  

# 遍历 (带索引值)
for i, item in enumerate(classmates):
    print(item,i)

坑:关于字符串

# 字符串其实是一个 list

a = "asd"
a[2]        # 'd'

特性:列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

# 要生成 list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 可以用 list(range(1, 11))
[x * x for x in range(1, 11) if x % 2 == 0]    
# [4, 16, 36, 64, 100]
(2)tuple - list 的不可变版本
# 建
classmates = ('Michael', 'Bob', 'Tracy')
classmates = ()                    # ()
classmates = tuple()            # ()
classmates = tuple(range(5))     # (0, 1, 2, 3, 4)

classmates = tuple()

# 剩下操作同上

坑:关于逗号

# 可以用逗号快速创建
classmates = ('Michael', 'Bob', 'Tracy')
classmates = 'Michael', 'Bob', 'Tracy'
# 如果只有一个字符串元素,记得末位加逗号!因为字符串是个 list

classmates = ('Michael')
# classmates[0] : M

classmates = ('Michael',)
# classmates[0] : Michael

classmates = 'Michael',
# classmates[0] : Michael

# 注意:如果只有一个字符串元素,打印这个tuple是这样的:('Michael',)
(3)切片
# 同时适用于 list 和 tuple
a = list(range(10)) 

a[1:5]
# [1, 2, 3, 4]

a[:]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

8、dict 和 set

(1)dict

类似于 JavaScript 的 对象 ,但只是键值对。

# 建
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}

# 取(会抛错)
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
try:
    thing_index = d["Colin"]
except KeyError:
    thing_index = None

# 取(不会抛错)
d.get('Col    in')        # None
d.get('Colin',-1)        # -1

# 存
d['Colin'] = 123 

# 删
del d['Michael'] 
d.pop('Michael')

# ------

### 查 (不会抛错)   

# 查 key 是否存在
'Colin' in {'Michael': 95, 'Bob': 75, 'Tracy': 85}           # False
# 查 value 是否存在
95 in {'Michael': 95, 'Bob': 75, 'Tracy': 85}.values()    # True

# ------

# 提取

{'Michael': 95, 'Bob': 75, 'Tracy': 85}.keys()        # 返回 list
{'Michael': 95, 'Bob': 75, 'Tracy': 85}.values()     # 返回 list

# ------    

# 遍历
for item in d:
    print(item)  

# 遍历 (带索引值)
for i, item in enumerate(d):
    print(item,i,d[item])  

和list比较,dict是用空间来换取时间的一种方法。

(2)set

类似于 JavaScript 的 Set ,但只是键对

# 建
s = set([1, 2, 3])        # {1, 2, 3}

# 存
s.add(4)

# 删
s.remove(4)

### 查 (不会抛错)   
22 in s            # False

# ------    

### 操作 

s1 = set([1, 2, 3])
s2 = set([2, 3, 4])

# 交集 
s1 & s2            # {2, 3}
# 并集
s1 | s2         # {1, 2, 3, 4}

# ------    

# 遍历
for item in s:
    print(item)  

9、数据类型转换

>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34

>>> str(1.23)
'1.23' 
>>> bool(1)
True 

10、校验类型

Python中一切都是对象。

python 比 js 更极端,js 的数据类型分 基本 + 对象。而 python 全是对象,哪怕 None 也是

(1)type() - 获取类型
print(type(True))   # <class 'bool'>
print(type("1"))    # <class 'str'>
print(type(None))   # <class 'NoneType'>
print(type(2))  # <class 'int'>

# 注意,NaN 和 无限 都会被视为 float。
print(type(float("inf")))   # <class 'float'>
print(type(float("nan")))   # <class 'float'>

print(type([1,2]))  # <class 'list'>
print(type((1,2)))  # <class 'tuple'>
print(type({'Michael': 95, 'Bob': 75, 'Tracy': 85}))    # <class 'dict'>
print(type(set([1, 2, 3]))) # <class 'set'>

def a():
    pass
print(type(a))  # <class 'function'>
class b:
    pass 
# 注意:class 会被视为 type。
print(type(b))  # <class 'type'>
(2)isinstance() - 判断类型
print(isinstance(True,bool))   # True
print(isinstance("1",str))    # True 

# 这里不推荐用 isinstance 或者 == ,原因请看:https://stackoverflow.com/questions/3257919/what-is-the-difference-between-is-none-and-none
print(None is None)   # True

print(isinstance(2,int))  # True

import math
print(math.isinf(float("inf"))) # True
print(math.isnan(float("nan"))) # True

print(isinstance([1,2],list))  # True
print(isinstance((1,2),tuple))  # True
print(isinstance({'Michael': 95, 'Bob': 75, 'Tracy': 85},dict))    # True
print(isinstance(set([1, 2, 3]),set)) # True

import types 
def a():
    pass
print(isinstance(a,types.FunctionType))  # True

# 关于 对象 的判断,详看下面关于 对象章节 的介绍

isinstance 可以一次性判断多个类型:isinstance(x, (int, float))

11、判断相等

== 比较操作符,用于判断 value(值) 是否相等

对 list/tuple/dict/set 等不适用。

is 同一性运算符,用于判断 内存地址 是否相等

id() 可以查看一个变量的内存地址。

12、其它

(1)collections 模块

collections是Python内建的一个集合模块,提供了许多有用的集合类

如 队列和栈,更好用的封装过的 dict 等。

(2)待写

生成器(generator) & 迭代器(Iterator)

三、函数


1、参数定义类型

(1)位置参数 & 默认参数 & 返回值
def my_abs(x, y=0, z=1):

    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')

    # 返回单个值
    # if x >= 0:
    #     return x
    # else:
    #     return -x

    # 返回 tuple
    return x, y, z   # 等于 return (x, y)
    # (哪怕函数里没有 return ,也会默认 return
    return  # 等于 return None


print(my_abs(-3))
print(my_abs(-3,2))
print(my_abs(-3,z=2)) # 跳过 y 而给 z 赋值,可这样写

注意:默认参数需要出现在所有位置参数的右边

坑:函数对引用对象都是引用传递,但如果默认参数也是引用类型,那么要小心每次调用函数,它都是同一个

def add_end(L=[]):
    L.append('END')
    return L

# 建议改造成下面这样:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
(2)可变参数 & 关键字参数

# 可变参数 —— 传list或tuple,接受tuple
def my_fun1(x, *y):
    return x, y


print(my_fun1(0))   # (0, ())
print(my_fun1(0, 1, 2, 3, 4, 5))    # (0, (1, 2, 3, 4, 5))
print(my_fun1(0, *(1, 2, 3, 4, 5)))  # (0, (1, 2, 3, 4, 5))

# 关键字参数 —— 传dict,接受dict
def my_fun2(x, **y):
    return x, y


print(my_fun2(0))   # (0, {})
# (0, {'city': 'Beijing', 'job': 'Engineer'})
print(my_fun2(0, city='Beijing', job='Engineer'))
# (0, {'city': 'Beijing', 'job': 'Engineer'})
print(my_fun2(0, **{'city': 'Beijing', 'job': 'Engineer'}))

注意:可变参数 和 关键字参数 接受后的 list/tuple/dict 虽然都是引用类型,但是却不是引用传递而是值传递(复制了后的值)。

还有一个命名关键字参数,不介绍了,待写。

(3)总结

注意:

1、参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

2、虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。

2、函数式编程

Python对函数式编程提供部分支持。因此,Python不是纯函数式编程语言。

待写

四、模块与包


1、模块

在Python中,一个.py文件就称之为一个模块(Module)

在 CommonJS 世界中,大多数情况下,一个文件也是一个模块。


下面我们自定义一个自己的模块,并尝试导入并调用。

(1)定义

myModule.py

#!/usr/bin/env python3    # 可以让此文件直接在Unix/Linux/Mac上运行

' it is my module'    # 模块的介绍(规范)

__author__ = 'xjnotxj'    # 模块的作者(规范)

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])    
    else:
        print('Too many arguments!')


def test2():
    print("test2")

test3 = "123"

if __name__=='__main__':
    test()

if __name__=='__main__' 这句话的意思是,如果我们直接运行myModule.py 在别处 import myModule,都会调用 test() 函数。而后者不是我们所希望的,加了这句就可以防止。

(2)引用

test.py

import myModule

myModule.test()
myModule.test2()
print(myModule.test3)

假设 myModule.pytest.py 在同一目录下,即:

  • myModule.py

  • test.py

当我们运行python test.py 111,打印出:

Hello, 111!
test2
123

还可以 import 模块里的部分,即 test.py 改为:

from myModule import test

test()  

注意:模块名不要和系统模块名冲突(包名也不要冲突),最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。

2、包

(1)定义

还以上面的myModule.py为例。但是目录结构发生了变化:

  • myModule

    • __init__.py

    • myPackage.py

  • test.py

这里的 myModule 文件夹,即为 包(Package),他可以按目录来组织模块。

(2)引用

test.py 改为:

import myPackage.myModule  
myPackage.myModule.test()

# 或者使用 as 重命名

import myPackage.myModule as myModule
myModule.test() 

注意:每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是 myModule

3、安装第三方模块

(1)pip / pip3
(2)Anaconda

推荐。详情可看我之前的一篇: Anaconda / Conda 实践

(3)模块搜索路径

默认情况下,Python解释器会搜索:

  • 当前目录

  • 内置模块所在目录

  • 第三方模块所在目录

可以在sys模块的path变量中查看这些目录路径:

>>> import sys
>>> print(sys.path)

如果我们自定义了模块/包,需要被引用,但不在上面所说的默认搜索范围内,那么有两种方法:

1、临时修改(在运行时修改,运行结束后失效。)

直接修改sys.path

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

2、长期修改

设置环境变量 PYTHONPATH

4、依赖管理

(1)生成依赖文件
# 生成所有的依赖
pip freeze > requirements.txt

# 生成当前目录下的依赖(需提前安装:pip install pipreqs)
pipreqs ./

执行后,会在当前目录生成 requirements.txt 依赖文件,内容长这样:

alembic==1.0.10
appnope==0.1.0
astroid==2.2.5
……
(2)安装依赖文件

pip install -r requirements.txt

5、环境管理

推荐用 Anaconda 自己的环境管理功能代替上面的依赖管理 或 virtualenv。详情可看我之前的一篇: Anaconda / Conda 实践

五、面向对象


1、基本概念

下面的 demo 涉及类、实例化对象、类变量/实例变量、公有/私有变量。

class Student(object):

    # 类变量
    class_name = "七年二班"

    # 类方法
    @classmethod
    def class_foo(cls, x):
        print("executing class_foo(%s, %s)" % (cls, x))

    # 静态方法
    @staticmethod
    def static_foo(x):
        print("executing static_foo(%s)" % x)

    def __init__(self, name, score):
        # 公有 实例变量(可以通过.访问)
        self.name = name
        # 私有 实例变量,可被继承(只是一种规范,还是可以通过.访问)
        self._score = score
        # 私有 实例变量,不可被继承(不可以通过.访问,但可以通过 hack 方法:例如 _Student__max_score))
        self.__max_score = 100 

    # 使 score 可以 get
    @property
    def score(self):
        return self._score

    # 使 score 之可以 set
    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError("score must be an integer!")
        if value < 0 or value > 100:
            raise ValueError("score must between 0 ~ 100!")
        self._score = value

    def print_score(self):
        print("%s: %s(满分%s)" % (self.name, self._score, self.__max_score))


jay = Student("周杰伦", 59)
quinlivan = Student("昆凌", 59)

# start do something……

print(Student.class_name)
jay.class_foo("aaa")
jay.static_foo("bbb")

jay.print_score()
quinlivan.print_score()

quinlivan.score = 87
quinlivan.print_score()

输出:

七年二班
executing class_foo(<class '__main__.Student'>, aaa)
executing static_foo(bbb)
周杰伦: 59(满分100)
昆凌: 59(满分100)
昆凌: 87(满分100)
(1)操作变量的辅助方法 —— hasattr()、getattr()、setattr()
# 判断是否存在 变量的方法 —— hasattr()
print(hasattr(jay,"score"))

# get 变量的方法 —— getattr() 
print(getattr(jay,"score"))
print(getattr(jay,"score",99))    # 默认值

# set 变量的方法 —— setattr()
jay.score = 99 
setattr(jay,"score",99) 
print(getattr(jay,"score"))

在对象中取不存在的变量会报错,建议可以使用getattr()带默认值的方式。

(2)操作方法
from types import MethodType

# 定义一个函数作为实例方法
def say_hello(self): 
    print("大家好,我是"+self._name)

# 给实例绑定一个方法
quinlivan.say_hello = MethodType(say_hello, quinlivan) 
quinlivan.say_hello()

# 给类绑定一个方法
Student.say_hello = say_hello
jay.say_hello()

2、继承和多态

(1)基本概念

class Animal(object): 
    def run(self):
        print('Animal is running...')

class Dog(Animal): 
    def run(self): 
        print('Dog is running...')

class Cat(Animal): 
    def run(self): 
        print('Cat is running...')

class Duck(Animal): 
     pass

dog = Dog() 
dog.run() 
cat = Cat()
cat.run()
duck = Duck()
duck.run()

多态调用方只管调用,不管细节

解释:因为你知道这个子类是Animal类型,所以你只管调用 run() ,具体 run() 的实现细节,由它run()的具体实现决定,哪怕没有,也可以往上追溯父类的实现。

(2)多重继承

用途:我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。这种设计通常称之为MixIn

如下面的例子,新建一个狗,属于动物,同时引入宠物的功用。


class Animal(object): 
    def run(self):
        print('Animal is running...')

class Pet(object): 
    def stay(self):
        print('I am a pet, I can stay at home....')

class Dog(Animal,Pet):  
    pass

dog = Dog() 
dog.run()  
dog.stay()  

3、常用方法

(1)判断变量属于什么类型 —— isinstance()

上面的 数据类型 章节里有提到 isinstance(),但对于 对象/类 的使用,在这里介绍:

# 还以上面的代码【2、继承和多态】为例:

class Animal(object): 
    def run(self):
        print('Animal is running...')

class Dog(Animal): 
    def run(self): 
        print('Dog is running...')

class Cat(Animal): 
    def run(self): 
        print('Cat is running...')

class Duck(Animal): 
     pass

dog = Dog() 
dog.run() 
cat = Cat()
cat.run()
duck = Duck()
duck.run()

# 可以匹配自己的类
print(isinstance(dog, Dog))             # True

# 可以匹配自己的父类
print(isinstance(dog, Animal))          # True

# 可以同时匹配多个值(有一个满足即为 true) 
print(isinstance(dog, (Dog,Animal)))    # True
(2)获取一个变量的所有属性和方法
# 还以上面的代码【1、基本概念】为例:

class Student(object):

    # 类变量
    class_name = '七年二班'

    def __init__(self, name, score):
        # 公开 实例变量
        self.name = name
        # 私有 实例变量
        self.__score = score

    def get_score(self):
        return self.__score

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

    def print_score(self):
        print('%s: %s' % (self.name, self.__score))

jay = Student('周杰伦', 59)  

print(dir('ABC'))

print(dir(Student))
print(dir(jay))

输出如下:

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith','expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'class_name', 'name', 'print_score', 'score']

['_Student__max_score', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_name', '_score', 'class_name', 'name', 'print_score', 'score']

实例化对象比类多了 私有变量 的属性。

4、定制类

(1)打印对象 —— __str__() __repr__()

改造前:

class Student(object): 
    pass

print(Student())    # <__main__.Student object at 0x1041985c0>

改造后:

class Student(object):  
    def __str__(self):
        return 'Student object'
    __repr__ = __str__

student = Student()
print(student)    # __str__负责打印的值:Student object
student    #__repr__负责调试的值:Student object
(2)可调用对象 —— __call__()
class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
        
s = Student('Michael')
s() # My name is Michael.

延伸:还记得 python 一切皆对象吗?其实 python 的 函数 就是拥有 __call__() 的对象。所以,我们可以用callable(var) ,来判断一个变量是不是函数

对象和函数的界限模糊了。

(3)其他

__len__() 作用于len()

__iter__() 作用于for ... in循环

__getitem__()__setitem__()__delitem__()作用于 list 下标(如f[3]

注意: f[5:10] 传 slice(切片) 和 f[-1] 传 负数 都需要额外小心的处理。

5、枚举类

枚举类的好处:

  • 不可修改

  • 加了 @unique 可以防止重复项

  • 形成逻辑共同体

  • 增加代码可读性

枚举类的每个枚举项,分 key 和 value。

注意:Weekday.Tue 并不能取到实际值,需要再调用 .value。

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

# 取枚举项的 key
print(Weekday.Tue)  # Weekday.Tue
print(Weekday['Tue'])  # Weekday.Tue
print(Weekday(1))  # Weekday.Mon
# 取枚举项的 value
print(Weekday.Tue.value)  # 2

print(type(Weekday))  # <class 'enum.EnumMeta'>
print(type(Weekday.Tue))  # <enum 'Weekday'>

# 判断相等
print(Weekday.Mon == Weekday.Mon)  # True
print(Weekday.Mon.value == Weekday.Thu.value)  # False

# 判断 key,value 是否在枚举类中
print("Thu" in Weekday.__members__) # True
print(6 in Weekday._value2member_map_) # True

# 遍历枚举类
for name, member in Weekday.__members__.items():
    print(name, '=>', member, member.value)
# Sun => Weekday.Sun 0
# Mon => Weekday.Mon 1
# Tue => Weekday.Tue 2
# Wed => Weekday.Wed 3
# Thu => Weekday.Thu 4
# Fri => Weekday.Fri 5
# Sat => Weekday.Sat 6

6、元类

涉及:type()、metaclass 以及 实现一个简单的 ORM。

待写

六、错误 & 调试 & 测试


1、错误处理

# 自定义异常类
class NewZeroDivisionError(ZeroDivisionError):
    pass

try:
    print('try...')
    r = 10 / int('0') # 会被 ZeroDivisionError 捕获
    # raise NewZeroDivisionError('NewZeroDivisionError is throw') # 会被 ZeroDivisionError 捕获
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
except BaseException as e:
    print('BaseException:', e)
    raise   # 原样抛出
else:
    print('no error!')
finally:
    print('finally...')
print('END')
  • Python 的错误是 class 类型,都继承自 BaseException,所以 except BaseException 会把所有异常类“一网打尽”。

  • 只有在必要的时候才定义我们自己的错误类型(如上面代码里的 NewZeroDivisionError )。尽量使用 Python 内置的错误类型(比如ValueError,TypeError)。更多错误类型请查看:https://docs.python.org/3/library/exceptions.html#exception-hierarchy

2、调试

(1)print

缺点:代码不能删除

(2)assert
a = 1
b = 2
c = a + b
assert c == 3, 'c is calculation error!'

注意:如果断言失败,则抛 AssertionError 异常。

特点:代码虽然跟 print 一样也不能删除,但是可以忽略执行,即用 python -O test.py

(3)logging

推荐。用 python 自带的 logging 模块。详情可参考我之前的一篇: Node.js / Python 日志

(4)vscode

推荐。结合 vscode 做调试更方便。

3、单元测试 & 文档测试

待写

七、IO 编程


1、文件读写

# read / read(size)
with open('./log.txt', 'r') as f:
    print(f.read())
    # print(f.read(size)) # size 表示一次读取的最大字节数

# readlines
with open('./log.txt', 'r') as f:
    for line in f.readlines():
        print(line.strip())

# ------------

# write 
with open('crawler/demo/tutorial/spiders/log.txt', 'w') as f:
    f.write('1\n2\n3\n')
    # 两者相等
    f.write('1\n')
    f.write('2\n')
    f.write('3\n')

# writelines - 参数是序列
with open('crawler/demo/tutorial/spiders/log.txt', 'w') as f:
    f.write('1')
    f.write('2')
    f.write('3')
    # 两者相等
    f.writelines(['1','2','3']) 
    

with 的用法下面会提到。

1、“r” 可以换成:

  • r - 读

  • rb - 读二进制

  • w - 写

  • wb - 写二进制

  • a - 写追加

2、open() 的默认编码是 utf-8,或者可以手动指定:open('./log.txt', 'r', encoding='gbk')

2、StringIO 和 BytesIO

StringIOBytesIO 是用读写文件一致的接口在内存中操作str和bytes的方法(而无需从存储里读取,即无 open 操作)。

StringIO() / BytesIO() 返回值 = open() 返回值

应用:比如你要从某个接口下载一个文件,只做解析工作获取你想要的信息就够过了,无需保存。那麻烦的是,下载后还要手动删掉它。现在则没有那么麻烦,直接内存中就搞定,同时代码也不用修改,还用原来的读写文件的接口就好。

# 用 StringIO 写 string
from io import StringIO
f = StringIO()
f.write('hello')
f.write(' ')
f.write('world!')
print(f.getvalue())

# 用 StringIO 读 string
from io import StringIO
f = StringIO('Hello!\nHi!\nGoodbye!') 
for line in f.readlines():
    print(line.strip())

# ------

# 用 BytesIO 写 bytes
from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8')) 
print(f.getvalue()) 

# 用 BytesIO 读 bytes
from io import BytesIO
f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') 
print(f.read().decode('utf-8')) 

3、操作文件和目录 & 获取 OS 信息

os 模块可获取 操作系统信息、环境变量、操作文件和目录 等。

坑:但是复制文件的函数居然在os模块中不存在!幸运的是 shutil 模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。

第三方 psutil 模块还可以获取 CPU、内存、磁盘、网络、进程等信息。

4、序列化 (Serialization)

我们把变量从内存中变成可 存储 或 传输 的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

拓展:存储的过程叫持久化(persistence)

(1)pickle
from io import BytesIO
import pickle

d = dict(name='Bob', age=20, score=88)
picklingData = pickle.dumps(d)
# print(picklingData) # picklingData 已经变成二进制。可以把 picklingData 持久化或传输。

f = BytesIO(picklingData)
d_new = pickle.load(f) # 注意 pickle.load 的参数是 file (即 open() 返回值)
print(d_new)

缺点:Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容。

(2)JSON
import json
d = dict(name='Bob', age=20, score=88)
json_str = json.dumps(d)
print(json_str)
# {"name": "Bob", "age": 20, "score": 88}

print(json.loads(json_str))
# {"name": "Bob", "age": 20, "score": 88}

优点:JSON 跟 Pickle 比的好处就是跨平台。推荐使用。

(3)XML

待写

拓展:with 语句

上面的 文件读写 的代码中,有个 with 语句,他的作用是 open() 后自动调用 f.close() 这个函数,避免手写的繁琐。具体如下:

with open('./log.txt', 'r') as f: 
    print(f.read())

# 两者相等

try:
    f = open('./log.txt', 'r')
    print(f.read())
finally:
    if f:
        f.close()

并不是只有 open() 函数返回的f对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,即 class 内部有 __enter____exit__ 函数,就可以用于with语句。例如:

class Query(object):

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Begin')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')

    def query(self):
        print('Query info about %s...' % self.name)

with Query('Bob') as q:
    q.query()

# 打印出:
Begin
Query info about Bob...
End

更多上下文管理的功能,可使用 contextlib 内置模块。

posted @ 2019-12-02 19:46  小蒋不素小蒋  阅读(93421)  评论(0编辑  收藏  举报

ICP证:沪ICP备20014317号