我的个人博客排版更舒服: https://www.luozhiyun.com/archives/264

列表和元组

  • 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。
  • 而元组是静态的,长度大小固定,无法增加删减或者改(immutable)。

如果你想对已有的元组做任何"改变”,那就只能重新开辟一块内存,创建新的元组了。

如下:

tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 创建新的元组new_tup,并依次填充原元组的值 new _tup (1, 2, 3, 4, 5) l = [1, 2, 3, 4]
l.append(5) # 添加元素5到原列表的末尾 l [1, 2, 3, 4, 5]

列表和元组存储方式的差异

由于列表是动态的,所以它需要存储指针,来指向对应的元素。另外,由于列表可变,所以需要额外存储已经分配的长度大小,这样才可以即使扩容。

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个元素的空间

但是对于元组,情况就不同了。元组长度大小固定,元素不可变,所以存储空间固定。

列表和元组的性能

元组要比列表更加轻量级一些,所以总体上来 说,元组的性能速度要略优于列表。

Python会在后台,对静态数据做一些资源缓存资源缓存(resource caching)。通常来说,因为垃圾回收机制 的存在,如果一些变量不被使用了,Python就会回收它们所占用的内存,返还给操作系统,以便其他变量 或其他应用使用。

但是对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python会暂时缓存这部分内存。

由下面例子元组的初始化速 度,要比列表快5倍。

python3 -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
python3 -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop

字典和集合

集合和字典基本相同,唯一的区别,就是集合没有键和值的配对,是一系列无序的、唯一的元素组合。

如何访问、使用就不说了,说两个注意点:

  1. Python 中字典和集合,无论是键还是值,都可以是混合类型
s = {1, 'hello', 5.0}
  1. 字典访问可以直接索引键,如果不存在,就会抛出异常;也可以使用 get(key, default) 函数来进行索引。如果键不存在,调用 get() 函数可以返回一个默认值。
d = {'name': 'jason', 'age': 20}
d['name']
'jason'
d['location']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'location' d = {'name': 'jason', 'age': 20}
d.get('name')
'jason'
d.get('location', 'null')
'null

字典和集合的工作原理

字典和集合的内部结构都是一张哈希表。

  • 对于字典而言,这张表存储了哈希值(hash)、键和值这 3 个元素。
  • 而对集合来说,区别就是哈希表内没有键和值的配对,只有单一的元素了。

老版本 Python 的哈希表结构如下所示:

--+-------------------------------+
| 哈希值 (hash) 键 (key) 值 (value)
--+-------------------------------+
0 | hash0 key0 value0
--+-------------------------------+
1 | hash1 key1 value1
--+-------------------------------+
2 | hash2 key2 value2
--+-------------------------------+
. | ...
__+_______________________________+

随着哈希表的扩张,它会变得越来越稀疏。举个例子:

{'name': 'mike', 'dob': '1999-01-01', 'gender': 'male'}

那么它会存储为类似下面的形式:

entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'mike'],
['--', '--', '--'],
[9371539127, 'gender', 'male']
]

这样的设计结构显然非常浪费存储空间。

为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开:

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

在新的哈希表结构下的存储形式,上面的例子就是这样:

