大学时,学习Python爬虫,去图书馆借了一本Python基础书,不厚,因为有 matlab
和 C语言
基础,这本书一个星期看完了,学完后感觉Python入门很快,然后要开始学爬虫和矩阵计算,学习一下对应的包就行了,感觉很方便,爱上了这门语言,但时工作中也会用一些Python。不过初学者有很多基础知识点记不住,因为用得少,这里总结记录一下。
Python 是一门独特的语言,快速浏览一下他的要点:
变量复制的一个例子。
a = [1, 2] b = a print(id(a) - id(b)) # 地址差为 0,表示实质是同址的 0
b.append(3) print(a) # 只改动了 b,但 a 也跟着变动了 [1, 2, 3] a is b True
使用切片来重新分配空间:
a is a[:] False
Python 原生的数据结构包括:
细分为整数(int)与浮点数(float)两种。
首字母大写 True / False.
变量 x | x = [] | x = 0 | x = 2 |
---|---|---|---|
bool(x) | False | False | True |
if x: … | False | False | True |
if x is None: … | False | False | False |
序列主要包括 字符串(str)、列表(list)与元祖(tuple) 三类。
写于一对双引号或单引号内。用 str() 可以强制转换为字符串。
r"c:\new"
.**.split()
与 **.join()
.s = " I love Python" # 首位是空格 lst = s.split(' ') lst1 = '-'.join(lst) print(lst, '\n', lst1) ['', 'I', 'love', 'Python'] -I-love-Python
strip()
去掉字符串首尾两端的空格。方法 lstrip()/rstrip()
则只切除首端/尾端的空格。s.strip() 'I love Python'
'I like {} and {}'.format('Python', 'you') 'I like Python and you' '{0} + {2} = {1}'.format (10, 20, 'Python ') # 按顺序引用 '10 + Python = 20' '{0} * {1} = {0}'.format (10, 'Python ') # 编号反复引用 '10 * Python = 10'
格式化控制码:
控制码 | 含义 | 控制码 | 含义 |
---|---|---|---|
:unamused: | 字符串 | :c | 单个字符 |
:b/o/x/d | 二、八、十六、十进制数 | :e/f | 科学计数法/浮点数 |
一些复杂控制的例子: |
例子 | 含义 | 例子 | 含义 |
---|---|---|---|
:.2f/:+.2f | 两位小数/带符号两位小数 | : .2f | 正数前补空格的两位小数 |
:, | 逗号分隔符 | :.2% | 百分比两位小数 |
:.2e | 科学计数法两位小数 | :^4d | 总宽四位居中对齐 |
:>4d/<4d | 总宽四位左/右对齐 | :0>4d | 总宽四位左侧补零 |
举例: |
"{:0>7.2f} is an odd number".format(123.4) # 总宽 7 位小数点后 2 位,左侧补零 '0123.40 is an odd number'
其他实用的字符串函数:
中括号式的结构。 list()
用于强制转换类型。
lst = [1, 2, 3] print(lst) [1, 2, 3] # 【反转】:其中第二种方式会更改现有的列表 lst1 = list(reversed(lst)) lst.reverse() print(lst1, lst) [3, 2, 1] [3, 2, 1] # 【追加】:元素 append(),另一个列表:extend() lst.append(4) print(lst) [3, 2, 1, 4] lst.extend(lst1) print(lst) [3, 2, 1, 4, 3, 2, 1] # 【插入】:lst.insert(idx, obj) 会在 lst[idx] 处插入 obj,然后依次后移原有项 lst.insert(1, 100) print(lst) [3, 100, 2, 1, 4, 3, 2, 1] # 【删除】:lst.remove(obj) 会删除首个匹配值,若无匹配会报错; # lst.pop(idx) 会返回 lst[idx],并将其删除。如果不指定 idx,默认为列表尾 lst.remove(2) print(lst) [3, 100, 1, 4, 3, 2, 1] tmp = lst.pop() print(lst, "\n", tmp) [3, 100, 1, 4, 3, 2] 1 # 【搜索】:使用序列通用函数即可。用 count(obj) 可以计算频数。 # 【排序】:sort() 方法。如果指定 reverse 参数,可降序排序。 lst.sort(reverse=True) print(lst) [100, 4, 3, 3, 2, 1] # 【清空】:clear() lst.clear() print(lst) []
圆括号式的结构,是一种不可变序列。
a = (1, 'string ', [1 ,2]) print(a) (1, 'string ', [1, 2])
Note: 定义一个空的元组用 ()
,定义只有一个元组的元组,需要加 ,
,否则就不是元组了,如下:
>>> tuple1 = () >>> type(tuple1)>>> tuple2 = (1) >>> type(tuple2) >>> tuple3 = (1,) >>> type(tuple3)
字典是一种类哈希表的数据结构,内部无序,通过键值对(key: value)的形式存储数据。几种字典初始化的方式:
# 小字典直接赋值 d1 = {"name": "wklchris", "gender": "male"} # 利用字典增加键值对的方法 d2 = {} d2['name'] = 'wklchris' # 一个值赋给多个键 d3 = {}.fromkeys(("name", "gender"), "NA") # 强制格式转换 d4 = dict(name="wklchris", gender="male") print(d1, d2, d3, d4, sep="\n") {'name': 'wklchris', 'gender': 'male'} {'name': 'wklchris'} {'name': 'NA', 'gender': 'NA'} {'name': 'wklchris', 'gender': 'male'}
字典的操作方法:
len(d1) 2 # 【复制】: dd = d1.copy() dd is d1 False # 【查找键名称】: "name" in dd True # 【删除键值对】 del(dd["name"]) # 【get】 dd.get("name", "Nothing") # 如果键不存在,返回“Nothing” 'Nothing' # 【setdefault】 dd.setdefault("name", "wklchris") # 如果键不存在,就新建该键,并赋值 'wklchris' print(dd) {'name': 'wklchris', 'gender': 'male'} # 【输出键值】: list(dd.items()) [('name', 'wklchris'), ('gender', 'male')] list(dd.keys()) ['name', 'gender'] list(dd.values()) ['wklchris', 'male'] # 【弹出键值对】:pop(key) / popitem(key) # 其中,后者会随机弹出一个键值对 tmp = dd.pop("gender") print(dd, tmp) {'name': 'wklchris'} male # 【更新】:update(ref_dict) 以 ref_dict 为准,更新当前字典 d4 = {"name": "Test", "Age": 3} dd.update(d4) print(dd) {'name': 'Test', 'Age': 3}
本文只讨论可变集合,关于不可变集合的内容,参考 help(frozenset)。
集合是一种无序的数据存储方式,且内部元素具有唯一性。集合与字典一样都可以用花括号的形式创立。但在书写 a={} 时,Python 会将其识别为字典类型。
ss = {"a", "b", "c"} ss | set("de") {'a', 'b', 'c', 'd', 'e'} ss | set(["de"]) {'a', 'b', 'c', 'de'}
同大多数程序语言一样,Python 拥有 if, for, while
语句。什么? switch
语句?使用字典就好。
在 Python 中, else if
被缩写为单个关键词 elif
.
if 1.0 > 1: a = 1 elif 1.0 < 1: a = 2 else: a = 3 a 3
值得一提的是,Python 中的 if 语句支持链式比较,形如 a < x < b, a < x >= b
等:
a = 0 if 1 < 2 > 1.5: a = 1 a 1
三元操作实质是高度简化的 if 环境,形如 X = a if flag else b
:
a = 1 if 2 < 1 else 2 a 2
Python 的循环语句中,像其他语言一样,有 break
(跳出循环体) 与 continue
(循环步进) 关键词可以使用。
for 语句借助关键词 in 使用:(函数 range(N, M=0, s=1)
是一个生成等差数列的函数,位于左闭右开区间 [M,N)
上且公差为 s)。
for i in range(3): print(i) 0 1 2
注意到字典的 d.items(), d.keys(), d.values() 命令也常常用于 for 语句:
d = {"a": 1, "b": 2, "c": 3} for k, v in d.items(): print(k, v) b 2 c 3 a 1
以上等价于:
for k in d.keys(): print(k, d[k]) b 2 c 3 a 1
Python 中的 for
语句可选 else
语法块,表示 for
语句正常结束后执行的内容(中途 break
不属于正常结束)。这对于处理一些 break 操作很有帮助。例如:
a = 0 flag = 0 for i in range(5): if i > 2: flag = 1 break if flag == 1: a = 1 a 1
这在 Python 中显得太复杂了,直接使用 for…else…
即可:
a = 1 for i in range(5): if i > 1: break else: a = 0 a 1
while 语句的 else
语法块,指明了退出 while 循环后立刻执行的内容;它不是必需的。
如果你想要将 while 语句内部的参数传出(比如下例的计数器终值),这是一个不错的方案。
count = 1 while count < 5: a = count count *= 2 else: b = count print(a, b) 4 8
列表解析是一种创建列表的高度缩写方式:
lst = [x ** 2 for x in range(4)] lst [0, 1, 4, 9]
也可以配合 if 语句:
lst = [x ** 2 for x in range(4) if x > 0] lst [1, 4, 9]
类似的,也有字典解析,以及下文会介绍的生成器,也有生成器解析(把外围的括号换成圆括号即可):
{n: n ** 2 for n in range(3)} {0: 0, 1: 1, 2: 4}
本节介绍 Python 函数的基础特点,以及一些实用函数。
使用 def
关键字。三连双引号间的内容被视为函数的帮助字符串,可以通过 help()
命令查看。
def func(a, b=0): """ This is a function that can meow. """ return " ".join(["meow"] * (a + b))
调用函数:
func(2) # 单参数,仅 a 'meow meow' func(2, 3) # 双参数, a 与 b 都被传入 'meow meow meow meow meow' help(func) Help on function func in module __main__: func(a, b=0) This is a function that can meow.
通过 callable()
可以判断一个对象是否是一个可调用的函数:
callable(func) True
利用序列(或元组)与字典,向函数传参。前者在传入时需要加上一个星号,后者需要两个。
lst = [1, 3, 4] d = {"a": 2, "b": 3, "c": 5} print("{}+{}={}".format(*lst), "{a}+{b}={c}".format(**d)) 1+3=4 2+3=5
zip() 函数的作用是“合并”多个列表为一个。其返回值是一个列表,列表内的元素类型是元组。如果待合并的列表长度不同,以最短的为准。
a = [1, 2, 3, 4] b = [5 ,6, 7] c = "abcd" list(zip(a, b, c)) [(1, 5, 'a'), (2, 6, 'b'), (3, 7, 'c')]
它比较常用于交换字典的键与值:
dict(zip(d.values(), d.keys())) {2: 'a', 3: 'b', 5: 'c'}
一种匿名函数的声明方式。如果你使用过 MATLAB
,你可能熟悉这一类概念。
func = lambda x, y: x + y func(2, 5) 7
map()
能够对传入的序列进行依次操作,并将结果返回为一个可转换为列表的 map
对象。通常列表解析(或生成器解析)可以实现与其同样的工作。
lst = list(map(lambda x: x + 1, range (5))) print(lst) [1, 2, 3, 4, 5] f = lambda x: x + 1 [f(x) for x in range(5)] [1, 2, 3, 4, 5]
给定序列,对于满足某规则的部分(即 True),予以返回。
list(filter(lambda x: x > 0, range(-3, 3))) [1, 2]
该函数在 Python 2 中是可以直接调用的,但在 Python 3 中需要从 functools
模块进行调用。
from functools import reduce reduce(lambda x, y: x + y, range (5)) # 0+1+2+3+4 10
它允许你像 d.items() 那样,用类似的方式操作列表:
a = [1, 3, 5] for i, v in enumerate(a): print("lst[{}] = {}".format(i, v)) lst[0] = 1 lst[1] = 3 lst[2] = 5
装饰器是函数的函数——传入的参数是一个函数,返回的值也是一个函数。相当于一个函数集到另一个函数集的映射,可以理解为数学意义上的算子。
首先来看一个简单的例子:函数可以被赋值给一个变量。
def pyrint(data="Python"): return data.upper() f = pyrint f() 'PYTHON'
还可以通过 __name__
来得到当前函数的名称:
f.__name__ 'pyrint'
那什么时候需要装饰器呢?比如在函数需要被重用、但又不能直接改写 def
的场合(在维护中应该不少见吧!)。例如,我们希望在返回值之前,把函数名也打印出来:
def showname(func): def subfunc(*args, **kwarg): print("FUNCTION {} called.".format(func.__name__)) return func(*args, **kwarg) return subfunc
这样如果我们通过 showname(pyrint)
这种形式,就能够在 pyrint
函数被调用之前,额外打印一行内容。
想要改动该函数,不需要改动 def 语句以下的内容,只需要用 @showname
命令来应用这个装饰器:
@showname def pyrint(data="Python"): return data.upper() pyrint() FUNCTION pyrint called. 'PYTHON'
如果装饰器需要传递参数,那么,需要在定义时,外层再嵌套一个函数:
def showname(num=1): def decorator(func): def subfunc(*args, **kwarg): print("Call time: {}. FUNCTION {} called.".format(num, func.__name__)) return func(*args, **kwarg) return subfunc return decorator @showname(2) def pyrint(data="Python"): return data.upper() pyrint() Call time: 2. FUNCTION pyrint called. 'PYTHON'
不过装饰器被应用于函数定义之前时,函数的 __name__
属性会改变。比如上例:
pyrint.__name__ 'subfunc' 使用模块 functools 来解决这一问题: import functools def showname(num=1): def decorator(func): @functools.wraps(func) # 加上这一行 def subfunc(*args, **kwarg): print("Call time: {}. FUNCTION {} called.".format(num, func.__name__)) return func(*args, **kwarg) return subfunc return decorator @showname(2) def pyrint(data="Python"): return data.upper() pyrint.__name__ 'pyrint'
迭代器与生成器在内存优化上很有意义。
迭代器最显著的特征是拥有 __iter__()
和 __next__()
方法;它像一个链表。如果它指向末尾,那么再次执行 __next__()
时会报错。一个例子:
a = [1, 2, 3] b = iter(a) print(b.__next__(), b.__next__()) # 或者使用 next(b) 1 2
实际上,Python 3 内置了一个 itertools
的库,里面有诸如 cycle
和 count
等适用于迭代器的函数:
import itertools # count: 给定首项与公差的无穷等差数列 p = itertools.count(start = 1, step = 0.5) print(p.__next__(), p.__next__()) # cycle: 周期循环的无穷序列 p = itertools.cycle(list("AB")) print(next(p), next(p), next(p)) # islice: 从无穷序列中切片 p = itertools.cycle(list("AB")) print(list(itertools.islice(p, 0, 4))) 1 1.5 A B A ['A', 'B', 'A', 'B']
请时刻注意当前指向的迭代器位置——失之毫厘,谬以千里。
生成器是迭代器的一种,其实质是定义中含有 yield 关键词的函数。它没有 return() 语句。
生成器可以直接使用类似列表解析的方式,称为生成器解析。例如:(i for i in range(10)。
def Fib(N): # 斐波那契数列 n, former, later = 0, 0, 1 while n < N: yield later former, later = later, later + former n += 1 list(Fib(5)) [1, 1, 2, 3, 5]
上例与普通的写法看上去差别不大,但实际上可以将 while 语句改写为 while True
,删除变量 n,在外部借助 itertools 的 islice
函数来截取。这在函数定义时对代码的压缩是显然的。
def iterFib(): former, later = 0, 1 while True: yield later former, later = later, later + former list(itertools.islice(iterFib(), 0, 5)) [1, 1, 2, 3, 5]
try()
语句常见的错误有以下几种:
try: a = 1 except ZeroDivisionError as e: print(e) exit() else: # 如果无错误,执行 print(a) finally: # 不管有无错误均执行 print("-- End --") 1 -- End --
其中, else
与 finally
语句都不是必需的。如果不想输出错误信息、或不能预先判断可能的错误类型,可以使用仅含 exit() 语句的 except
块。
一个 try 语法块是可以跟着多个 except
的;如果靠前的 except 捕获了错误,之后的就不会运行。 这也就是说,如果错误之间有继承关系时,子错误需要放在父错误之前尝试 except,否则子错误永远也不可能被捕获。
比如上一节的例子中, ZeroDivisionError
是 ArithmeticError
下的子错误,而 ArithmeticError
又是 Exception 下的子错误(当不清楚错误的类型时,Exception 可以捕获绝大多数错误)。关于错误的继承关系,参考:Python - Exception Hierarchy 官方页面。
一个例子:
try: a = 1 / 0 except Exception: print("Exception") exit() except ZeroDivisionError: print("ZeroDivisionError") exit() else: print("No error.") finally: print("-- End --") 输出 Exception 与 – End –。
错误在很多地方都可能发生,那是否需要在可能的地方都加上 try 语句呢?当然不是。建议只在主代码中加入 try 语句,因为 Python 会自动跟踪到错误产生的源头何在。
有时候我们想人为抛出一个错误,这是使用 raise
即可:
# raise TypeError("Wrong type.")
如果在函数中没有处理错误的语句,可能在捕获错误后将其上抛。记住,捕获错误只是为了记录错误的产生,并不意味者必须原地解决错误。
def makeerror(n): if n == 0: raise ValueError("Divided by zero.") return 1 / n def callerror(): try: makeerror(0) except ValueError as e: print("ValueError detected.") raise # 输出 "ValueError detected." 并打印错误日志 # callerror()
上面的 raise
命令没有紧跟任何参数,表示将错误原样上抛。你也可以手动指定上抛的错误类型,并不需要与原错误类型一致。甚至你可以定义一个错误(继承某一错误类):
class MyError(ValueError): print("This is MyError.") # raise MyError This is MyError.
open() 函数用于文件的读写操作。一般我们会在操作文件时,引入 os 模块(os 模块的用法参考“常用模块”一节的内容)。
import os
open()
函数常常配合 with
语法块进行使用,它会在语法块结束时自动关闭文件。该函数:
open(file, mode="r", encoding=None)
第一参数是包含文件名的路径(传入基于当前目录的相对路径,传入或者绝对路径),mode 参数是读写操作方式; encoding
是编码类型,一般取 ”utf8”
。其中,读写操作方式常用的有:
参数 | 含义 |
---|---|
“r” | (默认)读。 |
“w” | 写。该模式会覆盖原有内容;如文件不存在,会自动新建。 |
“x” | 创建新文件并写入。 |
“a” | 在已有文件的尾部追加。 |
read() / readlines()
函数 read()
将整个文件读为一个字符串,来看一个例子:
datapath = os.path.join(os.getcwd(), "data", "iris.data.csv") with open(datapath, "r", encoding="utf8") as f: rawtext = f.read() rawtext[:200] '5.1,3.5,1.4,0.2,Iris-setosa\n4.9,3.0,1.4,0.2,Iris-setosa\n4.7,3.2,1.3,0.2,Iris-setosa\n4.6,3.1,1.5,0.2,Iris-setosa\n5.0,3.6,1.4,0.2,Iris-setosa\n5.4,3.9,1.7,0.4,Iris-setosa\n4.6,3.4,1.4,0.3,Iris-setosa\n5.0,'
函数 readlines()
将整个文件读为一个列表,文件的每一行对应列表的一个元素。
with open(datapath, "r", encoding="utf8") as f: rawtext = f.readlines() rawtext[:3] ['5.1,3.5,1.4,0.2,Iris-setosa\n', '4.9,3.0,1.4,0.2,Iris-setosa\n', '4.7,3.2,1.3,0.2,Iris-setosa\n']
上述的 readlines()
函数实质等同于列表解析:
with open(datapath, "r", encoding="utf8") as f: rawtext = [line for line in f] rawtext[:3] ['5.1,3.5,1.4,0.2,Iris-setosa\n', '4.9,3.0,1.4,0.2,Iris-setosa\n', '4.7,3.2,1.3,0.2,Iris-setosa\n']
文件写入,使用 write()
函数。一个简单的例子:
with open(datapath, "w") as f: f.write("Sometimes naive.")
readline()
如果文件比较大,使用 read()/readlines()
函数直接读入可能会占用太多内存。推荐使用函数 readline()
,一种迭代器式的读取方法。
with open(datapath, "r", encoding="utf8") as f: print(f.readline().strip()) print(f.readline().strip()) 5.1,3.5,1.4,0.2,Iris-setosa 4.9,3.0,1.4,0.2,Iris-setosa
你会发现两次结果是不同的,这是因为迭代器内部的“指针”向后移动了。
怎样获取 / 移动“指针”的位置呢?使用 tell() / seek() 命令。
with open(datapath, "r", encoding="utf8") as f: print(f.tell(), f.readline().strip()) print(f.tell(), f.readline().strip()) f.seek(0) # 回到文件头 print(f.tell(), f.readline().strip()) 0 5.1,3.5,1.4,0.2,Iris-setosa 28 4.9,3.0,1.4,0.2,Iris-setosa 0 5.1,3.5,1.4,0.2,Iris-setosa
类的成员包括属性( attribute
)与方法( method
)两种。例子:
class MyClass: """ This is a class that can meow! """ animal = "cat" # An attribute def talk(self): # A method return "Meow" # An instance of the class a = MyClass() print(a.animal, a.talk()) cat Meow
上例中的 self
表示类的实例,所有类内部的方法都需要把该参数放在首位(你也不可不用 self 而使用 this 等,但是 self
是惯例)。例如, self.animal
就表示了实例的 animal 属性。这与 C# 等语言中的“this.animal”是类似的。
下例证明了 self
代表的实质是类的实例,而不是类本身。
class EgClass: def __init__(self): print(self) # 实例,有对应地址 print(self.__class__) # 类 a = EgClass() <__main__.EgClass object at 0x000002531C0AF860>
__init__()
类的构造函数是 __init__()
(左右均为双下划线),用于初始化实例。在声明实例时,该函数自动被调用。
class MyClass2: def __init__(self, animal="cat"): self.animal = animal a = MyClass2("dog") a.animal 'dog'
类的重要特性是封装性,即部分变量只能在其内部修改或访问,不能从类的外部进行处理。Python 中的封装非常简单,只要把属性或方法的名称前缀设置为双下划线即可。
由此可见,构造函数 __init__()
是最基本的一个私有方法。一个例子:
class MyClass3: def __init__(self, animal="cat"): self.__animal = animal self.__foo() def __foo(self): self.__animal = "rabbit" def show(self): print(self.__animal) a = MyClass3("dog") a.show() rabbit
如果想直接调用 __foo() 或者 __animal
,都会被禁止,产生 AttributeError
。
# a.__animal # AttributeError
要注意,前后均添加了双下划线的属性,如 name ,表示特殊属性而不是私有属性,是可以从外部访问的。
下面是一个著名的猫与狗的例子;类 Cat 与 Dog 都继承自 Animal,同时也都重载了方法 talk()。
class Animal: def talk(self): pass # 表示定义留空 class Cat(Animal): # 从Animal 继承 def talk(self): # 重写talk() print('Meow') class Dog(Animal): def talk(self): print('Woof') a, b = Cat(), Dog() a.talk() # 'Meow' b.talk() # 'Woof' Meow Woof
通过 isinstance()
函数可以判断一个对象是否是某个类(或其子类)的实例:
print(isinstance(a, Cat), isinstance(a, Animal)) True True 或者: type(a).__name__ 'Cat'
当然,类也可以多继承。写在左侧的类的属性与方法,在继承时会被优先采用。例如:
class Pet: def talk(self): print("Pet") class Cat2(Pet, Cat): pass a = Cat2() a.talk() Pet
装饰器 @property
可以被用于限制类属性的读写行为。比如,一个普通的类,如果想封装一个属性,却允许从外部读取它的值,一般我们用 getter
函数实现:
class Person: def __init__(self): self.__name = "Py" def get_name(self): return self.__name a = Person() a.get_name() 'Py'
不得不说这实在是麻烦了,代码里一堆 get 函数满天飞并不令人愉快。而且还不能忘记它是一个函数,需要在尾部加上括号。
装饰器 @property
可以将一个方法伪装成同名的属性,因此装饰了 getter
函数后,调用时就不用加上尾部的括号了:
class Person: def __init__(self): self.__name = "Py" @property def name(self): return self.__name a = Person() a.name 'Py'
而且,如果你想从外部修改该属性的值,会产生错误:
a.name = 1 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last)in () ----> 1 a.name = 1 AttributeError: can't set attribute
但同时,我们也可以指定其 setter 函数(该装饰器 @age.setter 在用 @property 装饰 age 方法后会自动生成),让属性修改成为可能,甚至附加修改条件:
class Person: def __init__(self): self.__age = 20 @property def age(self): return self.__age @age.setter def age(self, value): if not isinstance(value, int): raise ValueError("Age should be an integer.") else: self.__age = value a = Person() a.age = 30 a.age 30
不传入整数会报错:
a.age = 0.5 --------------------------------------------------------------------------- ValueError Traceback (most recent call last)in () ----> 1 a.age = 0.5 in age(self, value) 10 def age(self, value): 11 if not isinstance(value, int): ---> 12 raise ValueError("Age should be an integer.") 13 else: 14 self.__age = value ValueError: Age should be an integer.
__dict__
首先是 __dict__
属性,用于查看类的属性与方法,返回一个字典。
a = MyClass() MyClass.__dict__ mappingproxy({'__dict__':, '__doc__': '\n This is a class that can meow!\n ', '__module__': '__main__', '__weakref__': , 'animal': 'cat', 'talk': })
需要注意的是,此时实例 a 的属性没有被更改过,实例的 dict 是一个空字典:
print(a.__dict__, a.animal) {} cat
类的 __dict__
方法下的同名键,与实例具有相同值。
MyClass.__dict__["animal"] 'cat'
一旦被从外部更改,实例 a 的 dict 字典就不再为空。
a.animal = "dog" print(a.__dict__, a.animal) {'animal': 'dog'} dog
__slots__
从上面可以看到,非私有的类属性可以从外部更改值,而且属性还能直接从外部增加。 slots 属性的作用就在于使类的属性不能从外部进行更改、追加。它能够限制属性滥用,并在优化内存上也有意义。
class MySlotClass(): __slots__ = ("meow", "woof") def __init__(self): self.meow = "Meow" self.woof = "Woof" a = MySlotClass() MySlotClass.__dict__ mappingproxy({'__doc__': None, '__init__':, '__module__': '__main__', '__slots__': ('meow', 'woof'), 'meow': , 'woof': })
此时,如果使用 a.__dict__
,结果不会返回空字典,而是会报错。
特别地,Python 提供了运算符重载的功能。常用的对应如下(参考 官方页面):
方法 含义 应用
一元运算符
len长度 len(a)
bool逻辑值 bool(a)
neg取负值 -a
str/ repr 字符串形式 repr(a) / str(a), print(a)
二元运算符
add加 a + b, a += b
sub减 a - b, a -= b
mul乘 a * b, a *= b
div除 a / b, a /= b
pow乘方 a ** b, a **= b
radd左加 … + a
二元关系符
lt/ le 小于 / 小于等于 a < b, a <= b
gt/ ge 大于 / 大于等于 a > b, a >= b
eq/ ne 等于 / 不等于 a == b, a != b
比如下例中,对多个运算进行了重载,完成了二维向量在加减法上与向量、与数运算的基本定义。
class Vector: def __init__(self, a, b): self.a = a self.b = b def __add__(self, another): if isinstance(another, Vector): c, d = another.a, another.b else: c, d = another, another return Vector(self.a + c, self.b + d) def __radd__(self, another): return self.__add__(another) def __neg__(self): return Vector(-self.a, -self.b) def __sub__(self, another): return self.__add__(-another) def __str__(self): return "Vector({},{})".format(self.a, self.b) v1 = Vector(0,3) v2 = Vector(5,-2) print(v1 - 1, -v2, v1 + v2, v1 - v2) Vector(-1,2) Vector(-5,2) Vector(5,1) Vector(-5,5)
其中, __repr__()
与 __str__()
的主要区别在于,前者在交互式步骤中显示结果,后者在 print 函数中显示结果。
例如上例,如果直接输入 v1,不会以 “Vector(0,3)”的形式显示。
v1 # 在类中附加定义: __repr__ = __str__ 即可解决问题。 <__main__.Vector at 0x2531c129c88>
在类中也能定义迭代行为,需要 iter () 与 next () 方法。
# 该例改编自官方文档 class MyClass4: def __init__(self, lst): self.data = lst self.__index = len(lst) def __iter__(self): return self def __next__(self): if self.__index == 0: raise StopIteration self.__index -= 1 return self.data[self.__index] a = MyClass4("Meow") for char in a: print(char) w o e M
下面介绍几个常用的 Python 标准模块(即随 Python 安装的模块)。更多的第三方模块,例如 NumPy, pandas, matplotlib,可以参考本系列博文的其他文章。
这个模块应该是 Python 自带模块中使用率最高的一个了。一些例子:
# import os # # ----- 文件操作 ----- # os.rename("old.py", "new.py") # 重命名 # os.remove("a.py") # 删除 # os.stat("b.py") # 查看文件属性 # # ----- 路径操作 ----- # os.getcwd() # 获取当前目录 # os.chdir(r"d:\list") # 更改当前目录为 # os.chdir(os.pardir) # 返回上一级目录 # os.mkdir('newfolder ') # 在当前目录新建一个文件夹 # os.listdir('c:\list') # 列出文件夹下所有文件的列表 # os.removedirs('thefolder ') # 删除空文件夹 # os.path.isfile/ispath("f") # 检查路径是文件或是目录 # os.path.exists("f") # 检查路径是否存在 # # ----- 操作平台相关 ----- # os.sep # 当前操作系统的路径分隔符 # os.linesep # 当前操作系统的换行符 # os.path.join(r"c:\abc", "d") # 连接字串成为路径
一般我很少用到这个模块。可能有这么几个命令会用到:
import sys sys.platform 'win32'
一个 sys.argv
的例子:
sys.argv ['e:\\python\\lib\\site-packages\\ipykernel_launcher.py', '-f', 'C:\\Users\\wklchris\\AppData\\Roaming\\jupyter\\runtime\\kernel-3724c4c9-2130-485d-b388-7a84379fd043.json']
以上不是典型的例子,因为并不是在命令行下运行的。命令行下通常有如下格式:
python test.py hello
此时,sys.argv[0] = test.py,sys.argv[1] = hello.
参考本文附录。
defaultdict
Python 中有一些内置的办法进行调试与测试。
断言的含义在于,如果断言失败(False),那么代码会被终止(抛出一个 AssertionError
)。比如:
n = 0 assert(n != 0) 1 / n --------------------------------------------------------------------------- AssertionError Traceback (most recent call last)in () 1 n = 0 ----> 2 assert(n != 0) 3 1 / n AssertionError:
与大家一贯喜欢使用的 print 调试法相比,断言语句可以用命令行参数 -O 忽略。这样所有的 assert() 语句都不会被执行。
$ python -O main.py
logging 模块支持将错误日志输出(到控制台或者到文件)。
此乃调试神器。延伸阅读: logging 官方基础教程。
import logging # 该行也可能通过控制台:$ python main.py --log=WARNING 的方式实现 logging.basicConfig(level=logging.WARNING) n = 0 logging.warning("n = {}".format(n)) WARNING:root:n = 0
logging 模块的灵活之处在于你可以记录信息的级别( DEBUG,INFO,WARNING,ERROR,CRITICAL
),各级别的作用如下:
filename
参数,可以将日志写入到文件。一般使用 DEBUG
级别,即输出所有信息。# logging.basicConfig(filename="log.log", level=logging.DEBUG)
默认会将日志追加到文件末尾,如果想要覆写文件而不是追加,使用 filemode 参数:
# logging.basicConfig(filename="log.log", filemode="w", level=logging.DEBUG)
更改日志格格式,使用 format 参数。一般来说,常用的格式码(格式码后加 s 表示字符串)有:
%(levelname)
:当前日志字串级别。%(message)
:当前日志字串。%(asctime)
:当前时间。默认 datefmt 参数为 %Y-%m-%d %I:%M:%S
# logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') # logging.warning('is when this event was logged.') # 常用的格式: # logging.basicConfig(format="%(levelname)s: %(message)s")
还可以通过配置文件来代替 basicConfig 命令,并进行设置 logger 等更高级的配置。这部分可以参考:此处。
# import logging.config # logging.config.fileConfig('logging.conf')
正则表达式的基础内容参考本博客的这篇博文:正则表达式。注意:如果要保存一个正则表达式供多次使用,请存储其 compile
后的结果,避免反复编译。
import re bool(re.match(r"\d", "1")) True phone_re = re.compile(r'\d{3,4}-\d{7,8}') phone_re.match('010-12345678').group() '010-12345678' # 如果在正则表达式中添加了子组(小括号),那么会返回子组依顺序组成的一个元组 phone_re = re.compile(r'(\d{3,4})-(\d{7,8})') phone_re.match('010-12345678').groups() ('010', '12345678') phone_re = re.compile(r'\d{3,4}-\d{7,8}') # 寻找所有子串 phone_set = '010-12345678, 021-65439876 ' phone_re.findall(phone_set) ['010-12345678', '021-65439876'] s = 'a b c' # 用 re.split() 处理连续的空格 print(s.split(' '), re.split(r"\s+", s)) ['a', 'b', '', '', 'c'] ['a', 'b', 'c']