python是动态(运行期确定类型)强类型(不会发生隐式转换)语言
1. python基本数据类型
6个标准数据类型:
- Number(int float bool etc)
- String
- List
- Tuple
- Set
- Dictionary
其中:
不可变数据:Number、String、Tuple
可变数据:List、Dictionary、Set
2. python如何传参
python传递的都是引用, 如果是可变数据,则直接修改;如果是不可变数据,则产生新对象,让形参指向新对象。
举例:
def first(l):
l.append(0)
print(id(l))
print(l)
ll = []
print(id(ll))
first(ll) # [0]
first(ll) # [0, 0]
def second(s):
print(id(s))
s += 'a'
print(id(s))
print(s)
s = "test"
print(id(s))
second(s)
print(s)
另外两个关于形参的例子:
def clear_list(l):
l = [] # 创建了新对象,与ll无关
ll = [1, 2, 3]
clear_list(ll)
print(ll) # [1, 2, 3]
def default(l=[]):
l.append(1)
print(l)
print(id(l))
default() # [1]
default() # [1, 1]
第一个例子中,L是新创建的对象,与传入的LL无关;
第二个例子中,L默认参数只会在第一次调用的时候生成。
python中的 *args
和 **kargs
用于处理可变参数,本质上*args
被打包成tuple
,**kwargs
被打包成为dict
举例子:
def print_multi_args(*args):
print(type(args), args)
for idx, val in enumerate(args):
print(idx, val)
print_multi_args(1,2,3,'sad')
def print_kwargs(**kwargs):
print(type(kwargs), kwargs)
for k, v in kwargs.items():
print('{}:{}'.format(k, v))
print_kwargs(a=1, b=2, c='qq')
3. python的线程安全
首先,解释器读取Python代码并检查是否有语法或格式错误。
如果发现错误,则暂停执行。如果没有发现错误,则解释器会将Python代码转换为等效形式或字节代码。
然后将字节码发送到Python虚拟机(PVM),这里Python代码将被执行,如果发现任何错误,则暂停执行,否则结果将显示在输出窗口中。
编译后的字节码在解释器中执行过程中,Cpython解释器中的GIL(Global interpreter Lock, 全局解释器GIL)会导致同一时刻只有一个线程执行字节码。
所以,在一个解释器进程中通过多线程的方式无法利用多核处理器来实现真正的并行。python伪装多线程,同一时刻只有一个线程真正运行。
我们通常说的线程互斥锁,是在另一个层面上保证线程安全。GIL保证了解释器级别的互斥。线程互斥锁是代码级别的互斥,保证程序级别共享数据的一致性。
4. Python内存管理
-
内存池
创建大量消耗小内存的对象时,C中频繁调用new/malloc会导致大量的内存碎片,进而导致效率降低。
内存池就是预先在内存申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,先从内存池分配内存,不够了再申请新的。
pymalloc对于小的对象(< 236k)内存池申请,大的对象直接new/malloc申请。 -
垃圾回收机制
GC做两件事情:1. 找到内存中无用的垃圾对象资源,2. 清除找到的这些垃圾对象,释放内存给其他对象使用。
方法:- 引用计数:reference counting
- 标记-清除(mark and sweep)解决容器对象可能产生的循环引用问题
- 分代回收(generation collection)以空间换时间提高垃圾回收效率
引用计数:
对象在源码中的结构体如下:
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是作为引用计数。每当出现对象新的引用,ob_refcnt会增加;当引用对象被删除,ob_refcnt相应减少。引用计数为0时候,对象立即被回收,占用的内存空间被释放。
循环引用:
A B 相互引用而且没有外部引用AB中任意一个。
举例:
a = {}
b = {}
a['b'] = b
b['a'] = a
del a
del b
对于上例,如果使用引用计数法就会导致内存泄漏。python用两种方法来应对:
标记清除:
对象之间通过引用连在一起,构成以有向图。对象构成这个有向图的节点。引用关系构成这个有向图的边。从根对象(全局变量,调用栈,寄存器)出发,所有可达的对象标记为活动对象,不可达的就是要被清除的非活动对象。
分代技术:
python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,python将内存分为三代,分别对应三个链表,年轻代链表达到上限就会触发垃圾回收,将可回收的回收掉,不可回收的移动到中年代。以此类推。
分代机制是建立在标记清除技术基础上。
5. 迭代器与生成器
迭代器
容器(container)
多个元素组织在一起的数据结构,其元素可以逐个迭代获取,可以用 in, not in 关键字判断元素是否包含在容器中。常见的容器:list,set,deque
可迭代对象(iterables)
大部分container都是iterable,返回一个迭代器的都可以称为可迭代对象。
迭代器(iterator)
我们通常用 for in 语句对可迭代对象进行枚举,其底层机制是:可迭代对象通过iter()函数返回一个迭代器,迭代器提供一个next方法,调用这个方法得到容器下一个对象或者stopiteration错误。
迭代器是一个懒加载的工厂,需要他的时候才生成值返回,不调用的时候就是休眠状态等待下一次调用。
举例:
x = [1,2,3] # container
y = iter(x) # 调用迭代函数,返回一个迭代器
print(type(x), ' ', type(y))
# output: <class 'list'> <class 'list_iterator'>
print(next(y))
生成器generator
生成器是升级版本的迭代器。相比迭代器的优势是,不会像迭代器那样占用大量内存。举个例子:
[i for i in range(1000000)]
这个列表中每个元素都会保存在内存中,实际上我们也许并不需要保存那么多东西,只是需要在使用next()
函数的时候,再生成下一个对象。面向这个问题,生成器应运而生。比如这个例子,生成器的写法(i for i in range(1000000))
生成器的其他形式:
def example(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
for n in example(0, 2, 0.5):
print(n)
用图表示:
6. 闭包
函数套函数,内部函数用到外部函数的变量,内部函数为闭包(closure)
举例:
def addx(x):
def adder(y):
return x + y
return adder
# c 是 return回来的adder函数
c = addx(8)
print(type(c))
print(c.__name__)
print(c(10))
# output:
<class 'function'>
adder
18
7. 深拷贝和浅拷贝
直接看例子:
import copy
a = [1, 2, 3, 4, ['a', 'b']]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)
a[4].append('c')
print(a)
print(b)
print(c)
print(d)
# output:
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
[1, 2, 3, 4, ['a', 'b', 'c']]
[1, 2, 3, 4, ['a', 'b']]