indices = [None, 1, None, None, 0, None, 2]
entries = [
[1231236123, 'name', 'mike'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
]

插入操作

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

如果哈希表中此位置是空的,那么这个元素就会被插入其中。而如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。

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

删除操作

对于删除操作,Python 会暂时对这个位置的元素,赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。

为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。

函数变量作用域

局部变量

如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问。

对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字:

def outer():
x = "local"
def inner():
nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
x = 'nonlocal'
print("inner:", x)
inner()
print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal

如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。

def outer():
x = "local"
def inner():
x = 'nonlocal' # 这里的 x 是 inner 这个函数的局部变量
print("inner:", x)
inner()
print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local

全局变量

全局变量则是定义在整个文件层次上的,可以在文件内的任何地方被访问,但是不能在函数内部随意改变全局变量的值。

例如:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
...
MIN_VALUE += 1
...
validation_check(5) #输出
UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment

因为,Python 的解释器会默认函数内部的变量为局部变量,但是又发现局部变量 MIN_VALUE 并没有声明,因此就无法执行相关操作。

如果我们一定要在函数内部改变全局变量的值,就必须加上 global 这个声明:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
global MIN_VALUE
...
MIN_VALUE += 1
...
validation_check(5)

如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量,比如下面这种:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
MIN_VALUE = 3
...

闭包

闭包中外部函数返回的是一个函数,返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。

比如,我们想计算一个数的 n 次幂,用闭包可以写成下面的代码:

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

这里外部函数 nth_power() 返回值,是函数 exponent_of(),而不是一个具体的数值。

面对对象

函数

静态函数:与类没有什么关联可以用来做一些简单独立的任务,既方便测试,也能优化代码结构。静态函数可以通过在函数前一行加上 @staticmethod 来表示。

类函数:第一个参数一般为 cls,表示必须传一个类进来。类函数最常用的功能是实现不同的 init 构造函数,类似java中的构造器。类函数需要装饰器 @classmethod 来声明。

成员函数:是我们最正常的类的函数,它不需要任何装饰器声明,第一个参数 self 代表当前对象的引用,可以通过此函数,来实现想要的查询 / 修改类的属性等功能。

例子如下:

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 # 类函数
@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) # 静态函数
@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.

继承

class Entity():
def __init__(self, object_type):
print('parent class init called')
self.object_type = object_type def get_context_length(self):
raise Exception('get_context_length not implemented') def print_title(self):
print(self.title) class Document(Entity):
def __init__(self, title, author, context):
print('Document class init called')
Entity.__init__(self, 'document')
self.title = title
self.author = author
self.__context = context def get_context_length(self):
return len(self.__context) class Video(Entity):
def __init__(self, title, author, video_length):
print('Video class init called')
Entity.__init__(self, 'video')
self.title = title
self.author = author
self.__video_length = video_length def get_context_length(self):
return self.__video_length harry_potter_book = Document('Harry Potter(Book)', 'J. K. Rowling', '... Forever Do not believe any thing is capable of thinking independently ...')
harry_potter_movie = Video('Harry Potter(Movie)', 'J. K. Rowling', 120) print(harry_potter_book.object_type)
print(harry_potter_movie.object_type) harry_potter_book.print_title()
harry_potter_movie.print_title() print(harry_potter_book.get_context_length())
print(harry_potter_movie.get_context_length()) ########## 输出 ########## Document class init called
parent class init called
Video class init called
parent class init called
document
video
Harry Potter(Book)
Harry Potter(Movie)
77
120

我们可以从中抽象出一个叫做 Entity 的类,来作为Document 和 Video的父类。

每个类都有构造函数,继承类在生成对象的时候,是不会自动调用父类的构造函数的,因此你必须在 init() 函数中显式调用父类的构造函数。它们的执行顺序是 子类的构造函数 -> 父类的构造函数。

由于父类的get_context_length方法是用来被重写的,所以使用 Entity 直接生成对象,调用 get_context_length() 函数,就会 raise error 中断程序的执行。

继承的优势:减少重复的代码,降低系统的熵值(即复杂度)。

抽象类

抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器 @abstractmethod 来表示。

抽象类就是这么一种存在,它是一种自上而下的设计风范,你只需要用少量的代码描述清楚要做的事情,定义好接口,然后就可以交给不同开发人员去开发和对接。

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

代码中entity = Entity()直接报错,只有通过 Document 继承 Entity 才能正常使用。

