课程来源:极客时间
《Python核心技术与实战》笔记1
《Python核心技术与实战》笔记2
《Python核心技术与实战》笔记3
列表和元组
列表是动态的,元组是静态的。
l = [1, 2, 3]
l.__sizeof__()
64
tup = (1, 2, 3)
tup.__sizeof__()
48
- 关于列表的存储:
事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于 int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。
l = []
l.__sizeof__() // 空列表的存储空间为 40 字节
40
l.append(1)
l.__sizeof__()
72 // 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 // 由于之前分配了空间,所以加入元素 2,列表空间不变
l.append(3)
l.__sizeof__()
72 // 同上
l.append(4)
l.__sizeof__()
72 // 同上
l.append(5)
l.__sizeof__()
104 // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间
元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。
思考:
# 创建空列表
# option A
empty_list = list()
# option B
empty_list = []
请问它们在效率上有什么区别吗?我们应该优先考虑使用哪种呢? 区别主要在于list()是一个function call,Python的function call会创建stack,并且进行一系列参数检查的操作,比较expensive,反观[]是一个内置的C函数,可以直接被调用,因此效率高。
字典和集合
字典是一系列由键(key)和值(value)配对组成的元素的集合,在 Python3.7+,字典被确定为有序,而 3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变。
相比于列表和元组,字典的性能更优,特别是对于查找、添加和删除操作,字典都能在常数时间复杂度内完成。
而集合和字典基本相同,唯一的区别,就是集合没有键和值的配对,是一系列无序的、唯一的元素组合。
# 集合
s1 = {1, 2, 3}
s2 = set([1, 2, 3])
s1 == s2
True
# 字典
d1 = {'name': 'jason', 'age': 20, 'gender': 'male'}
d2 = dict({'name': 'jason', 'age': 20, 'gender': 'male'})
d3 = dict([('name', 'jason'), ('age', 20), ('gender', 'male')])
d4 = dict(name='jason', age=20, gender='male')
d1 == d2 == d3 ==d4
True
集合的 pop() 操作是删除集合中最后一个元素,可是集合本身是无序的,你无法知道会删除哪个元素,因此这个操作得谨慎使用。
- 字典排序
d = {'b': 1, 'a': 2, 'c': 10}
d_sorted_by_key = sorted(d.items(), key=lambda x: x[0]) # 根据字典键的升序排序
d_sorted_by_value = sorted(d.items(), key=lambda x: x[1]) # 根据字典值的升序排序
d_sorted_by_key
[('a', 2), ('b', 1), ('c', 10)]
d_sorted_by_value
[('b', 1), ('a', 2), ('c', 10)]
注意:这里返回了一个列表。列表中的每个元素,是由原字典的键和值组成的元组。
字符串
特别要注意,Python 的字符串是不可变的(immutable)。
从 Python2.5 开始,每次处理字符串的拼接操作时(str1 += str2),Python 首先会检测 str1 还有没有其他的引用。如果没有的话,就会尝试原地扩充字符串 buffer 的大小,而不是重新分配一块内存来创建新的字符串并拷贝。这样的话,上述例子中的时间复杂度就仅为 O(n) 了。
因此,以后你在写程序遇到字符串拼接时,如果使用’+='更方便,就放心地去用吧,不用过分担心效率问题了。
输入与输出
Python 对 int 类型没有最大限制(相比之下, C++ 的 int 最大为 2147483647,超过这个数字会产生溢出),但是对 float 类型依然有精度限制。
filter(function, iterable)
function -- 判断函数。
iterable -- 可迭代对象。
# function 为True,过滤出来
def is_odd(n):
return n % 2 == 1
newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(newlist))
newlist1 = filter(lambda x:x, [1,0 , None, 4, 5, 6, 7, 8, 9, 10])
# newlist1为[1, 4, 5, 6, 7, 8, 9, 10]
条件与循环
i = 0
while i < 1000000:
i += 1
for i in range(0, 1000000):
pass
究竟哪个效率高呢?
要知道,range() 函数是直接由 C 语言写的,调用它速度非常快。而 while 循环中的“i += 1”这个操作,得通过 Python 的解释器间接调用底层的 C 语言;并且这个简单的操作,又涉及到了对象的创建和删除(因为 i 是整型,是 immutable,i += 1 相当于 i = new int(i + 1))。所以,显然,for 循环的效率更胜一筹。
异常处理
捕捉多个异常
try:
s = input('please enter two numbers separated by comma: ')
num1 = int(s.split(',')[0].strip())
num2 = int(s.split(',')[1].strip())
...
except ValueError as err:
print('Value Error: {}'.format(err))
except IndexError as err:
print('Index Error: {}'.format(err))
except Exception as err:
print('Other error: {}'.format(err))
print('continue')
# 另一种方式
try:
db = DB.connect('<db path>') # 可能会抛出异常
raw_data = DB.queryData('<viewer_id>') # 可能会抛出异常
except (DBConnectionError, DBQueryDataError) as err:
print('Error: {}'.format(err))
自定义函数
-
如果我们一定要在函数内部改变全局变量的值,就必须加上 global 这个声明
-
对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上nonlocal这个关键字:
def outer():
x = "local"
def inner():
nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
x = 'nonlocal'
print("inner:", x)
def inner2():
global x
x = 'global00'
print("inner:", x)
inner()
print("outer:", x)
inner2()
print("outer:", x)
outer()
print(x)
# 输出
inner: nonlocal
outer: nonlocal
inner: global00
outer: nonlocal
global00
global 将x的值赋给全局,更改全局x变量的值。
闭包 closure
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。闭包本质上是一个函数。
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函数
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
print(square(2)) # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
# 输出
4 # 2^2
8 # 2^3
- Python 中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查;
- 和其他语言不同,Python 中函数的参数可以设定默认值;
- 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率;
- 合理地使用闭包,则可以简化程序的复杂度,提高可读性。
匿名函数
lambda argument1, argument2,... argumentN : expression
# lambda 是一个表达式,并不是一个语句;它只能写成一行的表达形式
map(function, iterable)
# 第一个参数是函数对象,第二个参数是一个可以遍历的集合,它表示对 iterable 的每一个元素,都运用 function 这个函数
filter(function, iterable)
# 使用 function 判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合
reduce(function, iterable)
# 对一个集合做一些累积操作
l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l) # 1*2*3*4*5 = 120
类
-
如果一个属性以 __ (注意,此处有两个 _) 开头,我们就默认这个属性是私有属性
-
class Document(): # 常量 WELCOME_STR = 'Welcome! The context for this book is {}.' def __init__(self, title, author, context): print('init function called') self.title = title self.author = author self.__context = context # 类函数 # 类函数的第一个参数一般为 cls,表示必须传一个类进来。类函数最常用的功能是实现不同的 init 构造函数 @classmethod def create_empty_book(cls, title, author): return cls(title=title, author=author, context='nothing') # 成员函数 def get_context_length(self): return len(self.__context) # 静态函数 # 静态函数可以用来做一些简单独立的任务,注意没有self @staticmethod def get_welcome(context): return Document.WELCOME_STR.format(context) empty_book = Document.create_empty_book('What Every Man Thinks About Apart from Sex', 'Professor Sheridan Simove') print(empty_book.get_context_length()) print(empty_book.get_welcome('indeed nothing')) ########## 输出 ########## init function called 7 Welcome! The context for this book is indeed nothing.
-
from abc import ABCMeta, abstractmethod class Entity(metaclass=ABCMeta): @abstractmethod def get_title(self): pass @abstractmethod def set_title(self, title): pass class Document(Entity): def get_title(self): return self.title def set_title(self, title): self.title = title document = Document() document.set_title('Harry Potter') print(document.get_title()) entity = Entity() ########## 输出 ########## Harry Potter --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-266b2aa47bad> in <module>() 21 print(document.get_title()) 22 ---> 23 entity = Entity() 24 entity.set_title('Test') TypeError: Can't instantiate abstract class Entity with abstract methods get_title, set_title
-
抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。
模块化
-
sys.path.append("…")` 表示将当前程序所在位置向上提了一级
-
我们还需要在模块所在的文件夹新建一个
__init__.py
,内容可以为空,也可以用来表述包对外暴露的模块接口。不过,事实上,这是 Python 2 的规范。在 Python 3 规范中,__init__.py
并不是必须的 -
用
if __name__ == '__main__'
来避开 import 时执行
加餐
e = 1
try:
1 / 0
except ZeroDivisionError as e:
print('error')
print(e)
# 错误:NameError: name 'e' is not defined
# 如果你在异常处理的 except block 中,把异常赋予了一个变量,那么这个变量会在 except block 执行结束时被删除
# 上述代码相当于:
e = 1
try:
1 / 0
except ZeroDivisionError as e:
try:
pass
finally:
del e
# 因此,这里提醒我们,在平时写代码时,一定要保证 except 中异常赋予的变量,在之后的语句中不再被用到。