CS-Notes/notes/笔记/Python 知识点扩展.md.txt
2018-02-22 14:47:22 +08:00

471 lines
16 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[TOC]
# 实现单例模式
## 1 使用\_\_new\_\_方法
```python
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
```
## 2 共享属性
创建实例时把所有实例的`__dict__`指向同一个字典 , 这样它们具有相同的属性和方法 .
```python
class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state
        return ob
class MyClass2(Borg):
    a = 1
```
## 3 装饰器版本
```python
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:
  ...
```
## 4 import 方法
作为 python 的模块是天然的单例模式
```python
# mysingleton.py
class My_Singleton(object):
    def foo(self):
        pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
```
# 垃圾回收机制
Python GC 主要使用引用计数reference counting来跟踪和回收垃圾。在引用计数的基础上通过“标记 - 清除”mark and sweep解决容器对象可能产生的循环引用问题通过“分代回收”generation collection以空间换时间的方法提高垃圾回收效率。
## 1 引用计数
PyObject 是每个对象必有的内容其中`ob_refcnt`就是做为引用计数。当一个对象有新的引用时,它的`ob_refcnt`就会增加,当引用它的对象被删除,它的`ob_refcnt`就会减少 . 引用计数为 0 该对象生命就结束了。
优点 :
1. 简单
2. 实时性
缺点 :
1. 维护引用计数消耗资源
2. 循环引用
## 2 标记 - 清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
## 3 分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python 默认定义了三代对象集合索引数越大对象存活时间越长。
举例:
当某些内存块 M 经过了 3 次垃圾收集的清洗之后还存活时我们就将内存块 M 划到一个集合 A 中去而新分配的内存都划分到集合 B 中去。当垃圾收集开始工作时大多数情况都只对集合 B 进行垃圾回收而对集合 A 进行垃圾回收要隔相当长一段时间后才进行这就使得垃圾收集机制需要处理的内存少了效率自然就提高了。在这个过程中集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 当然集合 A 中实际上也存在一些垃圾这些垃圾的回收会因为这种分代的机制而被延迟。
# list 实现
https://www.jianshu.com/p/J4U6rR
# Python 2  3 的区别
http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/
# 去除列表中重复的数
```python
a = [1, 2, 4, 2, 4, 5, 6, 5, 7, 8, 9, 0]
a = list(set(a))
```
# 替换字符串
使用 replace() 函数
```python
str = "aaa bbb ccc"
str = str.replace("aaa", "111")
```
使用 re 模块的 sub() 函数进行查询和替换。
```python
import re
str = "aaa bbb ccc"
rex = r'(aaa|bbb)'
str = re.sub(rex, "111", str)
```
# read, readline  readlines
- read 读取整个文件
- readline 读取下一行 , 使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
# is  ==
is 比较地址== 比较值
# 可变对象与不可变对象
 python strings, tuples,  numbers 是不可更改的对象 list, dict, set 等则是可以修改的对象。
当一个变量引用了一个不可变对象时,变量值发生改变,变量所引用的对象也发生改变,也就是对象引用的地址也发生了改变。
# 静态方法和类方法
Python 其实有 3 个方法 , 即静态方法 (staticmethod), 类方法 (classmethod) 和对象方法。
其中对象方法的第一个参数 selft在使用类对象调用时会将对象隐式传入类方法的第一个参数 cls在使用类调用时会将类隐式传入而静态方法没有隐式参数。
```python
def foo(x):
    print "executing foo(%s)"%(x)
class A(object):
    def foo(self, x):
        print "executing foo(%s,%s)"%(self, x)
    @classmethod
    def class_foo(cls, x):
        print "executing class_foo(%s,%s)"%(cls, x)
    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x
```
详情:[What is the difference between @staticmethod and @classmethod in Python?
](https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python)
# 拷贝
赋值创建了对象的一个新的引用;浅拷贝创建一个新的对象,但它包含的是对原始对象中包含项的引用,而深拷贝会递归的复制它所包含的对象。
创建浅拷贝的方式:
- 完全切片方法;
- 工厂函数 list()
- copy 模块的 copy() 函数
创建深拷贝的方式:
- copy 模块的 deepcopy() 函数
```python
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']]
```
# 作用域
 Python 遇到一个变量的话他会按照这样的顺序进行搜索
