python基础篇

列表与元组

list是mutable, tuple是imutalbe,tuple初始化之后不能修改item,否则会报错,也不支持增加和删除操作。只能重新开辟一块空间来存储。造成这个原因在于它们的存储方式不一样。

列表分配存储空间时,会额外分配更多的空间,这样的机制保证了其操作的高效性:增加 / 删除的时间复杂度均为 O(1)。但这个分配的空间肯定不能无限多,所以列表会额外存储已经使用的空间大小,这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间。另外,为了实现列表可以修改item, 每个item还额外分配了一个指针来指向item。所以列表是动态的,而元组则是静态的。

注意的是列表类似指针数组,但是也有静态数组的特性,因为它也是一次性开辟更多的空间

# list的动态扩容
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个元素的空间

# 对比list与tuple
l = [1, 2, 3]
l.__sizeof__()
64
tup = (1, 2, 3)
tup.__sizeof__()
48

总结,当存储的数据和数量不变,优先使用tuple, 否则使用list。 初始化tuple比list要更快。

想要修改tuple,只能重新开辟一开内存

tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 创建新的元组new_tup,并依次填充原元组的值
new _tup
(1, 2, 3, 4, 5)

字典与集合

3.7后字典变得有序。为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开,也就是下面这样新的结构

Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------
Entries
--------------------
hash0   key0  value0
---------------------
hash1   key1  value1
---------------------
hash2   key2  value2
---------------------
        ...
---------------------

清楚了具体的设计结构,我们接着来看这几个操作的工作原理。

插入操作:每次向字典插入一个元素时,Python 会首先计算键的哈希值hash(key),再和 mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index = hash(key) & mask。

如果哈希表中此位置是空的,那么这个元素就会被插入其中。若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。

若两者中有一个不相等或者都不相等,这种情况我们通常称为哈希冲突(hash collision),Python 便会继续寻找表中空余的位置,直到找到位置为止。

不难理解,哈希冲突的发生,往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。所以,平均情况下,这仍能保证插入、查找和删除的时间复杂度为 O(1)。 

字符串

字符串相关的基本不难。值得注意的是字符串是不可变的,想修改字符串需要新建一个新的字符串。python对字符串的拼接进行了优化,用+=拼接操作符的话,会尝试原地扩容,复杂度就降低了。另外就是json库,序列化dumps和反序列化loads都只支持python的基本数据类型,不支持自己创建的类对象。

函数

关于lambda匿名函数,与 def 定义的函数区别,在于lambda可以用于构建简单逻辑的功能函数,而不需要繁琐去def创建函数。而且一般可以与map, filter 和 reduce 等联合使用。个人用得比较少,下面放出一下例子加强理解。


l = [(1, 20), (3, 0), (9, 10), (2, -1)]
l.sort(key=lambda x: x[1]) # 按列表中元祖的第二个元素排序
print(l)
# 输出
[(2, -1), (3, 0), (9, 10), (1, 20)]

 关于函数式编程,并不仅仅是以功能函数去组织代码那么简单,其函数需要满足同样输入可以得到同样的输出,而且没有副作用。这个副作用可以参照以下的例子

l = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, l) # [2, 4, 6, 8, 10]

# 这里map生成了一个新的list 而不会影响到原来的list, 这就是没有副作用的纯函数
# 如果使用def 直接传入一个list不做其他处理的话,会改变原来list

 面向对象

这部分基本都比较熟悉,关于抽象基类和抽象函数用得比较少。抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。这其实正是软件工程中一个很重要的概念,定义接口。抽象类就是这么一种存在,它是一种自上而下的设计风范,你只需要用少量的代码描述清楚要做的事情,定义好接口,然后就可以交给不同开发人员去开发和对接。例子如下

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

entity = Entity()  # 会直接报错,抽象基类不可以实例化
document = Document() # 不会报错

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值