里讲的也很多.
488 |
489 | > 一个旧式类的深度优先的例子
490 |
491 | ```python
492 | class A():
493 | def foo1(self):
494 | print("A")
495 | class B(A):
496 | def foo2(self):
497 | pass
498 | class C(A):
499 | def foo1(self):
500 | print("C")
501 | class D(B, C):
502 | pass
503 |
504 | d = D()
505 | d.foo1()
506 |
507 | # A
508 | ```
509 |
510 | **按照经典类的查找顺序`从左到右深度优先`的规则,在访问`d.foo1()`的时候,D这个类是没有的..那么往上查找,先找到B,里面没有,深度优先,访问A,找到了foo1(),所以这时候调用的是A的foo1(),从而导致C重写的foo1()被绕过**
511 |
512 |
513 |
514 | ## 15 `__new__`和`__init__`的区别
515 |
516 | 这个`__new__`确实很少见到,先做了解吧.
517 |
518 | 1. `__new__`是一个静态方法,而`__init__`是一个实例方法.
519 | 2. `__new__`方法会返回一个创建的实例,而`__init__`什么都不返回.
520 | 3. 只有在`__new__`返回一个cls的实例时后面的`__init__`才能被调用.
521 | 4. 当创建一个新实例时调用`__new__`,初始化一个实例时用`__init__`.
522 |
523 | [stackoverflow](http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init)
524 |
525 | ps: `__metaclass__`是创建类时起作用.所以我们可以分别使用`__metaclass__`,`__new__`和`__init__`来分别在类创建,实例创建和实例初始化的时候做一些小手脚.
526 |
527 | ## 16 单例模式
528 |
529 | > 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
530 | >
531 | > `__new__()`在`__init__()`之前被调用,用于生成实例对象。利用这个方法和类的属性的特点可以实现设计模式的单例模式。单例模式是指创建唯一对象,单例模式设计的类只能实例
532 | **这个绝对常考啊.绝对要记住1~2个方法,当时面试官是让手写的.**
533 |
534 | ### 1 使用`__new__`方法
535 |
536 | ```python
537 | class Singleton(object):
538 | def __new__(cls, *args, **kw):
539 | if not hasattr(cls, '_instance'):
540 | orig = super(Singleton, cls)
541 | cls._instance = orig.__new__(cls, *args, **kw)
542 | return cls._instance
543 |
544 | class MyClass(Singleton):
545 | a = 1
546 | ```
547 |
548 | ### 2 共享属性
549 |
550 | 创建实例时把所有实例的`__dict__`指向同一个字典,这样它们具有相同的属性和方法.
551 |
552 | ```python
553 |
554 | class Borg(object):
555 | _state = {}
556 | def __new__(cls, *args, **kw):
557 | ob = super(Borg, cls).__new__(cls, *args, **kw)
558 | ob.__dict__ = cls._state
559 | return ob
560 |
561 | class MyClass2(Borg):
562 | a = 1
563 | ```
564 |
565 | ### 3 装饰器版本
566 |
567 | ```python
568 | def singleton(cls):
569 | instances = {}
570 | def getinstance(*args, **kw):
571 | if cls not in instances:
572 | instances[cls] = cls(*args, **kw)
573 | return instances[cls]
574 | return getinstance
575 |
576 | @singleton
577 | class MyClass:
578 | ...
579 | ```
580 |
581 | ### 4 import方法
582 |
583 | 作为python的模块是天然的单例模式
584 |
585 | ```python
586 | # mysingleton.py
587 | class My_Singleton(object):
588 | def foo(self):
589 | pass
590 |
591 | my_singleton = My_Singleton()
592 |
593 | # to use
594 | from mysingleton import my_singleton
595 |
596 | my_singleton.foo()
597 |
598 | ```
599 | **[单例模式伯乐在线详细解释](http://python.jobbole.com/87294/)**
600 |
601 | ## 17 Python中的作用域
602 |
603 | Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。
604 |
605 | 当 Python 遇到一个变量的话他会按照这样的顺序进行搜索:
606 |
607 | 本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in)
608 |
609 | ## 18 GIL线程全局锁
610 |
611 | 线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.**对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。**
612 |
613 | 见[Python 最难的问题](http://www.oschina.net/translate/pythons-hardest-problem)
614 |
615 | 解决办法就是多进程和下面的协程(协程也只是单CPU,但是能减小切换代价提升性能).
616 |
617 | ## 19 协程
618 |
619 | 知乎被问到了,呵呵哒,跪了
620 |
621 | 简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
622 |
623 | Python里最常见的yield就是协程的思想!可以查看第九个问题.
624 |
625 |
626 | ## 20 闭包
627 |
628 | 闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
629 |
630 | 当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
631 |
632 | 1. 必须有一个内嵌函数
633 | 2. 内嵌函数必须引用外部函数中的变量
634 | 3. 外部函数的返回值必须是内嵌函数
635 |
636 | 感觉闭包还是有难度的,几句话是说不明白的,还是查查相关资料.
637 |
638 | 重点是函数运行后并不会被撤销,就像16题的instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里.这个功能类似类里的类变量,只不过迁移到了函数上.
639 |
640 | 闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样.
641 |
642 | ```python
643 | def count_time_wrapper(func):
644 | def improved_func():
645 | start_time = time.time()
646 | func()
647 | end_time = time.time()
648 | print(f"it takes {end_time - start_time}s to find all the olds")
649 |
650 | return improved_func
651 | # 闭包本质上是一个函数
652 | # 闭包函数的传入参数和返回值都是函数
653 | # 闭包函数的返回值函数是对传入函数进行增强的函数
654 | ```
655 |
656 | ## 21 lambda函数
657 |
658 | 其实就是一个匿名函数,为什么叫lambda?因为和后面的函数式编程有关.
659 |
660 | ```python
661 | f = lambda x: x * x
662 | print(f(4)) #16
663 |
664 | g = lambda x, y: x + y
665 | print(g(1, 2)) #3
666 |
667 |
668 | def que(a, b, c):
669 | return lambda x: a * x * x + b * x + c
670 |
671 | # 第一种写法
672 | f = que(-1, 1, 2) #-18
673 | print(f(5))
674 | # 第二种写法
675 | print(que(-1, 1, 2)(5)) #-18
676 | ```
677 |
678 | 推荐: [知乎](http://www.zhihu.com/question/20125256)
679 |
680 |
681 | ## 22 Python函数式编程
682 |
683 | 这个需要适当的了解一下吧,毕竟函数式编程在Python中也做了引用.
684 |
685 | 推荐: [酷壳](http://coolshell.cn/articles/10822.html)
686 |
687 | python中函数式编程支持:
688 |
689 | filter 函数的功能相当于过滤器。调用一个布尔函数`bool_func`来迭代遍历每个seq中的元素;返回一个使`bool_seq`返回值为true的元素的序列。
690 |
691 | ```python
692 | >>>a = [1,2,3,4,5,6,7]
693 | >>>b = filter(lambda x: x > 5, a)
694 | >>>print(b)
695 | >>>[6,7]
696 | ```
697 |
698 | map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:
699 |
700 | ```python
701 | >>> a = map(lambda x:x*2,[1,2,3])
702 | >>> list(a)
703 | [2, 4, 6]
704 | ```
705 |
706 | reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:
707 |
708 | ```python
709 | >>> reduce(lambda x,y:x*y,range(1,4))
710 | 6
711 | ```
712 |
713 | ## 23 Python里的拷贝
714 |
715 | 引用和copy(),deepcopy()的区别
716 |
717 | ```python
718 | import copy
719 | a = [1, 2, 3, 4, ['a', 'b']] #原始对象
720 |
721 | b = a #赋值,传对象的引用
722 | c = copy.copy(a) #对象拷贝,浅拷贝
723 | d = copy.deepcopy(a) #对象拷贝,深拷贝
724 |
725 | a.append(5) #修改对象a
726 | a[4].append('c') #修改对象a中的['a', 'b']数组对象
727 |
728 | print('a = ', a)
729 | print('b = ', b)
730 | print('c = ', c)
731 | print('d = ', d)
732 |
733 | 输出结果:
734 | a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
735 | b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
736 | c = [1, 2, 3, 4, ['a', 'b', 'c']]
737 | d = [1, 2, 3, 4, ['a', 'b']]
738 | ```
739 |
740 | **浅拷贝: 创建新对象,其内容是原对象的引用。**
741 |
742 | **深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。**
743 |
744 | ## 24 Python垃圾回收机制
745 |
746 | Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
747 |
748 | ### 1 引用计数
749 |
750 | PyObject是每个对象必有的内容,其中`ob_refcnt`就是做为引用计数。当一个对象有新的引用时,它的`ob_refcnt`就会增加,当引用它的对象被删除,它的`ob_refcnt`就会减少.引用计数为0时,该对象生命就结束了。
751 |
752 | 优点:
753 |
754 | 1. 简单
755 | 2. 实时性
756 |
757 | 缺点:
758 |
759 | 1. 维护引用计数消耗资源
760 | 2. 循环引用
761 |
762 | ### 2 标记-清除机制
763 |
764 | 基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
765 |
766 | ### 3 分代技术
767 |
768 | 分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
769 |
770 | Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
771 |
772 | 举例:
773 | 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
774 |
775 | ## 25 Python的List
776 |
777 | 推荐: http://www.jianshu.com/p/J4U6rR
778 |
779 | ## 26 Python的is
780 |
781 | is是对比地址,==是对比值
782 |
783 | ## 27 read,readline和readlines
784 |
785 | * read 读取整个文件
786 | * readline 读取下一行,使用生成器方法
787 | * readlines 读取整个文件到一个迭代器以供我们遍历
788 |
789 | ## 28 Python2和3的区别
790 | 推荐:[Python 2.7.x 与 Python 3.x 的主要差异](http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/)
791 |
792 | ## 29 super init
793 | super() lets you avoid referring to the base class explicitly, which can be nice. But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.
794 |
795 | Note that the syntax changed in Python 3.0: you can just say super().`__init__`() instead of super(ChildB, self).`__init__`() which IMO is quite a bit nicer.
796 |
797 | http://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
798 |
799 | [Python2.7中的super方法浅见](http://blog.csdn.net/mrlevo520/article/details/51712440)
800 |
801 | ## 30 range and xrange
802 | 都在循环时使用,xrange内存性能更好。
803 | for i in range(0, 20):
804 | for i in xrange(0, 20):
805 | What is the difference between range and xrange functions in Python 2.X?
806 | range creates a list, so if you do range(1, 10000000) it creates a list in memory with 9999999 elements.
807 | xrange is a sequence object that evaluates lazily.
808 |
809 | http://stackoverflow.com/questions/94935/what-is-the-difference-between-range-and-xrange-functions-in-python-2-x
810 |
811 | # 操作系统
812 |
813 | ## 1 select,poll和epoll
814 |
815 | 其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了.
816 |
817 | 这个问题可能有点深入了,但相信能回答出这个问题是对I/O多路复用有很好的了解了.其中tornado使用的就是epoll的.
818 |
819 | [selec,poll和epoll区别总结](http://www.cnblogs.com/Anker/p/3265058.html)
820 |
821 | 基本上select有3个缺点:
822 |
823 | 1. 连接数受限
824 | 2. 查找配对速度慢
825 | 3. 数据由内核拷贝到用户态
826 |
827 | poll改善了第一个缺点
828 |
829 | epoll改了三个缺点.
830 |
831 | 关于epoll的: http://www.cnblogs.com/my_life/articles/3968782.html
832 |
833 | ## 2 调度算法
834 |
835 | 1. 先来先服务(FCFS, First Come First Serve)
836 | 2. 短作业优先(SJF, Shortest Job First)
837 | 3. 最高优先权调度(Priority Scheduling)
838 | 4. 时间片轮转(RR, Round Robin)
839 | 5. 多级反馈队列调度(multilevel feedback queue scheduling)
840 |
841 | 常见的调度算法总结:http://www.jianshu.com/p/6edf8174c1eb
842 |
843 | 实时调度算法:
844 |
845 | 1. 最早截至时间优先 EDF
846 | 2. 最低松弛度优先 LLF
847 |
848 | ## 3 死锁
849 | 原因:
850 |
851 | 1. 竞争资源
852 | 2. 程序推进顺序不当
853 |
854 | 必要条件:
855 |
856 | 1. 互斥条件
857 | 2. 请求和保持条件
858 | 3. 不剥夺条件
859 | 4. 环路等待条件
860 |
861 | 处理死锁基本方法:
862 |
863 | 1. 预防死锁(摒弃除1以外的条件)
864 | 2. 避免死锁(银行家算法)
865 | 3. 检测死锁(资源分配图)
866 | 4. 解除死锁
867 | 1. 剥夺资源
868 | 2. 撤销进程
869 |
870 | 死锁概念处理策略详细介绍:https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/10.html
871 |
872 | ## 4 程序编译与链接
873 |
874 | 推荐: http://www.ruanyifeng.com/blog/2014/11/compiler.html
875 |
876 | Bulid过程可以分解为4个步骤:预处理(Prepressing), 编译(Compilation)、汇编(Assembly)、链接(Linking)
877 |
878 | 以c语言为例:
879 |
880 | ### 1 预处理
881 |
882 | 预编译过程主要处理那些源文件中的以“#”开始的预编译指令,主要处理规则有:
883 |
884 | 1. 将所有的“#define”删除,并展开所用的宏定义
885 | 2. 处理所有条件预编译指令,比如“#if”、“#ifdef”、 “#elif”、“#endif”
886 | 3. 处理“#include”预编译指令,将被包含的文件插入到该编译指令的位置,注:此过程是递归进行的
887 | 4. 删除所有注释
888 | 5. 添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时可显示行号
889 | 6. 保留所有的#pragma编译器指令。
890 |
891 | ### 2 编译
892 |
893 | 编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。这个过程是整个程序构建的核心部分。
894 |
895 | ### 3 汇编
896 |
897 | 汇编器是将汇编代码转化成机器可以执行的指令,每一条汇编语句几乎都是一条机器指令。经过编译、链接、汇编输出的文件成为目标文件(Object File)
898 |
899 | ### 4 链接
900 |
901 | 链接的主要内容就是把各个模块之间相互引用的部分处理好,使各个模块可以正确的拼接。
902 | 链接的主要过程包块 地址和空间的分配(Address and Storage Allocation)、符号决议(Symbol Resolution)和重定位(Relocation)等步骤。
903 |
904 | ## 5 静态链接和动态链接
905 |
906 | 静态链接方法:静态链接的时候,载入代码就会把程序会用到的动态代码或动态代码的地址确定下来
907 | 静态库的链接可以使用静态链接,动态链接库也可以使用这种方法链接导入库
908 |
909 | 动态链接方法:使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态链接的程序
910 |
911 | ## 6 虚拟内存技术
912 |
913 | 虚拟存储器是指具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充的一种存储系统.
914 |
915 | ## 7 分页和分段
916 |
917 | 分页: 用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
918 |
919 | 分段: 将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。
920 |
921 | ### 分页与分段的主要区别
922 |
923 | 1. 页是信息的物理单位,分页是为了实现非连续分配,以便解决内存碎片问题,或者说分页是由于系统管理的需要.段是信息的逻辑单位,它含有一组意义相对完整的信息,分段的目的是为了更好地实现共享,满足用户的需要.
924 | 2. 页的大小固定,由系统确定,将逻辑地址划分为页号和页内地址是由机器硬件实现的.而段的长度却不固定,决定于用户所编写的程序,通常由编译程序在对源程序进行编译时根据信息的性质来划分.
925 | 3. 分页的作业地址空间是一维的、分段的地址空间是二维的。
926 |
927 | ## 8 页面置换算法
928 |
929 | 页面置换:在地址映射过程中,若所要访问的页面不在内存中,则产生了‘缺页中断(page fault)’。此时操作系统必须在内存中选择一个页面将其移出内存,为即将调入的页面让出空间。
930 | 1. 最佳置换算法 OPT (optional replacement):被替换的页面为在未来最长时间内不会被访问的页面,可保证最低的缺页率,但不可能实现,主要用于评估算法。
931 | 2. 先进先出 FIFO:最易实现,但会频繁换页,性能差。
932 | 3. 最近最久未使用算法 LRU (Least Recently Used):最近一段时间里最久没有使用过的页面予以置换。
933 | 4. 时钟替换算法 (Clock):依照使用位替换页面。
934 |
935 | ## 9 边沿触发和水平触发
936 |
937 | 1. 边沿触发 (Edge Trigger):自上次状态改变后有新的 I/O 事件就会触发通知,需要尽可能多的执行 I/O 操作。
938 | 2. 水平触发 (Level Trigger):准备就绪时(可非阻塞地执行 I/O 系统调用)触发通知,可在任意时刻重复检测 I/O 状态。
939 |
940 | # 数据库
941 |
942 | ## 1 事务
943 |
944 | 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
945 | 彻底理解数据库事务: http://www.hollischuang.com/archives/898
946 |
947 | ## 2 数据库索引
948 |
949 | 推荐: https://zhuanlan.zhihu.com/p/113917726
950 | 聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理
951 |
952 | ## 3 Redis原理
953 |
954 | ### Redis是什么?
955 |
956 | 1. 是一个完全开源免费的key-value内存数据库
957 | 2. 通常被认为是一个数据结构服务器,主要是因为其有着丰富的数据结构 strings、map、 list、sets、 sorted sets
958 |
959 | ### Redis数据库
960 |
961 | > 通常局限点来说,Redis也以消息队列的形式存在,作为内嵌的List存在,满足实时的高并发需求。在使用缓存的时候,redis比memcached具有更多的优势,并且支持更多的数据类型,把redis当作一个中间存储系统,用来处理高并发的数据库操作
962 |
963 | - 速度快:使用标准C写,所有数据都在内存中完成,读写速度分别达到10万/20万
964 | - 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策略,一是根据时间,更新次数的快照(save 300 10 )二是基于语句追加方式(Append-only file,aof)
965 | - 自动操作:对不同数据类型的操作都是自动的,很安全
966 | - 快速的主--从复制,官方提供了一个数据,Slave在21秒即完成了对Amazon网站10G key set的复制。
967 | - Sharding技术: 很容易将数据分布到多个Redis实例中,数据库的扩展是个永恒的话题,在关系型数据库中,主要是以添加硬件、以分区为主要技术形式的纵向扩展解决了很多的应用场景,但随着web2.0、移动互联网、云计算等应用的兴起,这种扩展模式已经不太适合了,所以近年来,像采用主从配置、数据库复制形式的,Sharding这种技术把负载分布到多个特理节点上去的横向扩展方式用处越来越多。
968 |
969 | ### Redis缺点
970 |
971 | - 是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
972 | - Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
973 |
974 |
975 | ## 4 乐观锁和悲观锁
976 |
977 | 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
978 |
979 | 乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
980 |
981 | 乐观锁与悲观锁的具体区别: http://www.cnblogs.com/Bob-FD/p/3352216.html
982 |
983 | ## 5 MVCC
984 |
985 |
986 | > 全称是Multi-Version Concurrent Control,即多版本并发控制,在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。
987 |
988 | ### [MySQL](http://lib.csdn.net/base/mysql)的innodb引擎是如何实现MVCC的
989 |
990 | innodb会为每一行添加两个字段,分别表示该行**创建的版本**和**删除的版本**,填入的是事务的版本号,这个版本号随着事务的创建不断递增。在repeated read的隔离级别([事务的隔离级别请看这篇文章](http://blog.csdn.net/chosen0ne/article/details/10036775))下,具体各种数据库操作的实现:
991 |
992 | - select:满足以下两个条件innodb会返回该行数据:
993 | - 该行的创建版本号小于等于当前版本号,用于保证在select操作之前所有的操作已经执行落地。
994 | - 该行的删除版本号大于当前版本或者为空。删除版本号大于当前版本意味着有一个并发事务将该行删除了。
995 | - insert:将新插入的行的创建版本号设置为当前系统的版本号。
996 | - delete:将要删除的行的删除版本号设置为当前系统的版本号。
997 | - update:不执行原地update,而是转换成insert + delete。将旧行的删除版本号设置为当前版本号,并将新行insert同时设置创建版本号为当前版本号。
998 |
999 | 其中,写操作(insert、delete和update)执行时,需要将系统版本号递增。
1000 |
1001 | 由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。
1002 |
1003 | 通过MVCC很好的实现了事务的隔离性,可以达到repeated read级别,要实现serializable还必须加锁。
1004 |
1005 | > 参考:[MVCC浅析](http://blog.csdn.net/chosen0ne/article/details/18093187)
1006 |
1007 |
1008 |
1009 | ## 6 MyISAM和InnoDB
1010 |
1011 | MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
1012 |
1013 | InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
1014 |
1015 | mysql 数据库引擎: http://www.cnblogs.com/0201zcr/p/5296843.html
1016 | MySQL存储引擎--MyISAM与InnoDB区别: https://segmentfault.com/a/1190000008227211
1017 |
1018 | # 网络
1019 |
1020 | ## 1 三次握手
1021 |
1022 | 1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分。客户端把这段连接的序号设定为随机数 A。
1023 | 2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机序号 B。
1024 | 3. 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。
1025 |
1026 | ## 2 四次挥手
1027 |
1028 | _注意: 中断连接端可以是客户端,也可以是服务器端. 下面仅以客户端断开连接举例, 反之亦然._
1029 |
1030 | 1. 客户端发送一个数据分段, 其中的 FIN 标记设置为1. 客户端进入 FIN-WAIT 状态. 该状态下客户端只接收数据, 不再发送数据.
1031 | 2. 服务器接收到带有 FIN = 1 的数据分段, 发送带有 ACK = 1 的剩余数据分段, 确认收到客户端发来的 FIN 信息.
1032 | 3. 服务器等到所有数据传输结束, 向客户端发送一个带有 FIN = 1 的数据分段, 并进入 CLOSE-WAIT 状态, 等待客户端发来带有 ACK = 1 的确认报文.
1033 | 4. 客户端收到服务器发来带有 FIN = 1 的报文, 返回 ACK = 1 的报文确认, 为了防止服务器端未收到需要重发, 进入 TIME-WAIT 状态. 服务器接收到报文后关闭连接. 客户端等待 2MSL 后未收到回复, 则认为服务器成功关闭, 客户端关闭连接.
1034 |
1035 | 图解: http://blog.csdn.net/whuslei/article/details/6667471
1036 |
1037 | ## 3 ARP协议
1038 |
1039 | 地址解析协议(Address Resolution Protocol),其基本功能为透过目标设备的IP地址,查询目标的MAC地址,以保证通信的顺利进行。它是IPv4网络层必不可少的协议,不过在IPv6中已不再适用,并被邻居发现协议(NDP)所替代。
1040 |
1041 | ## 4 urllib和urllib2的区别
1042 |
1043 | 这个面试官确实问过,当时答的urllib2可以Post而urllib不可以.
1044 |
1045 | 1. urllib提供urlencode方法用来GET查询字符串的产生,而urllib2没有。这是为何urllib常和urllib2一起使用的原因。
1046 | 2. urllib2可以接受一个Request类的实例来设置URL请求的headers,urllib仅可以接受URL。这意味着,你不可以伪装你的User Agent字符串等。
1047 |
1048 |
1049 | ## 5 Post和Get
1050 | [GET和POST有什么区别?及为什么网上的多数答案都是错的](http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html)
1051 | [知乎回答](https://www.zhihu.com/question/31640769?rf=37401322)
1052 |
1053 | get: [RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://tools.ietf.org/html/rfc2616#section-9.3)
1054 | post: [RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1](http://tools.ietf.org/html/rfc2616#section-9.5)
1055 |
1056 |
1057 |
1058 | ## 6 Cookie和Session
1059 |
1060 | | | Cookie | Session |
1061 | | :--- | :------------------------- | :------ |
1062 | | 储存位置 | 客户端 | 服务器端 |
1063 | | 目的 | 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 | 跟踪会话 |
1064 | | 安全性 | 不安全 | 安全 |
1065 |
1066 | session技术是要使用到cookie的,之所以出现session技术,主要是为了安全。
1067 |
1068 | ## 7 apache和nginx的区别
1069 |
1070 | nginx 相对 apache 的优点:
1071 | * 轻量级,同样起web 服务,比apache 占用更少的内存及资源
1072 | * 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能
1073 | * 配置简洁
1074 | * 高度模块化的设计,编写模块相对简单
1075 | * 社区活跃
1076 |
1077 | apache 相对nginx 的优点:
1078 | * rewrite ,比nginx 的rewrite 强大
1079 | * 模块超多,基本想到的都可以找到
1080 | * 少bug ,nginx 的bug 相对较多
1081 | * 超稳定
1082 |
1083 | ## 8 网站用户密码保存
1084 |
1085 | 1. 明文保存
1086 | 2. 明文hash后保存,如md5
1087 | 3. MD5+Salt方式,这个salt可以随机
1088 | 4. 知乎使用了Bcrypy(好像)加密
1089 |
1090 | ## 9 HTTP和HTTPS
1091 |
1092 |
1093 | | 状态码 | 定义 |
1094 | | :-------- | :--------------- |
1095 | | 1xx 报告 | 接收到请求,继续进程 |
1096 | | 2xx 成功 | 步骤成功接收,被理解,并被接受 |
1097 | | 3xx 重定向 | 为了完成请求,必须采取进一步措施 |
1098 | | 4xx 客户端出错 | 请求包括错的顺序或不能完成 |
1099 | | 5xx 服务器出错 | 服务器无法完成显然有效的请求 |
1100 |
1101 | 403: Forbidden
1102 | 404: Not Found
1103 |
1104 | HTTPS握手,对称加密,非对称加密,TLS/SSL,RSA
1105 |
1106 | ## 10 XSRF和XSS
1107 |
1108 | * CSRF(Cross-site request forgery)跨站请求伪造
1109 | * XSS(Cross Site Scripting)跨站脚本攻击
1110 |
1111 | CSRF重点在请求,XSS重点在脚本
1112 |
1113 | ## 11 幂等 Idempotence
1114 |
1115 | HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的**副作用**。(注意是副作用)
1116 |
1117 | `GET http://www.bank.com/account/123456`,不会改变资源的状态,不论调用一次还是N次都没有副作用。请注意,这里强调的是一次和N次具有相同的副作用,而不是每次GET的结果相同。`GET http://www.news.com/latest-news`这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。
1118 |
1119 | DELETE方法用于删除资源,有副作用,但它应该满足幂等性。比如:`DELETE http://www.forum.com/article/4231`,调用一次和N次对系统产生的副作用是相同的,即删掉id为4231的帖子;因此,调用者可以多次调用或刷新页面而不必担心引起错误。
1120 |
1121 |
1122 | POST所对应的URI并非创建的资源本身,而是资源的接收者。比如:`POST http://www.forum.com/articles`的语义是在`http://www.forum.com/articles`下创建一篇帖子,HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。
1123 |
1124 | PUT所对应的URI是要创建或更新的资源本身。比如:`PUT http://www.forum/articles/4231`的语义是创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性。
1125 |
1126 |
1127 | ## 12 RESTful架构(SOAP,RPC)
1128 |
1129 | 推荐: http://www.ruanyifeng.com/blog/2011/09/restful.html
1130 |
1131 |
1132 | ## 13 SOAP
1133 |
1134 | SOAP(原为Simple Object Access Protocol的首字母缩写,即简单对象访问协议)是交换数据的一种协议规范,使用在计算机网络Web服务(web service)中,交换带结构信息。SOAP为了简化网页服务器(Web Server)从XML数据库中提取数据时,节省去格式化页面时间,以及不同应用程序之间按照HTTP通信协议,遵从XML格式执行资料互换,使其抽象于语言实现、平台和硬件。
1135 |
1136 | ## 14 RPC
1137 |
1138 | RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
1139 |
1140 | 总结:服务提供的两大流派.传统意义以方法调用为导向通称RPC。为了企业SOA,若干厂商联合推出webservice,制定了wsdl接口定义,传输soap.当互联网时代,臃肿SOA被简化为http+xml/json.但是简化出现各种混乱。以资源为导向,任何操作无非是对资源的增删改查,于是统一的REST出现了.
1141 |
1142 | 进化的顺序: RPC -> SOAP -> RESTful
1143 |
1144 | ## 15 CGI和WSGI
1145 | CGI是通用网关接口,是连接web服务器和应用程序的接口,用户通过CGI来获取动态数据或文件等。
1146 | CGI程序是一个独立的程序,它可以用几乎所有语言来写,包括perl,c,lua,python等等。
1147 |
1148 | WSGI, Web Server Gateway Interface,是Python应用程序或框架和Web服务器之间的一种接口,WSGI的其中一个目的就是让用户可以用统一的语言(Python)编写前后端。
1149 |
1150 | 官方说明:[PEP-3333](https://www.python.org/dev/peps/pep-3333/)
1151 |
1152 | ## 16 中间人攻击
1153 |
1154 | 在GFW里屡见不鲜的,呵呵.
1155 |
1156 | 中间人攻击(Man-in-the-middle attack,通常缩写为MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。
1157 |
1158 | ## 17 c10k问题
1159 |
1160 | 所谓c10k问题,指的是服务器同时支持成千上万个客户端的问题,也就是concurrent 10 000 connection(这也是c10k这个名字的由来)。
1161 | 推荐: https://my.oschina.net/xianggao/blog/664275
1162 |
1163 | ## 18 socket
1164 |
1165 | 推荐: http://www.360doc.com/content/11/0609/15/5482098_122692444.shtml
1166 |
1167 | Socket=Ip address+ TCP/UDP + port
1168 |
1169 | ## 19 浏览器缓存
1170 |
1171 | 推荐: http://www.cnblogs.com/skynet/archive/2012/11/28/2792503.html
1172 |
1173 | 304 Not Modified
1174 |
1175 | ## 20 HTTP1.0和HTTP1.1
1176 |
1177 | 推荐: http://blog.csdn.net/elifefly/article/details/3964766
1178 |
1179 | 1. 请求头Host字段,一个服务器多个网站
1180 | 2. 长链接
1181 | 3. 文件断点续传
1182 | 4. 身份认证,状态管理,Cache缓存
1183 |
1184 | HTTP请求8种方法介绍
1185 | HTTP/1.1协议中共定义了8种HTTP请求方法,HTTP请求方法也被叫做“请求动作”,不同的方法规定了不同的操作指定的资源方式。服务端也会根据不同的请求方法做不同的响应。
1186 |
1187 | GET
1188 |
1189 | GET请求会显示请求指定的资源。一般来说GET方法应该只用于数据的读取,而不应当用于会产生副作用的非幂等的操作中。
1190 |
1191 | GET会方法请求指定的页面信息,并返回响应主体,GET被认为是不安全的方法,因为GET方法会被网络蜘蛛等任意的访问。
1192 |
1193 | HEAD
1194 |
1195 | HEAD方法与GET方法一样,都是向服务器发出指定资源的请求。但是,服务器在响应HEAD请求时不会回传资源的内容部分,即:响应主体。这样,我们可以不传输全部内容的情况下,就可以获取服务器的响应头信息。HEAD方法常被用于客户端查看服务器的性能。
1196 |
1197 | POST
1198 |
1199 | POST请求会 向指定资源提交数据,请求服务器进行处理,如:表单数据提交、文件上传等,请求数据会被包含在请求体中。POST方法是非幂等的方法,因为这个请求可能会创建新的资源或/和修改现有资源。
1200 |
1201 | PUT
1202 |
1203 | PUT请求会身向指定资源位置上传其最新内容,PUT方法是幂等的方法。通过该方法客户端可以将指定资源的最新数据传送给服务器取代指定的资源的内容。
1204 |
1205 | DELETE
1206 |
1207 | DELETE请求用于请求服务器删除所请求URI(统一资源标识符,Uniform Resource Identifier)所标识的资源。DELETE请求后指定资源会被删除,DELETE方法也是幂等的。
1208 |
1209 | CONNECT
1210 |
1211 | CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。
1212 |
1213 | OPTIONS
1214 |
1215 | OPTIONS请求与HEAD类似,一般也是用于客户端查看服务器的性能。 这个方法会请求服务器返回该资源所支持的所有HTTP请求方法,该方法会用’*’来代替资源名称,向服务器发送OPTIONS请求,可以测试服务器功能是否正常。JavaScript的XMLHttpRequest对象进行CORS跨域资源共享时,就是使用OPTIONS方法发送嗅探请求,以判断是否有对指定资源的访问权限。 允许
1216 |
1217 | TRACE
1218 |
1219 | TRACE请求服务器回显其收到的请求信息,该方法主要用于HTTP请求的测试或诊断。
1220 |
1221 | HTTP/1.1之后增加的方法
1222 |
1223 | 在HTTP/1.1标准制定之后,又陆续扩展了一些方法。其中使用中较多的是 PATCH 方法:
1224 |
1225 | PATCH
1226 |
1227 | PATCH方法出现的较晚,它在2010年的RFC 5789标准中被定义。PATCH请求与PUT请求类似,同样用于资源的更新。二者有以下两点不同:
1228 |
1229 | 但PATCH一般用于资源的部分更新,而PUT一般用于资源的整体更新。
1230 | 当资源不存在时,PATCH会创建一个新的资源,而PUT只会对已在资源进行更新。
1231 |
1232 | ## 21 Ajax
1233 | AJAX,Asynchronous JavaScript and XML(异步的 JavaScript 和 XML), 是与在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。
1234 |
1235 | # *NIX
1236 |
1237 | ## unix进程间通信方式(IPC)
1238 |
1239 | 1. 管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
1240 | 2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
1241 | 3. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
1242 | 4. 消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
1243 | 5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
1244 | 6. 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
1245 | 7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
1246 | 8. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
1247 |
1248 |
1249 | # 数据结构
1250 |
1251 | ## 1 红黑树
1252 |
1253 | 红黑树与AVL的比较:
1254 |
1255 | AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多;
1256 |
1257 | 红黑是用非严格的平衡来换取增删节点时候旋转次数的降低;
1258 |
1259 | 所以简单说,如果你的应用中,搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择RB。
1260 |
1261 | 红黑树详解: https://xieguanglei.github.io/blog/post/red-black-tree.html
1262 |
1263 | 教你透彻了解红黑树: https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/03.01.md
1264 |
1265 | # 编程题
1266 |
1267 | ## 1 台阶问题/斐波那契
1268 |
1269 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
1270 |
1271 | ```python
1272 | fib = lambda n: n if n <= 2 else fib(n - 1) + fib(n - 2)
1273 | ```
1274 |
1275 | 第二种记忆方法
1276 |
1277 | ```python
1278 | def memo(func):
1279 | cache = {}
1280 | def wrap(*args):
1281 | if args not in cache:
1282 | cache[args] = func(*args)
1283 | return cache[args]
1284 | return wrap
1285 |
1286 |
1287 | @memo
1288 | def fib(i):
1289 | if i < 2:
1290 | return 1
1291 | return fib(i-1) + fib(i-2)
1292 | ```
1293 |
1294 | 第三种方法
1295 |
1296 | ```python
1297 | def fib(n):
1298 | a, b = 0, 1
1299 | for _ in xrange(n):
1300 | a, b = b, a + b
1301 | return b
1302 | ```
1303 |
1304 | ## 2 变态台阶问题
1305 |
1306 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
1307 |
1308 | ```python
1309 | fib = lambda n: n if n < 2 else 2 * fib(n - 1)
1310 | ```
1311 |
1312 | ## 3 矩形覆盖
1313 |
1314 | 我们可以用`2*1`的小矩形横着或者竖着去覆盖更大的矩形。请问用n个`2*1`的小矩形无重叠地覆盖一个`2*n`的大矩形,总共有多少种方法?
1315 |
1316 | >第`2*n`个矩形的覆盖方法等于第`2*(n-1)`加上第`2*(n-2)`的方法。
1317 |
1318 | ```python
1319 | f = lambda n: 1 if n < 2 else f(n - 1) + f(n - 2)
1320 | ```
1321 |
1322 | ## 4 杨氏矩阵查找
1323 |
1324 | 在一个m行n列二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
1325 |
1326 | 使用Step-wise线性搜索。
1327 |
1328 | ```python
1329 | def get_value(l, r, c):
1330 | return l[r][c]
1331 |
1332 | def find(l, x):
1333 | m = len(l) - 1
1334 | n = len(l[0]) - 1
1335 | r = 0
1336 | c = n
1337 | while c >= 0 and r <= m:
1338 | value = get_value(l, r, c)
1339 | if value == x:
1340 | return True
1341 | elif value > x:
1342 | c = c - 1
1343 | elif value < x:
1344 | r = r + 1
1345 | return False
1346 | ```
1347 |
1348 | ## 5 去除列表中的重复元素
1349 |
1350 | 用集合
1351 |
1352 | ```python
1353 | list(set(l))
1354 | ```
1355 |
1356 | 用字典
1357 |
1358 | ```python
1359 | l1 = ['b','c','d','b','c','a','a']
1360 | l2 = {}.fromkeys(l1).keys()
1361 | print(l2)
1362 | ```
1363 |
1364 | 用字典并保持顺序
1365 |
1366 | ```python
1367 | l1 = ['b','c','d','b','c','a','a']
1368 | l2 = list(set(l1))
1369 | l2.sort(key=l1.index)
1370 | print(l2)
1371 | ```
1372 |
1373 | 列表推导式
1374 |
1375 | ```python
1376 | l1 = ['b','c','d','b','c','a','a']
1377 | l2 = []
1378 | [l2.append(i) for i in l1 if not i in l2]
1379 | ```
1380 |
1381 | sorted排序并且用列表推导式.
1382 |
1383 | l = ['b','c','d','b','c','a','a']
1384 | [single.append(i) for i in sorted(l) if i not in single]
1385 | print single
1386 |
1387 | ## 6 链表成对调换
1388 |
1389 | `1->2->3->4`转换成`2->1->4->3`.
1390 |
1391 | ```python
1392 | class ListNode:
1393 | def __init__(self, x):
1394 | self.val = x
1395 | self.next = None
1396 |
1397 | class Solution:
1398 | # @param a ListNode
1399 | # @return a ListNode
1400 | def swapPairs(self, head):
1401 | if head != None and head.next != None:
1402 | next = head.next
1403 | head.next = self.swapPairs(next.next)
1404 | next.next = head
1405 | return next
1406 | return head
1407 | ```
1408 |
1409 | ## 7 创建字典的方法
1410 |
1411 | ### 1 直接创建
1412 |
1413 | ```python
1414 | dict = {'name':'earth', 'port':'80'}
1415 | ```
1416 |
1417 | ### 2 工厂方法
1418 |
1419 | ```python
1420 | items=[('name','earth'),('port','80')]
1421 | dict2=dict(items)
1422 | dict1=dict((['name','earth'],['port','80']))
1423 | ```
1424 |
1425 | ### 3 fromkeys()方法
1426 |
1427 | ```python
1428 | dict1={}.fromkeys(('x','y'),-1)
1429 | dict={'x':-1,'y':-1}
1430 | dict2={}.fromkeys(('x','y'))
1431 | dict2={'x':None, 'y':None}
1432 | ```
1433 |
1434 | ## 8 合并两个有序列表
1435 |
1436 | 知乎远程面试要求编程
1437 |
1438 | > 尾递归
1439 |
1440 | ```python
1441 | def _recursion_merge_sort2(l1, l2, tmp):
1442 | if len(l1) == 0 or len(l2) == 0:
1443 | tmp.extend(l1)
1444 | tmp.extend(l2)
1445 | return tmp
1446 | else:
1447 | if l1[0] < l2[0]:
1448 | tmp.append(l1[0])
1449 | del l1[0]
1450 | else:
1451 | tmp.append(l2[0])
1452 | del l2[0]
1453 | return _recursion_merge_sort2(l1, l2, tmp)
1454 |
1455 | def recursion_merge_sort2(l1, l2):
1456 | return _recursion_merge_sort2(l1, l2, [])
1457 | ```
1458 |
1459 | > 循环算法
1460 |
1461 | 思路:
1462 |
1463 | 定义一个新的空列表
1464 |
1465 | 比较两个列表的首个元素
1466 |
1467 | 小的就插入到新列表里
1468 |
1469 | 把已经插入新列表的元素从旧列表删除
1470 |
1471 | 直到两个旧列表有一个为空
1472 |
1473 | 再把旧列表加到新列表后面
1474 |
1475 |
1476 | ```python
1477 | def loop_merge_sort(l1, l2):
1478 | tmp = []
1479 | while len(l1) > 0 and len(l2) > 0:
1480 | if l1[0] < l2[0]:
1481 | tmp.append(l1[0])
1482 | del l1[0]
1483 | else:
1484 | tmp.append(l2[0])
1485 | del l2[0]
1486 | tmp.extend(l1)
1487 | tmp.extend(l2)
1488 | return tmp
1489 | ```
1490 |
1491 |
1492 | > pop弹出
1493 |
1494 | ```Python
1495 | a = [1,2,3,7]
1496 | b = [3,4,5]
1497 |
1498 | def merge_sortedlist(a,b):
1499 | c = []
1500 | while a and b:
1501 | if a[0] >= b[0]:
1502 | c.append(b.pop(0))
1503 | else:
1504 | c.append(a.pop(0))
1505 | while a:
1506 | c.append(a.pop(0))
1507 | while b:
1508 | c.append(b.pop(0))
1509 | return c
1510 | print merge_sortedlist(a,b)
1511 |
1512 | ```
1513 |
1514 |
1515 | ## 9 交叉链表求交点
1516 |
1517 | > 其实思想可以按照从尾开始比较两个链表,如果相交,则从尾开始必然一致,只要从尾开始比较,直至不一致的地方即为交叉点,如图所示
1518 |
1519 | 
1520 |
1521 | ```python
1522 | # 使用a,b两个list来模拟链表,可以看出交叉点是 7这个节点
1523 | a = [1,2,3,7,9,1,5]
1524 | b = [4,5,7,9,1,5]
1525 |
1526 | for i in range(1,min(len(a),len(b))):
1527 | if i==1 and (a[-1] != b[-1]):
1528 | print "No"
1529 | break
1530 | else:
1531 | if a[-i] != b[-i]:
1532 | print "交叉节点:",a[-i+1]
1533 | break
1534 | else:
1535 | pass
1536 | ```
1537 |
1538 | > 另外一种比较正规的方法,构造链表类
1539 |
1540 | ```python
1541 | class ListNode:
1542 | def __init__(self, x):
1543 | self.val = x
1544 | self.next = None
1545 | def node(l1, l2):
1546 | length1, lenth2 = 0, 0
1547 | # 求两个链表长度
1548 | while l1.next:
1549 | l1 = l1.next
1550 | length1 += 1
1551 | while l2.next:
1552 | l2 = l2.next
1553 | length2 += 1
1554 | # 长的链表先走
1555 | if length1 > lenth2:
1556 | for _ in range(length1 - length2):
1557 | l1 = l1.next
1558 | else:
1559 | for _ in range(length2 - length1):
1560 | l2 = l2.next
1561 | while l1 and l2:
1562 | if l1.next == l2.next:
1563 | return l1.next
1564 | else:
1565 | l1 = l1.next
1566 | l2 = l2.next
1567 | ```
1568 |
1569 | 修改了一下:
1570 |
1571 |
1572 | ```python
1573 | #coding:utf-8
1574 | class ListNode:
1575 | def __init__(self, x):
1576 | self.val = x
1577 | self.next = None
1578 |
1579 | def node(l1, l2):
1580 | length1, length2 = 0, 0
1581 | # 求两个链表长度
1582 | while l1.next:
1583 | l1 = l1.next#尾节点
1584 | length1 += 1
1585 | while l2.next:
1586 | l2 = l2.next#尾节点
1587 | length2 += 1
1588 |
1589 | #如果相交
1590 | if l1.next == l2.next:
1591 | # 长的链表先走
1592 | if length1 > length2:
1593 | for _ in range(length1 - length2):
1594 | l1 = l1.next
1595 | return l1#返回交点
1596 | else:
1597 | for _ in range(length2 - length1):
1598 | l2 = l2.next
1599 | return l2#返回交点
1600 | # 如果不相交
1601 | else:
1602 | return
1603 | ```
1604 |
1605 |
1606 | 思路: http://humaoli.blog.163.com/blog/static/13346651820141125102125995/
1607 |
1608 |
1609 | ## 10 二分查找
1610 |
1611 |
1612 | ```python
1613 |
1614 | #coding:utf-8
1615 | def binary_search(list, item):
1616 | low = 0
1617 | high = len(list) - 1
1618 | while low <= high:
1619 | mid = (high - low) / 2 + low # 避免(high + low) / 2溢出
1620 | guess = list[mid]
1621 | if guess > item:
1622 | high = mid - 1
1623 | elif guess < item:
1624 | low = mid + 1
1625 | else:
1626 | return mid
1627 | return None
1628 | mylist = [1,3,5,7,9]
1629 | print(binary_search(mylist, 3))
1630 |
1631 | ```
1632 |
1633 | 参考: http://blog.csdn.net/u013205877/article/details/76411718
1634 |
1635 | ## 11 快排
1636 |
1637 | ```python
1638 | #coding:utf-8
1639 | def quicksort(list):
1640 | if len(list)<2:
1641 | return list
1642 | else:
1643 | midpivot = list[0]
1644 | lessbeforemidpivot = [i for i in list[1:] if i<=midpivot]
1645 | biggerafterpivot = [i for i in list[1:] if i > midpivot]
1646 | finallylist = quicksort(lessbeforemidpivot)+[midpivot]+quicksort(biggerafterpivot)
1647 | return finallylist
1648 |
1649 | print(quicksort([2,4,6,7,1,2,5]))
1650 | ```
1651 |
1652 |
1653 | > 更多排序问题可见:[数据结构与算法-排序篇-Python描述](http://blog.csdn.net/mrlevo520/article/details/77829204)
1654 |
1655 |
1656 | ## 12 找零问题
1657 |
1658 |
1659 | ```python
1660 |
1661 | #coding:utf-8
1662 | #values是硬币的面值values = [ 25, 21, 10, 5, 1]
1663 | #valuesCounts 钱币对应的种类数
1664 | #money 找出来的总钱数
1665 | #coinsUsed 对应于目前钱币总数i所使用的硬币数目
1666 |
1667 | def coinChange(values,valuesCounts,money,coinsUsed):
1668 | #遍历出从1到money所有的钱数可能
1669 | for cents in range(1,money+1):
1670 | minCoins = cents
1671 | #把所有的硬币面值遍历出来和钱数做对比
1672 | for kind in range(0,valuesCounts):
1673 | if (values[kind] <= cents):
1674 | temp = coinsUsed[cents - values[kind]] +1
1675 | if (temp < minCoins):
1676 | minCoins = temp
1677 | coinsUsed[cents] = minCoins
1678 | print('面值:{0}的最少硬币使用数为:{1}'.format(cents, coinsUsed[cents]))
1679 |
1680 | ```
1681 |
1682 | 思路: http://blog.csdn.net/wdxin1322/article/details/9501163
1683 |
1684 | 方法: http://www.cnblogs.com/ChenxofHit/archive/2011/03/18/1988431.html
1685 |
1686 | ## 13 广度遍历和深度遍历二叉树
1687 |
1688 | 给定一个数组,构建二叉树,并且按层次打印这个二叉树
1689 |
1690 |
1691 | ## 14 二叉树节点
1692 |
1693 | ```python
1694 |
1695 | class Node(object):
1696 | def __init__(self, data, left=None, right=None):
1697 | self.data = data
1698 | self.left = left
1699 | self.right = right
1700 |
1701 | tree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4)))
1702 |
1703 | ```
1704 |
1705 | ## 15 层次遍历
1706 |
1707 | ```python
1708 |
1709 | def lookup(root):
1710 | row = [root]
1711 | while row:
1712 | print(row)
1713 | row = [kid for item in row for kid in (item.left, item.right) if kid]
1714 |
1715 | ```
1716 |
1717 | ## 16 深度遍历
1718 |
1719 | ```python
1720 |
1721 | def deep(root):
1722 | if not root:
1723 | return
1724 | print(root.data)
1725 | deep(root.left)
1726 | deep(root.right)
1727 |
1728 | if __name__ == '__main__':
1729 | lookup(tree)
1730 | deep(tree)
1731 | ```
1732 |
1733 | ## 17 前中后序遍历
1734 |
1735 | 深度遍历改变顺序就OK了
1736 |
1737 | ```python
1738 |
1739 | #coding:utf-8
1740 | #二叉树的遍历
1741 | #简单的二叉树节点类
1742 | class Node(object):
1743 | def __init__(self,value,left,right):
1744 | self.value = value
1745 | self.left = left
1746 | self.right = right
1747 |
1748 | #中序遍历:遍历左子树,访问当前节点,遍历右子树
1749 |
1750 | def mid_travelsal(root):
1751 | if root.left is not None:
1752 | mid_travelsal(root.left)
1753 | #访问当前节点
1754 | print(root.value)
1755 | if root.right is not None:
1756 | mid_travelsal(root.right)
1757 |
1758 | #前序遍历:访问当前节点,遍历左子树,遍历右子树
1759 |
1760 | def pre_travelsal(root):
1761 | print (root.value)
1762 | if root.left is not None:
1763 | pre_travelsal(root.left)
1764 | if root.right is not None:
1765 | pre_travelsal(root.right)
1766 |
1767 | #后续遍历:遍历左子树,遍历右子树,访问当前节点
1768 |
1769 | def post_trvelsal(root):
1770 | if root.left is not None:
1771 | post_trvelsal(root.left)
1772 | if root.right is not None:
1773 | post_trvelsal(root.right)
1774 | print(root.value)
1775 |
1776 | ```
1777 |
1778 | ## 18 求最大树深
1779 |
1780 | ```python
1781 | def maxDepth(root):
1782 | if not root:
1783 | return 0
1784 | return max(maxDepth(root.left), maxDepth(root.right)) + 1
1785 | ```
1786 |
1787 | ## 19 求两棵树是否相同
1788 |
1789 | ```python
1790 | def isSameTree(p, q):
1791 | if p == None and q == None:
1792 | return True
1793 | elif p and q:
1794 | return p.val == q.val and isSameTree(p.left,q.left) and isSameTree(p.right,q.right)
1795 | else:
1796 | return False
1797 | ```
1798 |
1799 | ## 20 前序中序求后序
1800 |
1801 | 推荐: http://blog.csdn.net/hinyunsin/article/details/6315502
1802 |
1803 | ```python
1804 | def rebuild(pre, center):
1805 | if not pre:
1806 | return
1807 | cur = Node(pre[0])
1808 | index = center.index(pre[0])
1809 | cur.left = rebuild(pre[1:index + 1], center[:index])
1810 | cur.right = rebuild(pre[index + 1:], center[index + 1:])
1811 | return cur
1812 |
1813 | def deep(root):
1814 | if not root:
1815 | return
1816 | deep(root.left)
1817 | deep(root.right)
1818 | print(root.data)
1819 | ```
1820 |
1821 | ## 21 单链表逆置
1822 |
1823 | ```python
1824 | class Node(object):
1825 | def __init__(self, data=None, next=None):
1826 | self.data = data
1827 | self.next = next
1828 |
1829 | link = Node(1, Node(2, Node(3, Node(4, Node(5, Node(6, Node(7, Node(8, Node(9)))))))))
1830 |
1831 | def rev(link):
1832 | pre = link
1833 | cur = link.next
1834 | pre.next = None
1835 | while cur:
1836 | tmp = cur.next
1837 | cur.next = pre
1838 | pre = cur
1839 | cur = tmp
1840 | return pre
1841 |
1842 | root = rev(link)
1843 | while root:
1844 | print(root.data)
1845 | root = root.next
1846 | ```
1847 |
1848 | 思路: http://blog.csdn.net/feliciafay/article/details/6841115
1849 |
1850 | 方法: http://www.xuebuyuan.com/2066385.html?mobile=1
1851 |
1852 |
1853 | ## 22 两个字符串是否是变位词
1854 |
1855 | ```python
1856 | class Anagram:
1857 | """
1858 | @:param s1: The first string
1859 | @:param s2: The second string
1860 | @:return true or false
1861 | """
1862 | def Solution1(s1,s2):
1863 | alist = list(s2)
1864 |
1865 | pos1 = 0
1866 | stillOK = True
1867 |
1868 | while pos1 < len(s1) and stillOK:
1869 | pos2 = 0
1870 | found = False
1871 | while pos2 < len(alist) and not found:
1872 | if s1[pos1] == alist[pos2]:
1873 | found = True
1874 | else:
1875 | pos2 = pos2 + 1
1876 |
1877 | if found:
1878 | alist[pos2] = None
1879 | else:
1880 | stillOK = False
1881 |
1882 | pos1 = pos1 + 1
1883 |
1884 | return stillOK
1885 |
1886 | print(Solution1('abcd','dcba'))
1887 |
1888 | def Solution2(s1,s2):
1889 | alist1 = list(s1)
1890 | alist2 = list(s2)
1891 |
1892 | alist1.sort()
1893 | alist2.sort()
1894 |
1895 |
1896 | pos = 0
1897 | matches = True
1898 |
1899 | while pos < len(s1) and matches:
1900 | if alist1[pos] == alist2[pos]:
1901 | pos = pos + 1
1902 | else:
1903 | matches = False
1904 |
1905 | return matches
1906 |
1907 | print(Solution2('abcde','edcbg'))
1908 |
1909 | def Solution3(s1,s2):
1910 | c1 = [0]*26
1911 | c2 = [0]*26
1912 |
1913 | for i in range(len(s1)):
1914 | pos = ord(s1[i])-ord('a')
1915 | c1[pos] = c1[pos] + 1
1916 |
1917 | for i in range(len(s2)):
1918 | pos = ord(s2[i])-ord('a')
1919 | c2[pos] = c2[pos] + 1
1920 |
1921 | j = 0
1922 | stillOK = True
1923 | while j<26 and stillOK:
1924 | if c1[j] == c2[j]:
1925 | j = j + 1
1926 | else:
1927 | stillOK = False
1928 |
1929 | return stillOK
1930 |
1931 | print(Solution3('apple','pleap'))
1932 | ```
1933 |
1934 |
1935 |
1936 |
1937 | ## 23 动态规划问题
1938 |
1939 | > 可参考:[动态规划(DP)的整理-Python描述](http://blog.csdn.net/mrlevo520/article/details/75676160)
1940 |
1941 |
--------------------------------------------------------------------------------
/gh-md-toc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #
4 | # Steps:
5 | #
6 | # 1. Download corresponding html file for some README.md:
7 | # curl -s $1
8 | #
9 | # 2. Discard rows where no substring 'user-content-' (github's markup):
10 | # awk '/user-content-/ { ...
11 | #
12 | # 3.1 Get last number in each row like ' ... sitemap.js.*<\/h/)+2, RLENGTH-5)
21 | #
22 | # 5. Find anchor and insert it inside "(...)":
23 | # substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8)
24 | #
25 |
26 | gh_toc_version="0.4.8"
27 |
28 | gh_user_agent="gh-md-toc v$gh_toc_version"
29 |
30 | #
31 | # Download rendered into html README.md by its url.
32 | #
33 | #
34 | gh_toc_load() {
35 | local gh_url=$1
36 |
37 | if type curl &>/dev/null; then
38 | curl --user-agent "$gh_user_agent" -s "$gh_url"
39 | elif type wget &>/dev/null; then
40 | wget --user-agent="$gh_user_agent" -qO- "$gh_url"
41 | else
42 | echo "Please, install 'curl' or 'wget' and try again."
43 | exit 1
44 | fi
45 | }
46 |
47 | #
48 | # Converts local md file into html by GitHub
49 | #
50 | # ➥ curl -X POST --data '{"text": "Hello world github/linguist#1 **cool**, and #1!"}' https://api.github.com/markdown
51 | # Hello world github/linguist#1 cool, and #1!
'"
52 | gh_toc_md2html() {
53 | local gh_file_md=$1
54 | URL=https://api.github.com/markdown/raw
55 | TOKEN="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/token.txt"
56 | if [ -f "$TOKEN" ]; then
57 | URL="$URL?access_token=$(cat $TOKEN)"
58 | fi
59 | curl -s --user-agent "$gh_user_agent" \
60 | --data-binary @"$gh_file_md" -H "Content-Type:text/plain" \
61 | $URL
62 | }
63 |
64 | #
65 | # Is passed string url
66 | #
67 | gh_is_url() {
68 | case $1 in
69 | https* | http*)
70 | echo "yes";;
71 | *)
72 | echo "no";;
73 | esac
74 | }
75 |
76 | #
77 | # TOC generator
78 | #
79 | gh_toc(){
80 | local gh_src=$1
81 | local gh_src_copy=$1
82 | local gh_ttl_docs=$2
83 |
84 | if [ "$gh_src" = "" ]; then
85 | echo "Please, enter URL or local path for a README.md"
86 | exit 1
87 | fi
88 |
89 |
90 | # Show "TOC" string only if working with one document
91 | if [ "$gh_ttl_docs" = "1" ]; then
92 |
93 | echo "Table of Contents"
94 | echo "================="
95 | echo ""
96 | gh_src_copy=""
97 |
98 | fi
99 |
100 | if [ "$(gh_is_url "$gh_src")" == "yes" ]; then
101 | gh_toc_load "$gh_src" | gh_toc_grab "$gh_src_copy"
102 | else
103 | gh_toc_md2html "$gh_src" | gh_toc_grab "$gh_src_copy"
104 | fi
105 | }
106 |
107 | #
108 | # Grabber of the TOC from rendered html
109 | #
110 | # $1 — a source url of document.
111 | # It's need if TOC is generated for multiple documents.
112 | #
113 | gh_toc_grab() {
114 | # if closed is on the new line, then move it on the prev line
115 | # for example:
116 | # was: The command foo1
117 | #
118 | # became: The command foo1
119 | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n<\/h/<\/h/g' |
120 | # find strings that corresponds to template
121 | grep -E -o '//' | sed 's/<\/code>//' |
124 | # now all rows are like:
125 | # ... .*<\/h/)+2, RLENGTH-5)"](" gh_url substr($0, match($0, "href=\"[^\"]+?\" ")+6, RLENGTH-8) ")"}' | sed 'y/+/ /; s/%/\\x/g')"
130 | }
131 |
132 | #
133 | # Returns filename only from full path or url
134 | #
135 | gh_toc_get_filename() {
136 | echo "${1##*/}"
137 | }
138 |
139 | #
140 | # Options hendlers
141 | #
142 | gh_toc_app() {
143 | local app_name="gh-md-toc"
144 |
145 | if [ "$1" = '--help' ] || [ $# -eq 0 ] ; then
146 | echo "GitHub TOC generator ($app_name): $gh_toc_version"
147 | echo ""
148 | echo "Usage:"
149 | echo " $app_name src [src] Create TOC for a README file (url or local path)"
150 | echo " $app_name - Create TOC for markdown from STDIN"
151 | echo " $app_name --help Show help"
152 | echo " $app_name --version Show version"
153 | return
154 | fi
155 |
156 | if [ "$1" = '--version' ]; then
157 | echo "$gh_toc_version"
158 | return
159 | fi
160 |
161 | if [ "$1" = "-" ]; then
162 | if [ -z "$TMPDIR" ]; then
163 | TMPDIR="/tmp"
164 | elif [ -n "$TMPDIR" -a ! -d "$TMPDIR" ]; then
165 | mkdir -p "$TMPDIR"
166 | fi
167 | local gh_tmp_md
168 | gh_tmp_md=$(mktemp $TMPDIR/tmp.XXXXXX)
169 | while read input; do
170 | echo "$input" >> "$gh_tmp_md"
171 | done
172 | gh_toc_md2html "$gh_tmp_md" | gh_toc_grab ""
173 | return
174 | fi
175 |
176 | for md in "$@"
177 | do
178 | echo ""
179 | gh_toc "$md" "$#"
180 | done
181 |
182 | echo ""
183 | echo "Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)"
184 | }
185 |
186 | #
187 | # Entry point
188 | #
189 | gh_toc_app "$@"
190 |
--------------------------------------------------------------------------------
/img/builder.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taizilongxu/interview_python/28fa4704a80ea1cc5bb8edfebe5c5b504e00f1c8/img/builder.graffle
--------------------------------------------------------------------------------
/img/builder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/taizilongxu/interview_python/28fa4704a80ea1cc5bb8edfebe5c5b504e00f1c8/img/builder.png
--------------------------------------------------------------------------------
/pattern.md:
--------------------------------------------------------------------------------
1 | # Python设计模式
2 |
3 | 代码直戳: https://github.com/faif/python-patterns
4 |
5 | # 创建型模式
6 |
7 | ## 工厂方法
8 |
9 |
10 | 实例 -> 类 -> 类工厂
11 |
12 | ## 抽象工厂
13 |
14 | 简单来说就是把一些具有相同方法的类再进行封装,抽象共同的方法以供调用.是工厂方法的进阶版本.
15 |
16 | 实例 -> 类 -> 类工厂 -> 抽象工厂
17 |
18 | ## 惰性初始化 Lazy evaluation
19 |
20 | 这个Python里可以使用@property实现,就是当调用的时候才生成.
21 |
22 | ## 生成器 Builder
23 |
24 | 
25 |
26 | Builder模式主要用于构建一个复杂的对象,但这个对象构建的算法是稳定的,对象中的各个部分经常变化。Builder模式主要在于应对复杂对象各个部分的频繁需求变动。但是难以应对算法的需求变动。这点一定要注意,如果用错了,会带来很多不必要的麻烦。
27 |
28 | 重点是将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
29 |
30 | 简单的说:子对象变化较频繁,对算法相对稳定。
31 |
32 | ## 单例模式 Singleton
33 |
34 | 一个类只有一个实例
35 |
36 | ## 原型模式
37 |
38 | 特点是通过复制一个已经存在的实例来返回新的实例,而不是新建实例.
39 |
40 | 多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据.
41 |
42 | ## 对象池 Object pool
43 |
44 | 一个对象池是一组已经初始化过且可以使用的对象,而可以不用在有需求时创建和销毁对象。池的用户可以从池子中取得对象,对其进行操作处理,并在不需要时归还给池子而非销毁 而不是销毁它.
45 |
46 | 在Python内部实现了对象池技术.例如像小整型这样的数据引用非常多,创建销毁都会消耗时间,所以保存在对象池里,减少开销.
47 |
48 | # 结构型模式
49 |
50 | ## 修饰模型 Decorator
51 |
52 | Python里就是装饰器.
53 |
54 | ## 代理模式 Proxy
55 |
56 | 例如Python里的引用计数.
57 |
58 | # 行为型模式
59 |
60 | ## 迭代器
61 |
62 | 迭代容器里所有的元素.
63 |
64 |
65 |
--------------------------------------------------------------------------------