本地作用域Local→当前作用域被嵌入的本地作用域Enclosing locals→全局 / 模块作用域Global→内置作用域Built-in
# 类变量与实例变量
```python
class Test(object):
    num_of_instance = 0
    def __init__(self, name):
        self.name = name
        Test.num_of_instance += 1
if __name__ == '__main__':
    print Test.num_of_instance   # 0
    t1 = Test('jack')
    print Test.num_of_instance   # 1
    t2 = Test('lucy')
    print t1.name , t1.num_of_instance  # jack 2
    print t2.name , t2.num_of_instance  # lucy 2
```
# 自省
类似于 Java 的反射
```python
a = [1, 2, 3]
b = {'a':1,'b':2,'c':3}
c = True
print type(a), type(b), type(c) # <type 'list'> <type 'dict'> <type 'bool'>
print isinstance(a, list)  # True
```
# 字典推导式
```python
d = {key: value for (key, value) in iterable}
```
# 单下划线和双下划线
```python
>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}
```
\_\_foo\_\_: 一种约定 , Python 内部的名字 , 用来区别其他用户自定义的命名 , 以防冲突,就是例如\_\_init\_\_(),\_\_del\__(),\__call\__() 这些特殊方法
\_foo: 一种约定 , 用来指定变量私有 . 程序员用来指定私有变量的一种方式 . 不能用 from module import * 导入,其他方面和公有一样访问;
\_\_foo: 这个有真正的意义 : 解析器用\_classname\_\_foo 来代替这个名字 , 以区别和其他类相同的命名 , 它无法直接像公有成员一样随便访问 , 通过对象名 .\_类名\_\_xxx 这样的方式可以访问 .
# 迭代器和生成器
这里有个关于生成器的创建问题面试官有考: 问: 将列表生成式中 [] 改成 () 之后数据结构是否改变? 答案:是,从列表变为生成器
```python
>>> L = [x*x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000028F8B774200>
```
通过列表生成式可以直接创建一个列表。但是受到内存限制列表容量肯定是有限的。而且创建一个包含百万元素的列表不仅是占用很大的内存空间我们只需要访问前面的几个元素后面大部分元素所占的空间都是浪费的。因此没有必要创建完整的列表节省大量内存空间。在 Python 我们可以采用生成器边循环边计算的机制—>generator
http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python
10 \*args and \*\*kwargs
用\*args 和\*\*kwargs 只是为了方便并没有强制使用它们 .
当你不确定你的函数里将要传递多少参数时你可以用\*args. 例如 , 它可以传递任意数量的参数 :
```python
>>> def print_everything(*args):
        for count, thing in enumerate(args):
...         print '{0}. {1}'.format(count, thing)
...
>>> print_everything('apple', 'banana', 'cabbage')
0. apple
1. banana
2. cabbage
```
相似的 ,\*\*kwargs 允许你使用没有事先定义的参数名 :
```python
>>> def table_things(**kwargs):
...     for name, value in kwargs.items():
...         print '{0} = {1}'.format(name, value)
...
>>> table_things(apple = 'fruit', cabbage = 'vegetable')
cabbage = vegetable
apple = fruit
```
你也可以混着用 . 命名参数首先获得参数值然后所有的其他参数都传递给\*args 和\*\*kwargs. 命名参数在列表的最前端 . 例如 :
```python
def table_things(titlestring, **kwargs)
```
\*args 和*\\*kwargs 可以同时在函数的定义中 , 但是\*args 必须在\**\kwargs 前面 .
当调用函数时你也可以用\*和\*\*语法 . 例如 :
```python
>>> def print_three_things(a, b, c):
...     print 'a = {0}, b = {1}, c = {2}'.format(a, b, c)
...
>>> mylist = ['aardvark', 'baboon', 'cat']
>>> print_three_things(*mylist)
a = aardvark, b = baboon, c = cat
```
就像你看到的一样 , 它可以传递列表 ( 或者元组 ) 的每一项并把它们解包 . 注意必须与它们在函数里的参数相吻合 . 当然 , 你也可以在函数定义或者函数调用时用*.
http://stackoverflow.com/questions/3394835/args-and-kwargs
# 面向切面编程 AOP 和装饰器
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
这个问题比较大 , 推荐 : http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
# 鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
比如在 python 有很多 file-like 的东西比如 StringIO, GzipFile, socket。它们有很多相同的方法我们把它们当作文件使用。
又比如 list.extend() 方法中 , 我们并不关心它的参数是不是 list, 只要它是可迭代的 , 所以它的参数可以是 list/tuple/dict/ 字符串 / 生成器等 .
鸭子类型在动态语言中经常使用非常灵活使得 python 不想 java 那样专门去弄一大堆的设计模式。
# 重载
缺省参数实现重载。
http://www.zhihu.com/question/20053359
# 新式类和旧式类
这篇文章很好的介绍了新式类的特性 : http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
新式类很早在 2.2 就出现了 , 所以旧式类完全是兼容的问题 , Python3 里的类全部都是新式类 . 这里有一个 MRO 问题可以了解下 ( 新式类是广度优先 , 旧式类是深度优先 ),<Python 核心编程 > 里讲的也很多 .
一个旧式类的深度优先的例子
```python
class A():
    def foo1(self):
        print "A"
class B(A):
    def foo2(self):
        pass
class C(A):
    def foo1(self):
        print "C"
class D(B, C):
    pass
d = D()
d.foo1()
# A
```
**按照经典类的查找顺序`从左到右深度优先`的规则,在访问`d.foo1()`的时候 , D 这个类是没有的 .. 那么往上查找 , 先找到 B, 里面没有 , 深度优先 , 访问 A, 找到了 foo1(), 所以这时候调用的是 A  foo1()从而导致 C 重写的 foo1() 被绕过**
__new__是一个静态方法 , 而__init__是一个实例方法 .
__new__方法会返回一个创建的实例 , 而__init__什么都不返回 .
只有在__new__返回一个 cls 的实例时后面的__init__才能被调用 .
当创建一个新实例时调用__new__, 初始化一个实例时用__init__.
# `__new__`和`__init__`的区别
这个`__new__`确实很少见到 , 先做了解吧 .
1. `__new__`是一个静态方法 , 而`__init__`是一个实例方法 .
2. `__new__`方法会返回一个创建的实例 , 而`__init__`什么都不返回 .
3. 只有在`__new__`返回一个 cls 的实例时后面的`__init__`才能被调用 .
4. 当创建一个新实例时调用`__new__`, 初始化一个实例时用`__init__`.
[stackoverflow](http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init)
# lambda 函数
其实就是一个匿名函数 , 为什么叫 lambda? 因为和后面的函数式编程有关 .
推荐 : [ 知乎 ](http://www.zhihu.com/question/20125256)
# Python 函数式编程
推荐 : [ 酷壳 ](http://coolshell.cn/articles/10822.html)
python 中函数式编程支持 :
filter 函数的功能相当于过滤器。调用一个布尔函数 bool_func 来迭代遍历每个 seq 中的元素返回一个使 bool_seq 返回值为 true 的元素的序列。
```python
>>>a = [1, 2, 3, 4, 5, 6, 7]
>>>b = filter(lambda x: x > 5, a)
>>>print b
>>>[6, 7]
```
map 函数是对一个序列的每个项依次执行函数下面是对一个序列每个项都乘以 2
```python
>>> a = map(lambda x:x*2,[1, 2, 3])
>>> list(a)
[2, 4, 6]
```
reduce 函数是对一个序列的每个项迭代调用函数下面是求 3 的阶乘
```python
>>> reduce(lambda x, y:x*y, range(1, 4))
6
```
# 闭包
闭包 (closure) 是函数式编程的重要的语法结构。
创建一个闭包必须满足以下几点 :
1. 必须有一个内嵌函数
2. 内嵌函数必须引用外部函数中的变量
3. 外部函数的返回值必须是内嵌函数
# 参考资料
- [Githubinterview_python](https://github.com/taizilongxu/interview_python)