python学习要点(一)的更多相关文章

  1. python学习要点(二)

    我的博客:https://www.luozhiyun.com/archives/269 '==' VS 'is' '=='操作符比较对象之间的值是否相等. 'is'操作符比较的是对象的身份标识是否相等 ...

  2. coursera python 学习总结

    为啥要写这篇总结?早上突然想到了四个字:知行合一.实践,总结,再实践,再总结经验,积累经验,为己所用.闲话少叙,来干货: 1.目标要单一,如果想要完成课程,还要健身,还要玩玩游戏.看看电影,还学别的课 ...

  3. Python学习之路【第一篇】-Python简介和基础入门

    1.Python简介 1.1 Python是什么 相信混迹IT界的很多朋友都知道,Python是近年来最火的一个热点,没有之一.从性质上来讲它和我们熟知的C.java.php等没有什么本质的区别,也是 ...

  4. Deep learning with Python 学习笔记(1)

    深度学习基础 Python 的 Keras 库来学习手写数字分类,将手写数字的灰度图像(28 像素 ×28 像素)划分到 10 个类别 中(0~9) 神经网络的核心组件是层(layer),它是一种数据 ...

  5. Python 学习参考书目推荐

    Python 学习,参考书目推荐 前言 好的技术书籍可以帮助我们快速地成长,大部分人或多或少地受益于经典的技术书籍.在「Python开发者」微信公号后台,我们经常能收到让帮忙推荐书籍的消息.这类的问题 ...

  6. Python学习--04条件控制与循环结构

    Python学习--04条件控制与循环结构 条件控制 在Python程序中,用if语句实现条件控制. 语法格式: if <条件判断1>: <执行1> elif <条件判断 ...

  7. Python学习--01入门

    Python学习--01入门 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.和PHP一样,它是后端开发语言. 如果有C语言.PHP语言.JAVA语言等其中一种语言的基础,学习Py ...

  8. Python 学习小结

    python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...

  9. Python学习路径及练手项目合集

    Python学习路径及练手项目合集 https://zhuanlan.zhihu.com/p/23561159

随机推荐

  1. vue中如何缓存一些页面

    在vue中,有时候我们只想缓存页面中的一些组件或页面,这个时候怎么办呢,我们就需要用判断来加载keep-alive. 例如: // router.js { path: "/driving_l ...

  2. 伪元素 before 和 after 初探

    伪元素 before 和 after 初探 使用了 CodePen 做演示,欢迎点击预览 定义 首先来看 MDN 的定义: ::before 创建一个伪元素,作为已选中元素的第一个子元素,常通过 co ...

  3. JZOJ 5257. 小X的佛光 (Standard IO)

    5257. 小X的佛光 (Standard IO) Time Limits: 2000 ms Memory Limits: 524288 KB Description Input Output Sam ...

  4. vmware企业虚拟化平台vSphere管理与配置

    ├─1-CCIE-DC课程介绍.avi ├─2-vSphere-简介.avi ├─3-vSphere-新功能介绍.avi ├─4-vSphere-授权介绍.avi ├─5-vSphere-课程拓扑介绍 ...

  5. SpringBoot1.5.10.RELEASE整合druid

    1.先在pom文件中导入druid的jar包 <dependency> <groupId>com.alibaba</groupId> <artifactId& ...

  6. disruptor 入门 一

    一.disruptor基本概念 https://www.cnblogs.com/haiq/p/4112689.html 二.disruptor入门程序 导入disruptor包 <depende ...

  7. 脚本写一行echo也能写出bug ? glob了解一下

    背景 最近处理一个 bug 很有意思,有客户反馈某个配置文件解析失败了,出错的那行的内容就只有一个字母 a. 最开始以为是谁改动了处理的脚本,但要到了问题代码中的脚本,比较发现跟库上是一样的. 又经过 ...

  8. Simulink仿真入门到精通(五) Simulink模型的仿真

    5.1 模型的配置仿真 由各种模块所构建的可视化逻辑连接,只是模型的外在表现,模型仿真的核心驱动器是被称作解算器(Solver)的组件,相当于Simulink仿真过程的心脏,驱动着模型仿真,它在每一个 ...

  9. Google Flutter Clock 大赛优秀项目推荐

    Flutter 在 Google 加持下,如今可以作为跨平台首选了.早在 Flutter 刚刚出现强势苗头,我作为第一批体验了一把,<Flutter 初尝:从 Java 无缝过渡>,不过也 ...

  10. 【Win10】我们无法更新系统保留的分区

      前言 笔者是一个萌新,这个方案也是慢慢摸索出来的,有更好的方案欢迎大家提出 前段时间用公司电脑发现win10新版本还行,回家升级自己的电脑却提示“我们无法更新系统保留的分区”.(O_o)?? 笔者 ...