0%

python笔记

可变对象和不可变对象

python中所有东西都是一个对象

  • 不可变对象:int,string,float,tuple
  • 可变对象 :list,dictionary

不可变对象immutable

python中的对象存放的是对象引用,所以尽管不可变对象本身不可变,但其对象引用是可变的

1555570792969

可变对象mutable

可变对象的内容可以发生变化,但其引用不会改变

1555571056395

地址问题

id()返回一个对象的地址或者用is;==对比值

不可变对象的引用改变后,其地址发生变化;可变对象改变后其地址不变

参数传递

python中向函数传参都是引用传递(传地址)

当传的参数是不可变对象时,函数复制一份引用,所以无论怎么操作传入的参数都不会对函数外的变量造成影响;当传入的参数是可变对象时,函数内操作会改变函数外变量的内容

不要用可变对象作为参数的默认值

迭代器和生成器

1555573612285

迭代

可迭代对象:实现了iter方法的对象

1
2
for k, v in d.items()
for i, value in enumerate(['A', 'B', 'C'])

生成器

优点:对延迟操作提供了支持,在需要的时候才产生结果,而不是立即产生结果,减少资源消耗;可读性强(认准yield即可知道返回的结果)

生成器函数

每次调用next()执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

1
2
3
4
5
6
7
def gensquatres(N):
for i in range(N):
yield i**2

# gensquatres是一个生成器对象
for item in gensquares(5):
print(item)
生成式表达式
1
2
3
4
5
squares = (x**2 for x in range(5))

next(squares)
for item in squares:
...

注意:生成器只能遍历一次

迭代器

迭代器:实现了迭代器协议的对象,可以用isinstance()判断

迭代器协议:对象需要提供next()方法和iter()方法返回迭代器类的实例,返回下一项或引起StopIteration异常,以终止迭代

yield from

yield from后加上可迭代对象可以将其每个元素yield出来

生成嵌套

在yield from后面加上生成器

  • 调用方:调用委派生成器的客户端(调用方)代码

  • 委托生成器:包含yield from表达式的生成器函数

  • 子生成器:yield from后面加的生成器函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count

# 委托生成器
def proxy_gen():
while True:
yield from average_gen()

# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0

委托生成器,在调用方与子生成器之间建立一个双向通道,可以简化代码,而且会自动处理异常

函数式编程

map-reduce

1
2
3
from functools import reduce
map(f, [x1, x2, x3, x4]) = [f(x1), f(x2), f(x3), f(x4)]
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

配合lambda函数可以进一步简化

filter

filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

1
2
3
4
def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))

sorted

装饰器

被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。为已经存在的对象添加额外的功能,并重用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
...
@log('execute')
def now():
print('2015-3-25')
...
>>> now()
execute now():
2015-3-25

另一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from functools import wraps

def makebold(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapped

def makeitalic(fn):
@wraps(fn)
def wrapped(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</i>"
return wrapped

@makebold
@makeitalic
def hello():
return "hello world"

@makebold
@makeitalic
def log(s):
return s
1
print hello()        # returns "<b><i>hello world</i></b>"

面向对象

类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数

私有变量

__开头的变量,原理为将该变量重命名为(真正的私有变量)

1
_classname__varname

_开头是一种约定,为私有变量

函数重载

函数重载能解决的问题:

  • 可变参数类型:python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数
  • 可变参数个数:对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的

故python中不需要函数重载

属性

实例可以任意绑定属性和方法。给实例绑定属性的方法是通过实例变量,或者通过self变量

类属性直接在class中定义属性

注意实例属性会屏蔽类属性,故类属性和实例属性不要重名

__slots _

_slots__变量用于限制该class实例能添加的属性

1
2
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

仅对当前类实例起作用,对继承的子类是不起作用的

@property

既能检查参数,又可以用类似属性这样简单的方式来访问类的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
1
2
3
4
5
6
7
8
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!

静态方法、实例方法、类方法

\ 实例方法 类方法 静态方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

init()和new()方法

__new()方法在init()方法之前执行

真正创建实例是new方法,init__方法做的事情是在对象创建好之后初始化变量

new方法返回一个创建的实例,init方法不返回

异常处理

捕获错误时也会捕获其子类

序列化

class和json的转化

1
2
3
4
5
json.dumps(item, default=lambda obj: obj.__dict__)
json.loads(json_str, object_hook=dict2student)

def dict2student(d):
return Student(d['name'], d['age'], d['score'])

协程

python对协程的支持是通过generator实现的

特点

  • 在单线程里实现任务的切换的

  • 利用同步的方式去实现异步

  • 不再需要锁,提高了并发性能

生产者消费者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'

def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()

c = consumer()
produce(c)

注意 send(n)为将参数传给yield

垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率

引用计数

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。

优点:

  • 简单
  • 实时性

缺点:

  • 维护引用计数消耗资源
  • 循环引用

标记-清除机制

基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放

分代技术

将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量

单例模式

new方法

1
2
3
4
5
6
7
8
9
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance

class MyClass(Singleton):
a = 1

装饰器版本

1
2
3
4
5
6
7
8
9
10
11
def singleton(cls, *args, **kw):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance

@singleton
class MyClass:
...

GIL线程全局锁

线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,就是一个核只能在同一时间运行一个线程.

解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能)

闭包

  • 在一个外函数中定义了一个内函数
  • 内函数里运用了外函数的临时变量
  • 外函数的返回值是内函数的引用

其中的临时变量只能被读取不能更改,若要等该要引入nonlocal语句

作用:类似装饰器

1
2
3
4
5
6
7
8
9
10
11
def make_sing(animal):
def make_voice(voice):
return "{} sings {}".format(animal, voice)
return make_voice

>>> dog = make_sing("dog")
>>> dog("wong")
'dog sings wong'
>>> cow = make_sing("cow")
>>> cow("mow")
'cow sings mow'

细节

连续赋值

1
a, b = b, a + b

相当于

1
2
3
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

*args & **kwargs

  • *args:可以传递任意数量的参数,index-value
  • **kwargs:可以使用没有事先定义的参数名,name-value

拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象

b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝

a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象

print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d

输出结果:
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]