面试准备——(二)专业知识(2)Python
面试遇到的问题:
滴滴:
1. Python的数据结构
2. list和tuple的区别
3. list中有哪些操作?append和extend的区别?
4. list和dict的却别?dict是有序的吗?
5. 如果a = dict, b =a 其中dict发生了改变,b会改变吗?这是一个浅拷贝还是深拷贝?这是引用吗?
如果把dict换成list呢?
6. 你用用过多线程吗?多线程的作用是什么?
7. 如何实现Singleton?这个Singleton中有什么变量?
8. 静态方法、类方法是什么?类的成员方法能访问实例变量吗?能访问类的变量吗?能访问静态类方法吗?
9. 你用过哪些python的包
美团:
1. python语言的特点
2. Python装饰器(两面都问到)
今日头条:
1. Python的数据结构
2. list和set区别
**1. Python数据结构
——美团(滴滴),介绍一下Python的数据结构,并说明它们有什么操作
四个基本数据结构:list、tuple、dict、set
在python中,字符串 tuples, 和数字是不可更改的对象,而list,dict等则是可以修改的对象。
1. list
1). list的操作有哪些?——滴滴
可以用可以使用dir()和help()查询
2)比较list.append()和extend(),他们是生成一个新的列表还是原来的列表?
- append()接收任意数据类型的参数,并且简单的加在list的末尾,当作一个元素。——同一个list
- extend():——同一个list
- 可以接收list,将这个list中的每个元素都增加到原来的list中。所以如果增加的list有n个元素,原list有m个元素,则使用extend()扩充以后,list有m+n个元素
- 也可以接收dic:将每个key值添加到原list中
- 也可以接收tuple
例如:
>>> l1 = [1,2, 3, 4, 5] >>> l2 = [7, 8, 9] >>> l3 = ('a', 'b', 'c') >>> t3 = ('a', 'b', 'c') >>> d4 = {'a':1, 'b':2, 'c':3} >>> id(l1) 4330496200 >>> l1.append(l2) >>> id(l1) #append()是同一个list 4330496200 >>> l1 [1, 2, 3, 4, 5, [7, 8, 9]] #append()可以接收list,l2当作一个元素加入到l1中 >>> len(l1) 6 >>> l1.append(t3) #append()可以接收tuple,也当作一个元素 >>> l1 [1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c')] >>> len(l1) 7 >>> l1.append(d4) #append()可以接收dict,也当作一个元素 >>> l1 [1, 2, 3, 4, 5, [7, 8, 9], ('a', 'b', 'c'), {'a': 1, 'c': 3, 'b': 2}] >>> len(l1[7]) 3 # extend >>> l1 = [1,2, 3, 4, 5] >>> id(l1) 4330496200 >>> l1.extend(l2) #[1, 2, 3, 4, 5, 7, 8, 9] >>> id(l1) # 使用extend()是同一个list 4330496200 >>> l1.extend(t3) #extend()可以接收tuple,将其中所有的元素扩充到list中 >>> l1 [1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c'] >>> l1.extend(d4) ##extend()可以接收dict,将其中所有的key值扩充到list中 >>> l1 [1, 2, 3, 4, 5, 7, 8, 9, 'a', 'b', 'c', 'a', 'c', 'b']
3)合并list:——两个方法
# 方法一:+ 一个新的list l3 = l1 + l2 # 方法二:extend() #还是原来的list l1.extend(l2)
4)list使用切片操作,会生成新的list吗?——正向、负向、浅拷贝
会。因为切片操作相当于浅拷贝,深成了一个新的list对象。
list = [1, 2, 3, 4, 5] # list是有序的,所以可以根据索引来查找值 # 1. 正向 list[1:4, 2] #[1, 3] # 2. 负向 list[-1:-4:-2] #[5, 3] #切面相当于浅拷贝——新的list >>> id(list) #4330445000 >>> id(list[-1:-5]) #4330479176
2. tuple
1)list和tuple的区别——滴滴
list | tuple | |
创建 | list = [1, 2, 3, 4, 5] | t1 =(1,) # 如果只有一个元素,必须用“,”隔开,否则默认为‘int’类型 t2 = (1, 2, 3) t3 = (1, 2, 3, ['a', 'b']) # tuple中可以嵌套list |
元素是否可更改 (最大的不同) |
可更改: list.extend() list.remove() |
不可更改,所以不具备增、删、改。 如果删除:del t #删除整个元素 对于t3,tuple中嵌套有list,则可以更改那个list的值: t3[3].append('c') #t3 = (1, 2, 3, ['a', 'b', 'c']) |
切片操作 |
相当于浅拷贝(新的list、tuple) 正向、负向 |
|
是否有序 | 有序(按定义的次序进行排序) | |
优势 |
1. tuple操作比list更快。 |
|
转换 |
1. tuple(l) |
2)基本操作:
3. dict:
1)dict有哪些操作?
另外:dict推导式:
d = {key: value for (key, value) in iterable}
2)dict是有序的吗?
——不是,list、tuple是有序的
3)如果a = dictR, b=a, dictR修改了,a、b会变化吗?这属于引用吗?这是浅拷贝还是深拷贝?
这是赋值引用。如果更改a的值,b会随之变化。如果更改b的值,a会随之变化。
如果改成了list:同样。
如果改成了tuple,或者一个非容器类型(例如:数字、字符以及其他原子类型),没有拷贝一说,只能是引用,a、b发生变化都不会影响彼此的值。但是如果tuple中含有嵌套的list,改变这个list中的值,会互相影响。
(详情见:下面赋值、浅拷贝、深拷贝)
4. set
**2. Python中赋值引用、浅拷贝与深拷贝
Python中关于对象复制有三种类型的使用方式,赋值、浅拷贝与深拷贝。
1. 赋值(函数的参数/返回值)——对象引用
在Python中赋值就是对象的引用。不会开辟新的空间,只是复制了对象的引用。
包括两种形式:
- b = a
- 对象作为函数参数或返回值
分为了两种情况:
- 不可更改对象:tuple、数字、字符等原子类型——a、b的改变互不影响(相当于指向了另一个对象的内存空间)
- 可更改的对象:list、dict、set——a、b改变都会互相影响(相当于内存中的对象改变)
#一、不可更改对象 # 1. 数字 >>> a = 5 >>> b = a #b = 5 >>> a = 3 # b = 5 :a改变,b不会改变 >>> b = 2 # a = 3:b改变,a不会改变 # 2. 字符串 >>> a = 'sbw' >>> b = a #b = 'sbw' >>> a = 'lf' #b = 'sbw':a改变,b不会改变 >>> b = 'wf' #a = 'lf' :b改变,a不会改变 # 3. tuple >>> a = (1, 2, 3) >>> b = a #b = (1, 2, 3) >>> a = ('a', 'b', 'c') #b = (1, 2, 3):a变化,b不会改变 >>> b = ('d', 'e') # a = ('a', 'b', 'c'):b改变,a不会改变
# 二、可更改的对象 # 1. list >>> l1 = [1, 2 ,3, ['a', 'b']] >>> l2 =l1 #l2 = [1, 2, 3, ['a', 'b']] >>> l1.append('new_l1') #l2 = [1, 2, 3, ['a', 'b'], 'new_l1'] :b随a变化 >>> l2.append('new_l2') #l1 = [1, 2, 3, ['a', 'b'], 'new_l1', 'new_l2']:a随b变化 # 同理:dict和set
2. 浅拷贝
浅拷贝会创建新对象(两个对象的id不同),但是其内容是原对象的引用。
一般情况下,一方改变不会影响另一方,但是如果存在对嵌套对象改变,则双方会互相影响。
三种方式
- 切片操作:l2 = l1[:]
- 工厂函数:l2 = list(l1)
- copy函数:l2 = copy.copy(l1)
>>> l1 = [1, 2, 3, ['a', 'b']] >>> l2 = copy.copy(l1) #l2 = [1, 2, 3, ['a', 'b']] >>> id(l1) 4330497160 >>> id(l2) 4330557320 # l1、l2指向的是不同的地址 >>> [id(x) for x in l1] #l2内容是原对象l1的引用(引用的元素地址相同) [4297554880, 4297554912, 4297554944, 4330499912] >>> [id(x) for x in l2] [4297554880, 4297554912, 4297554944, 4330499912] # 一般情况下,l1和l2的改变互不影响 # 但是如果其中一个对里面的嵌套list进行修改,另一个会发生变化 >>> l2.append('new_l2') #l2 = [1, 2, 3, ['a', 'b'], 'new_l2'] >>> l1 #l1不发生改变 [1, 2, 3, ['a', 'b']] >>> l1.append('new_l1') #l1发生改变,l2不变 >>> l1 #[1, 2, 3, ['a', 'b'], 'new_l1'] >>> l2 #[1, 2, 3, ['a', 'b'], 'new_l2'] >>> l1[3].append('c') #l1更改里面的嵌套list,l2改变 >>> l1 #[1, 2, 3, ['a', 'b', 'c'], 'new_l1'] >>> l2 #[1, 2, 3, ['a', 'b', 'c'], 'new_l2']
3. 深拷贝
方式:copy.deepcopy()
和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因而,它的时间和空间开销要高。
两个对象完全独立,互不影响。
另外注意:
1、对于非容器类型,如数字,字符,以及其它“原子”类型,没有拷贝一说。产生的都是原对象的引用。
2、如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。
**3. Python的特点——美团
1. 动态语言。
- 动态类型语言:也就是可以不用事先声明变量,就可以对它赋值。在编译的时候,Python不会进行类型检查,而是直至运行时,自动判断变量类型并对变量赋值,所以可能抛出类型错误。
- 静态类型语言:变量在赋值前必须声明数据类型。这样的好处是在编译器就可以发现类型错误。例如:C++/C、Java
2. 解释型语言
- 编译型语言,例如C++,源文件需要通过编译器使用一系列标志和选项转换成计算机可以识别的二进制格式;在运行程序时,链接器/载入器软件将程序从硬盘复制到内存,然后开始运行。
- 对于解释型语言,例如Python,在计算机内部,Python解释器把源代码转换成称为字节码的中间形式,然后再把它翻译成计算机使用的机器语言并运行。
3. 支持面向对象编程和面向过程的编程。
- 面向对象:围绕着数据和功能
- 面向过程:程序围绕着过程或者函数(可重复使用的程序片段)构建
Python具有非常强大但是过于简洁的执行面向对象编程的方式,特别是相对于C++或者Java这种大型语言来说
4. 高层语言。
- Python在编写程序的时候,无需考虑如何管理你的程序使用内存一类的底层细节。
- C++语言:需要手动分配、销毁对象的内存。
5. 速度快
Python 的底层是用 C 语言写的,很多标准库和第三方库也都是用 C 写的,运行速度非常快
6. 使用强制缩进来规范代码,使得程序有极佳的可读性。
7. 可移植性。基于其开源代码的特性,Python已被移植到很多平台。
8.可扩展性。
如果需要一段运行很快的代码,或者是不愿开放的代码,可以使用C/C++编写,再再Python中调用
9. 扩展库强大。特别是在数据处理方面
4. Python如何进行内存管理的?
从三个方面来说:1. 引用计数;2. 垃圾回收; 3.内存池机制;
1. 引用计数
Python是动态语言,即在使用前不需要声明,对内存地址的分配是在程序运行时自动判断变量类型并赋值。
对每一个对象,都维护着一个指向改对象的引用的计数,obj_refcnt
当变量引用一个对象,则obj_refcnt+1,如果引用它的变量被删除,则obj_refcnt-1,系统会自动维护这个计数器,当其变成0时,被回收。
2. 内存池机制
Python内存机制分为6层,内存池用于管理对小块内存的申请和释放。
- -1,-2层:主要由操作系统进行操作
- 0层:是C中的malloc,free等内存分配和释放函数进行操作
- 1,2层:内存池,由Python的接口函数PyMem_Malloc函数实现,当对象<256字节的时候,由该层直接分配内存。虽然,对应的也会使用malloc直接分配256字节的内存快——解决C中malloc分配的内存碎片问题。
- 3层:最上层,我们对Python对象的操作
内存池与C不同的是,:
- 如果请求分配的内存在1~256字节之间就使用自己的内存管理系统,否则直接使用 malloc。
- 这里还是会调用 malloc 分配内存,但每次会分配一块大小为256k的大块内存。
- 经由内存池登记的内存,最后被回收到内存池,而不会调用C的free释放掉,以便下次使用。
- 对于Python对象,如数值、字符串、元组,都有其独立的私有内存池,对象间不共享他们的内存池。例如:如果分配又释放了一个大的整数,那么这块内存不能再被分配给浮点数。
3. Python的垃圾回收机制。
PythonGC主要是通过引用计数来跟踪和回收垃圾,并使用“标记-清理”技术来解决容器对象可能产生的循环引用问题,使用“分代回收”提高回收的效率。
1. 引用计数:在Python中存储每个对象的引用计数obj_refcnt。当这个对象有新的引用,则计数+1,当引用它的对象被删除,则计数-1,当技术为0时,则删除该对象。
- 优点:简单、实时性
- 缺点:维护引用奇数消耗资源、无法解决循环引用的问题。
2. 标记-清理:基本思路是按需分配。等到没有空闲时,从寄存器和程序栈中的引用出发,遍历以对象为节点,引用为边所构成的图,把所有可以访问到对象都打上标记,然后清扫一遍内存空间,把没有标记的对象释放。
3. 分代收集:
总体的思想:Python将系统中所有的内存块根据对象存活时间划分为不同的代(默认是三代),垃圾收集频率随着“代”的存活时间的增大而减小。
举例:
我们分为新生代、老年代、永久代。
在对对象进行分配时,首先分配到新生代。大部分的GC也是在新生代发生。
如果新生代的某内存M进过了N次GC以后还存在,则进入老年代,老年代的GC频率远低于新生代。
对于永久代,GC频率特别低。
5. 单下划线、双下划线
- __name__:一种约定,Python内部的名字,用来与用户自定义的名字区分开,防止冲突
- _name:一种约定,用来指定变量私有
- 在import *情况下,解释器会对单下划线开头的名称做处理
- 在from module/package import *,任何单下划线开头的名称不会被导入,除非模块/包的
__all__
列表明确包含了这些名称
- __name:
- 解释器用_classname__name来代替这个名字用以区别和其他类相同的命名
- 现在创建一个
A
的子类B
,B就不会轻易的覆盖掉A
中的__method_name
了,相当于:Java中的final
**6. 类变量和实例变量
类变量就是供类使用的变量,实例变量就是供实例使用的。
作为函数的参数/返回值——赋值引用——1. 对于不可更改对象,互不影响;2. 可更改对象:会互相影响。
class Person: name="aaa" #类变量 p1=Person() p2=Person() p1.name="bbb" #实例p1调用类变量name='aaa',在实例作用域里将类变量的引用改变了,变成了实例变量。self.name指向实例变量。 print p1.name # bbb print p2.name # aaa print Person.name # aaa
如果将类变量name =[]换成数组,则是不可变——相当于在内存中更改了对象的值
class Person: name=[] p1=Person() p2=Person() p1.name.append(1) #类变量 p1.name # [1] p2.name # [1] Person.name # [1]
**7. @staticmethod和@classmethod
他们都是装饰器。装饰器是一种特殊的函数,要么接受函数作为参数,并返回一个函数;要么接受一个类作为参数,返回一个类。他们的作用是对运行中的程序动态的加入特定的功能而不改变函数的本身。@标记是语法糖,可以让你简单易读的方式获得方法的装饰目标对象。
实例/成员方法 | 类方法 | 静态方法 | |
标记 | 无 例如:def __init__(self, name) |
@classmethod | @staticmethod |
参数 | 实例self | 类实例cls | 无,不需要对类或者实例进行绑定 |
调用 | a.foo(x)只可用实例调用, 不可以用类调用 |
A.class_foo(x)、a.class_foo(x) 类、实例可调用类方法 |
A.static_foo(x)、a.static_foo(x) 类、实例可调用类方法 |
访问类变量 |
可以访问。若在成员方法内对: 1. 不可更改的类变量做成更改, 2. 对可更改的类变量做成更改,实例中和类对象中的类变量都改变。 |
可以访问,直接影响 (无论是可更改还是不可更改) |
可以访问。 |
访问实例变量 | 能访问 每一个实例的实例变量互不影响 (无论是不可更改对象还是可更改对象) |
不可访问 | 不可访问 |
访问静态变量 | 不可以 | 不可访问 | 可以 |
一、实例方法/成员函数。
定义:实例所使用的方法。
1. 类的成员函数能访问类变量吗?——滴滴
#coding=utf-8 class foo(object): class_var = 0 # 类变量,所有实例对象所共有 class_var_dict = [] def __init__(self): self.instance_var = 1 #实例/成员变量:针对每个实例对象自身持有 self.instance_var_dict = [] # 在成员方法中。对可变类变量class_var_dict进行修改——都发生变化 self.class_var_dict.append('a') # 在成员方法中,对不可变类变量class_var进行修改 #不影响foo.class_var = 0 # 两个实例变量发生变化:foo1.class_var = foo2.class_var = 1 self.class_var += 1 # 创建两个实例 foo1 = foo() foo2 = foo()
在成员方法中,
- 若对不可更改的类变量(数字、字符串、tuple)做成更改,则实例中的类变量发生变化,但是对于类对象中的类变量没有改变。
- 对可更改的类变量(list、set、dict)做成更改,则实例中和类对象中的类变量都改变。
# 查看类对象和两个实例的变量:自己的命名空间__dict__(各不相同) print('foo1的命名空间:', id(foo1.__dict__), (foo1.__dict__)) print('foo2的命名空间:',id(foo2.__dict__), (foo2.__dict__)) # foo1的命名空间: 4300908680 {'instance_var': 1, 'class_var': 1} # foo2的命名空间: 4300909064 {'instance_var': 1, 'class_var': 1} # foo类对象的命名空间: 4328743560 {'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'foo' objects>, 'class_var_dict': ['a', 'a'], '__init__': <function foo.__init__ at 0x102074950>, 'class_var': 0, '__dict__': <attribute '__dict__' of 'foo' objects>} # 在实例中,更改不可更改的类变量——互不影响 foo1.class_var = '2' print('foo2.class_var:', foo2.class_var) #foo2.class_var: 1 print('foo类对象的类变量class_var:', foo.class_var) #foo.class_var: 0 # 在实例中,更改可更改的类变量——都会更改 foo1.class_var_dict.append('new_for_foo1') print('foo2.class_var_dict:',foo2.class_var_dict) #foo2.class_var_dict: ['a', 'a', 'new_for_foo1'] print('foo类对象的类变量class_var_dict:', foo.class_var_dict) # foo.class_var_dict: ['a', 'a', 'new_for_foo1']
2. 类的成员函数能够访问实例变量吗?。
每个实例的实例变量都完全独立,互不影响。
在成员函数中调用实例变量:self.instance_var
在实例中调用实例变量:实例名.instance_var
# 更改foo1中实例变量——即使是可更改对象dict,都互不影响 print('foo1中实例变量的ID:', id(foo1.instance_var_dict))#4321671176 print('foo2中实例变量的ID:', id(foo2.instance_var_dict))#4321669192 foo1.instance_var_dict.append('new_for_foo1') print('foo1.instance_var_dict:', foo1.instance_var_dict) #foo1.instance_var_dict: ['new_for_foo1'] print('foo2.instance_var_dict:', foo2.instance_var_dict)#foo2.instance_var_dict: []
二、静态方法
静态方法是一种普通函数,就位于类定义的命名空间中,它不会对任何实例类型进行操作。使用装饰器@staticmethod定义静态方法。类对象和实例都可以调用静态方法:
1. 静态方法能访问成员变量吗?
不能够访问成员变量,能访问并更改类变量。
@staticmethod def static_foo(): # 更改类属性: foo.class_var = 10 foo.class_var_dict.append('new_static_methoda') print('foo.class_var', foo.class_var) print('foo.class_var_dict', foo.class_var_dict)
2. 静态方法能访问类变量吗?
可以。
三、类方法
类方法是将类本身作为对象进行操作的方法。类方法使用@classmethod装饰器定义,其第一个参数是类,约定写为cls。类对象和实例都可以调用类方法
1. 类方法能访问类成员变量吗?
能够。并且直接影响,无论是可更改还是不可更改属性。
#coding=utf-8 class foo(object): class_var = 0 # 类变量,所有实例对象所共有 class_var_dict = [] def __init__(self): self.instance_var = 1 self.instance_var_dict = [] self.class_var_dict.append('a') self.class_var += 1 @classmethod def class_foo(cls): # 类方法中调用实例变量——报错!'foo' has no attribute 'instance_var' print('cls.isinstance:', cls.instance_var) print('cls.isinstance_dict:', cls.instance_var_dict) # 在类方法中调用类变量——直接影响 cls.class_var_dict.append('new_class_foo') print('cls.class_var_dict:', cls.class_var_dict) print('foo.class_var_dict:', foo.class_var_dict) cls.class_var = 2 print('cls.class_var:', cls.class_var) print('foo.class_var:', foo.class_var) foo1 = foo() foo2 = foo()
最后结果:
cls.class_var_dict: ['a', 'a', 'new_class_foo'] foo.class_var_dict: ['a', 'a', 'new_class_foo'] cls.class_var: 2 foo.class_var: 2
2. 类方法能访问实例变量吗?
不能。
报错:AttributeError: type object 'foo' has no attribute 'instance_var'
四、super方法
super()是调用父类的方法,在子类中,并不会主动调用父类的__init__方法。
例如:
class Foo(object): def __init__(self): self.val = 1 class Foo2(Foo): def __init__(self): print(self.val) if __name__ == '__main__': foo2 = Foo2()
运行时,报错
有两种方法:
一、调用实例方法
class Foo(object): def __init__(self): self.val = 1 class Foo2(Foo): def __init__(self): Foo.__init__(self) #类调用实例方法时,需要传入self指代的实例 print(self.val) if __name__ == '__main__': foo2 = Foo2()
二、使用super()
class Foo(object): def __init__(self): self.val = 1 class Foo2(Foo): def __init__(self): super(Foo2, self).__init__() print(self.val) if __name__ == '__main__': foo2 = Foo2()
**8. 多线程
1. 滴滴问题:你有用过多线程吗?多线程是用来干什么的?
2. 多线程作用:可以实现代码的并发性,优化处理能力。同时更小的功能划分可以使代码的重用性更好。
3. 实现:GIL(全局解释器锁),用thread、threading和Queue模块可以用来实现多线程编程
4. 解决方法:多进程和协程。协程是进程和线程的升级版。进程和线程都面临着内核态和用户态的切换问题。
协程可以自己控制切换的时机,不再需要陷入系统的内核态。
yield:协程的思想
1)GIL锁原理
Python代码的执行是由Python解释器来控制,在同一时刻只能由一个线程在解释器中运行。那么对Python解释器的访问就通过GIL来实现。
GIL能够保证在同一时刻只有一个线程运行。
在多线程的环境中,Python解释器按以下方式执行:
- 设置GIL
- 切换到一个线程去运行
- 当运行了指定数量的字节码指令,或线程主动让出控制(可以调用time.sleep(0)),则把该线程设置为睡眠状态,解锁GIL;
- 当一个线程结束计算,它就退出了。
- 可以调用thread.exit()之类的退出函数(但是不建议使用thread,因为主线程退出后,所有的子线程强制退出,且不会清理资源)
- 也可以使用Python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。
- 不过,不可以直接“杀掉”("kill")一个线程。
- 当调用外部代码(如C/C++扩展函数)时,GIL会被锁定,直到这个函数执行完成。(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
2)比较三个模块:thread、threading、queue
首先,thread和threading都允许程序员创建和管理线程。
- thread:提供了基本的线程和锁的支持。当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清理工作。
- threading:能确保重要的子线程退出后进程才退出。
- Queue模块:允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
对于threading模块:
import threading from time import sleep, ctime nsecs = [2, 4] def loop(nloop, nsec): print("Start", nloop, "at", ctime()) sleep(nsec) print("End", nloop, "at", ctime()) def main(): print("All start at", ctime()) nloops = range(len(nsecs)) nthreads = [] #线程组 # 创建线程 for i in nloops: t = threading.Thread(target=loop, args=(i, nsecs[i])) nthreads.append(t) #添加到线程组 # 执行线程 for i in nloops: nthreads[i].start() # 等待所有线程执行完成 for i in nloops: nthreads[i].join() print("All done at ", ctime()) if __name__ == "__main__": main()
对于thread模块:
import _thread from time import sleep, ctime loops = [4, 2] def loop(nloop, nsec, lock): print("Start", nloop, "at", ctime()) sleep(nsec) print("End", nloop, "at", ctime()) lock.release() def main(): print("All start at ", ctime()) nlocks = [] nloops = range(len(loops)) nsecs = loops for i in nloops: # 分配锁 lock = _thread.allocate_lock() # 锁定锁 lock.acquire() # 追加到locks[]数组中 nlocks.append(lock) # 对每一个线程调用loop函数 for i in nloops: _thread.start_new_thread(loop, (nloops[i], nsecs[i], nlocks[i])) # 判断所有的nloop线程都执行完以后结束 for i in nloops: while nlocks[i].locked: pass print("All end at ", ctime()) if __name__ == "__main__": main()
9. Python自省
自省就是面向对象的语言所写的程序在运行时,能知道对象的类型。
type()、dir():列出制定对象或类的属性、getattr()、hasattr()、isinstance()
10. @property装饰器
作用:把一个方法变成属性调用。在一个类中:
- 把getter方法变成属性,只需要加上:@property
- 把setter方法变成属性:需要创建另一个装饰器@属性名.setter
- 如果只有getter方法,没有setter方法,则表明只读,不可修改
class Student(object): @property #将score方法当作属性调用(相当于score 的getter方法) def score(self): return self._score @score.setter #score的setter方法,其中包括了验证score 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 @property def name(self): return self._name @name.setter def name(self, str): if not isinstance(str, str): raise ValueError('Name must be a string') self._name = str s = Student() s.score = -11 s.score
运行结果:
Traceback (most recent call last): File "/Users/lesley/PycharmProjects/sort/Student.py", line 26, in <module> s.score = -11 File "/Users/lesley/PycharmProjects/sort/Student.py", line 12, in score raise ValueError('Score must between 0~100') ValueError: Score must between 0~100
11. Python中的pass语句作用是什么?
pass语句不会执行任何操作,一般作为占位符或者创建占位程序。
12. Python如何进行类型转换?
Python提供了将变量或值从一个类型转换成另一个类型的内置方法。
1. 数值类:int(x [,base]), long(x [,base]), float(x), complex(real [, image])
2. 字符串:str(x)、chr(x)
3. 其他:list(s), tuple(s)
13. 字符串格式化:% 和 .format
.format()就是用{}:来代替%
1). %无法同时传递一个变量和元组
>>>name='sbw' >>>"hi, there is %" %name and #输出正确 >>>name=(1, 2, 3) #元组 >>>"hi there is %" %name #出错 # 必须更改为: >>>"hi, there is %" %(name, ) #提供一个单元素的元组 # 使用.format() >>>"hi, there is {}".format(name) #'hi,there is (1, 2, 3)'
2). 可重用参数——通过位置映射
>>> '{name},{age}'.format(age=19,name='sbw') 'sbw,19'
3). 格式限定符——通过{}:
它有着丰富的的“格式限定符”(语法是{}中带:号),比如:
- 填充与对齐
填充常跟对齐一起使用^、<、>分别是居中、左对齐、右对齐,后面带宽、填充的字符,只能是一个字符,不指定的话默认是用空格填充
# 右对齐,填充字符为8个 >>> '{:>8}'.format('189') ' 189' # 用0填充8个字符位、右对齐 >>>'{:0>8}'.format('189') '00000189' # 用字符a填充8个字符位、右对齐 >>>'{:a>8}'.format('189') 'aaaaa189'
- 精度与类型f
精度常跟类型f一起使用
>>>'{:.2f}'.format(321.33345) '321.33' # .2表示:长度为2的精度;f表示:float
- 其他类型
主要就是进制了,b、d、o、x分别是二进制、十进制、八进制、十六进制。
# 二进制:'{:b} >>>'{:b}'.format(17) '10001' # 十进制 {:d} >>>'{:d}'.format(17) '17' # 八进制 {:o} >>>'{:o}'.format(17) '21' #十六进制 {:x} >>>'{:x}'.format(17) '11'
用,号还能用来做金额的千位分隔符。
# {:,}:金额的分隔符 >>> '{:,}'.format(1234567890) '1,234,567,890'
14. 迭代器(iteration)和生成器(generator)
14.1 迭代——可以通过for...in来遍历——创建[for...in]
只要作用于一个可迭代对象,for都可以正常运行,不关心对象类型。
1. 可迭代对象——Iterable
可以直接作用于for
循环的对象,称为可迭代对象,有以下几种:
- 一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等; - 一类是
generator
,包括生成器和带yield
的generator function。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
#判断一个对象是否可以迭代——collection.Iterable >>> from collections import Iterable >>> isinstance('abd', Iterable) #str可迭代 True >>> isinstance([1, 2, 3], Iterable) #数组可迭代 True >>> isinstance(123, Iterable)#数字不可迭代 False >>>
2. 迭代器——可以被next()
函数调用并不断返回下一个值的对象(Iterator)——计算是惰性的
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance('abc', Iterator) False
生成器都是可迭代对象,但是str、dict、list虽然Iterable,但是不是Iterator。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
- 对dict迭代
# 1. 对dict迭代 >>>dict = {'a':1, 'b':2, 'c':3} #对key(默认;因为存储不按照list,所以迭代结果顺序可能不同) >>> for key in dict: ... print(key) ... c b a # 对value进行迭代 >>> for value in dict.values(): ... print(value) ... 3 2 1 # 同时对values和key迭代: >>> for key,item in dict.items(): ... print((key, item)) ... ('c', 3) ('b', 2) ('a', 1)
- 对字符串
>>> ch = 'ABC' >>> for i in ch: ... print(i) ... A B C >>>
- Python内置的
enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
>>> for i in enumerate(['A', 'B', 'C']): ... print(i, value) ... (0, 'A') 3 (1, 'B') 3 (2, 'C') 3
14.2 列表生成器/字典生成器
1. 列表生成器
>>> L1 = ['Hello', 'World', 18, 'Apple', None] >>> L2=[s.lower() for s in L1 if isinstance(s,str)==True] >>> L2 ['hello', 'world', 'apple']
例如:列出当前目录下的所有文件和目录名
>>> import os >>> [dir for dir in os.listdir('.')] ['.bash_history', '.bash_profile', ...]
2. 字典生成器
>>>dict = {'a':1, 'b':2, 'c':3} >>> dict2 = {key:value for key,value in dict.items()} >>> dict2 {'c': 3, 'b': 2, 'a': 1}
6.3 迭代器和生成器
引入生成器:列表容量大时,会占用很大的存储空间——生成器可以一边循环一边计算
生成器 ——1. 创建:(x*x for x in range(3));2.边循环边计算(只迭代一次)
是迭代器的一种,但是你只能迭代它们一次。因为它们不是全部存在内存里,它们只在要调用的时候在内存里生成:
# 创建方法:() >>> generator= (x*x for x in range(3)) >>> generator <generator object <genexpr> at 0x1022ba990> # 不能在生成器中用for i in mygenerator第二次调用生成器: # 首先计算0,然后会在内存里丢掉0去计算1,直到计算完4. # 只能调用一次 >>> for I in generator: ... print(i) ... 0 1 4 >>> for i in generator: ... print(i) ...
14.4 yeild关键字
yeild 可以理解为返回一个生成器。对于一个生成器的函数,
- 当for语句第一次调用函数里返回的生成器对象,代码才运行,遇到yield语句就返回本次循环中的第一个返回值。
- 下一次调用时,从上次返回的
yield
语句处继续执行。 - 一旦函数运行并没有碰到
yeild
语句就认为生成器已经为空了
例如:狄波拉数列
>>> def fib(max): ... n, a, b = 0, 0, 1 ... while n < max: ... yield b ... a, b = b, a + b ... n = n + 1 ... return 'done' ... >>> fib(10) <generator object fib at 0x1022bae60> # 输出 >>> f =fib(10) >>> for i in f: ... print(i) ... 1 1 2 3 5 8 13 21 34 55 # 可以用next()来获取生成器的值(只能一次)>>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
例如:杨辉三角
>>> def triangles(): ... L = [1] ... while True: ... yield L ... L.append(0) ... L = [L[i - 1] + L[i] for i in range(len(L))] ... >>> for t in triangles(): ... print(t) ... n = n + 1 ... if n == 10: ... break ... [1] [1, 1] [1, 2, 1] [1, 3, 3, 1] [1, 4, 6, 4, 1] [1, 5, 10, 10, 5, 1] [1, 6, 15, 20, 15, 6, 1] [1, 7, 21, 35, 35, 21, 7, 1] [1, 8, 28, 56, 70, 56, 28, 8, 1] [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
15. *args 和**kwargs
用*args
和**kwargs
只是为了方便并没有强制使用它们。
- *args:可变的位置参数:不确定函数里传递参数的个数(可传递任意数量)
>>> def print_everything(*args): ... for count, thing in enumerate(args): ... print('{0}, {1}'.format(count, thing)) ... >>> print_everything(['a', 'b', 'c']) 0, ['a', 'b', 'c'] >>> print_everything('a', 'b', 'c') 0, a 1, b 2, c
- **kwargs:可变的关键字参数:允许你使用没有事先定义的参数名
>>> 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
可以同时在函数的定义中,但是*args
必须在**kwargs
前面。
另外,调用函数时,也可以使用*、**
>>> 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
16. 面向切面编程AOP和装饰器——考的也非常多!!
1)AOP定义:在程序运行时,动态的将代码切入到类的指定方法、指定位置上的编程思想。
——通过装饰器实现
- 切入到指定类/方法的代码:切面
- 切入到哪些类,哪些方法:切入点
2)用处:较为经典的有插入日志、性能测试、事务处理等,通过声明的方式配置好。
——为什么不用公共类?如果我们只是提取了公共方法到一个独立的类和独立的方法中,然后再在其他类中调用,那么会在类与上面提到的独立的类之间产生耦合,一旦这个独立的类产生改变,那么会影响到调用他的类。
**17. 装饰器
装饰器是高阶函数,要么接受函数作为参数,并返回一个函数;要么接受一个类作为参数,返回一个类。他们的作用是对运行中的程序动态的加入特定的功能而不改变函数的本身。@标记是语法糖,可以让你简单易读的方式获得方法的装饰目标对象。
1. 基本的Decorator函数:
本质上来说:decorator是一个接受函数作为参数,并且返回函数的高阶函数。
举例:在login()函数中输出调试的信息
def printDebug(func): def wrapper(*args, **kwargs): print('Start %s()' % func.__name__) func() print('End %s()' % func.__name__) return wrapper @printDebug #语法糖 def login(): print('Login in') login() # 执行顺行:printDebug(login)()
输出结果为:
Start login() Login in End login()
注意:
>>> login.__name__ 'wrapper'
我们发现在decorator装饰过后的now()函数的名字__name__变成了:wrapper。为了防止有些依赖函数签名的代码出错,所以需要把原始函数的__name__等属性复制到wrapper函数中:通过Python内置的functools.wraps实现:
import functools def printDebug(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('Start %s()' % func.__name__) func() print('End %s()' % func.__name__) return wrapper @printDebug def login(): print('Login in')
2. login带参数:login(user)
import functools def printDebug(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('Start %s()' % func.__name__) func(*args, **kwargs) print('End %s()' % func.__name__) return wrapper @printDebug def login(user): # 函数带有参数 print('Login in '+ user) login('sbw')
执行的顺序:[decprated]login('sbw')——>printDebug(login)('sbw')——>wrapper('sbw')——>[real]login('sbw')
3. 装饰器本身有参数——三层
我们在定义渣装饰器的时候,也可以加入参数,比如我们传入一个参数来指定Bug的级别
import functools # 装饰器带有参数:Bug级别:level def print_debug_level(level): def print_debug(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('Start login with level:' + str(level)) func(*args, **kwargs) print('End %s()' % func.__name__) return wrapper return print_debug @print_debug_level(level=5) def login(user): # 函数带有参数 print('Login in '+ user) login('sbw')
输出结果:
Start login with level:5 Login in sbw End login()
执行过程:[decorated]login('sbw')——>print_debug_level(5)——>print_debug[with closure value 5](login)('sbw')——>wrapper('sbw')[use value 5] ——>[real]login('sbw')
4. 装饰器带有返回值的函数
例如:login返回值message用来判断login是否成功
import functools # 装饰器带有参数:Bug级别:level def print_debug_level(level): def print_debug(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('Start login with level:' + str(level)) result = func(*args, **kwargs) print('The Login result is:',result) return result return wrapper return print_debug @print_debug_level(level=5) def login(user): # 函数带有参数 print('Login in '+ user) msg='success' if user == 'sbw' else 'fail' return msg login('sbw')
5. 改进
在上面这个例子中,因为装饰器只能用于函数的整体,不能用于一部分,所以我们可以将验证validation的代码提取出来。同时因为validation()过程耗时,所以我们可以添加缓存cache。
#coding=utf-8 import time import functools dict_cache={} # 存放所有验证过的用户和验证的结果 def cache(func): @functools.wraps(func) def wrapper(user): now = time.time() if user in dict_cache: # 判断这个用户是否曾经验证过 print('曾经验证过') user, cache_time = dict_cache[user] if now - cache_time > 30: # 如果上一次验证的时间在30s之前,则重新进行验证 print(u'超过了缓存时间') result = func(user) dict_cache[user] = (result, now) else: # 还在缓存时间内 print('还在缓存时间内') else: print('第一次验证') result = func(user) dict_cache[user] = (result, now) return result return wrapper def login(user): print('in login:'+user) msg = validation(user) print(msg) @cache def validation(user): time.sleep(1) msg = 'success' if user == 'sbw' else 'fail' return msg login('sbw') login('lf') login('lf') login('sbw') login('sbw')
6. 应用多个装饰器
def print_debug(func): def wrapper(): print('enter the login') func() print('exit the login') return wrapper def others(func): # define a other decorator def wrapper(): print('***other decorator***') func() return wrapper @others # apply two of decorator @print_debug def login(): print('in login:') @print_debug # switch decorator order @others def logout(): print('in logout:') login() print('---------------------------') logout()
运行结果:
***other decorator*** enter the login in login: exit the login --------------------------- enter the login ***other decorator*** in logout: exit the login
对于logout()函数应用于装饰器可以看成:
@print_debug #switch decorator order ( @others ( def logout(): print('in logout:') ) )
[print_debug decorated]logout() ——>print_debug.wrapper[call [others decorated]logout() ] ——>print_debug.wrapper.other.wrapper[call real logout]
18. 鸭子类型
简单来说,如果一个东西,走路像鸭子,叫起来像鸭子,游泳起来像鸭子,我们就认为她是一只鸭子。——只关心行为。
例如,有两个类,一个是人类,一个是鸭子类,他们都会吃饭和走路,我们不管他们这两个方法中返回的值是否相同,只要她们能够吃饭和走路,我们统统当作鸭子,传递给一个函数当参数。
例如:list.extend(obj)方法中,只要obj可以迭代,那么我们不管他是list/dict/tuple/strs我们都可以传递进去当参数。
19. Python不支持函数重载
1)什么是函数重载?
函数重载,主要是为了解决两个问题:
- 可变参数类型
- 可变参数个数
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
2)Python调用函数机制:使用*agrs:可传入任意数量的参数;**wkagrs:可传入任意类型的阐述(事先未声明的)
20. 新式类和旧式类
MBR问题:(新式类是广度优先,旧式类是深度优先)
http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
21. __new__ 和 __init__
__new__
是一个静态方法,而__init__
是一个实例方法.__new__
方法会返回一个创建的实例,而__init__
什么都不返回.- 只有在
__new__
返回一个cls的实例时后面的__init__
才能被调用. - 当创建一个新实例时调用
__new__
,初始化一个实例时用__init__
.
假如cls是一个类对象,当你调用C(*args, **kwargs)来创建一个类C的实例,python的内部机制:
- 通过:C.__new__(C, *args, **wkargs)创建这个类实例,并返回这个实例c
- 确认c是C的实例
- 调用C.__init(c, *args, **kwargs)初始化实例c
c = C.__new__(C, *arg, **kwargs) if(is instance(c, C)): c.__init__(c, 23) #__init__第一个参数要为实例对象
22. Python命名空间
1)定义
在Python中,使用命名空间来记录变量的轨迹,本质是一个字典,key是变量名,value是那些变量的值。命名空间分为:
- Local(局部命名空间):函数的命名空间。记录了:函数的参数、局部变量。可用locals()查看
- Enclosing(内嵌函数命名空间):def、lambda
- Global(模块的命名空间)。记录了:函数、类、其他导入模块、模块级的变量和常量。可用globals()查看
- Built-in(内置命名空间):(Python标准库中的函数)模块,任何模块均可访问它,它存放着内置的函数和异常。
2)命名空间查找顺序——Python函数作用域的LEGB顺序
L:local 局部命名空间(当前函数)
E:enclosing 内嵌函数(def;lambda;父函数)
G:global 模块命名空间
B:build-in 内置命名空间
如果找不到:NameError: name 'aa' is not defined。
3)命名空间的生命周期
- 内置命名空间在python解释器启动时创建,一直保留、不删除
- 模块的全局命名空间在模块定义时被读入创建,一直保留到解释器退出
- 当函数被调用时,创建一个局部命名空间。当函数返回结果或抛出异常时,被删除。
4)locals() & globals()
locals():返回的是局部命名空间的拷贝。——只读
globals():返回的是全局命名空间。——可改动
5)from module import & import module
- 使用 import module,模块自身被导入,但是它保持着自已的名字空间——>需要使用模块名来访问它的函数或属性(module.function)
- 使用 from module import,实际上是从另一个模块中将指定的函数和属性导入到你自己的名字空间,——>可以直接访问它们却不需要引用它们所来源的模块的原因。
23. 闭包(closure)
闭包(closure)是函数式编程的重要的语法结构,也是一种组织代码的结构。它提高了代码的可重用行。
创建一个闭包的三个条件:
- 必须有内嵌函数
- 内嵌函数必须要引用外部函数中的变量
- 外部函数返回值必须是内嵌函数
重点是函数运行后并不会被撤销,像单例模型中instance字典一样,当函数运行完后,instance并不被销毁,而是继续留在内存空间里。这个功能类似类里的类变量,只不过迁移到了函数上。
闭包就像个空心球一样,你知道外面和里面,但你不知道中间是什么样。
24. 面向函数式编程
什么是面向函数式编程?——关注我们要干什么?而不是怎么干
1. lambda匿名函数
>>> sum = lambda x,y:x*y >>> sum(1,2) 2
匿名函数的命名规则,用lamdba 关键字标识,冒号(:)左侧表示函数接收的参数(a,b) ,冒号(:)右侧表示函数的返回值(a+b)。
2. map()
有两个参数:函数名,作用对象(eg:list)。
r = map(func, seq)
- 函数func作用于seq中每个元素
- 输出必须转为list()
>>> m = map(lambda x:x*x, range(3)) >>> list(m) # 输出必须转化成list() [0, 1, 4] >>> m <map object at 0x1022be828>
3. reduce()
- 需要引入reduce()函数:from fuctools import reduce
- 类似于map,但是作用于seq中每两个元素
- 直接输出
例如:阶乘的实现:
>>> from functools import reduce >>> result = reduce(lambda x,y:x*y, range(1, 6)) >>> result 120
4. filter()
# 定义:通过函数func过滤掉seq中不满足条件的元素 # func:返回值必须为boolean filter(func, seq)
需要注意:
- 必须要用list()才能返回结果
>>> numbers = [1, 2, -3, 8, -2, 4, 7] >>> even_number = list(filter(lambda x:x>0 and x%2==0, numbers)) >>> even_number [2, 8, 4]
25、Python中的设计模式
2.1 单例模式(Singleton)——必考!!绝对要记住1-2个方法
1. 定义
- 单例模式:保证系统中一个类只有一个实例。
- 目的:通过单例模式,可以保证系统中一个类只有一个实例,而且该实例易于外界访问。
- 例如:一台计算机中可以有若干个打印机,但是只能有一个printer spooler(打印机后台处理服务),避免两个打印作业同时输出到打印机中。
- 实现的要点:
- 只提供私有的构造函数
- 只含有一个该类的静态私有对象
- 提供了一个静态的共用函数用于创建和获取他本身的静态私有对象
2. 四种实现方法:
1)使用__new__方法——将类变量绑定到_isinstance
# 方法一:用__new__实现,将类实例绑定到类变量_instance上面 # 如果cls._instance为None说明该类还没有实例化过,实例化该类,并返回 # 如果cls._instance不为None,直接返回cls._instance class Singleton(object): def __new__(cls, *args, **kwargs): if not (hasattr(cls, '_instance')): orig = super(Singleton, cls) cls._instance = orig.__new__(cls, *args, **kwargs) return cls._instance
实例:
class MyClass(Singleton): a = 1 one = MyClass() two = MyClass() two.a = 3 one.a #3 #one和two完全相同,可以用id(), ==, is检测
id(one) #29097904 id(two) #29097904 one == two #True one is two #True
2)Borg——指向同一个__dict__
__dict__是一个字典,键是属性名,值为属性值
思想:共享属性。所谓单例模式就是所有引用(实例、对象)拥有相同的属性和方法;因为方法天然相同,所以主要保证属性一致。——>将实例的__dict__属性指向(引用)同一个字典
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 one = MyClass2() two = MyClass2() #one和two是两个不同的对象,id, ==, is对比结果可看出 two.a = 3 one.a #3 id(one) #28873680 id(two) #28873712 one == two #False one is two #False #但是one和two具有相同的(同一个__dict__属性),见: id(one.__dict__) #30104000 id(two.__dict__) #30104000
3) import——天然的的单例模式
因为在Python中,模块只会出初始化一次,所有变量都归属于某个模块,import机制是线程安全的
例如,我们创建一个函数,获取汇率。在一般情况下,我们只需要第一次获得这个汇率,以后对它进行操作就行了。
my_singleton.py:
class My_Singleton(object): def func(self): val = 1 my_singleton = My_Singleton()
to use
from my_singleton import my_singleton my_singleton.func()
4). 装饰器
def singleton(cls, *args, **kwargs): instances = {} def _singleton(): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return _singleton @singleton class MyClass4(object): a = 1 def __init__(self, x=0): self.x = x one = MyClass4() two = MyClass4() two.a = 3 one.a #3 id(one) #29660784 id(two) #29660784 one == two #True one is two #True one.x = 1 one.x #1 two.x #1
2.2 工厂模式
例如,一个婚介所,存放了男男女女的基本信息、要求。你想要找一个男朋友,只需要提出你的要求:高富帅,这个婚介所就只通过这些信息自动给你匹配。
class Shape: pass class Circle(Shape): pass class Square(Shape): pass for name in ['Circle', 'Sqaure']: cls = globals[name] # cls是一个类,它的类型是type obj = cls() # 可以通过type()动态的创建类
结果:
cls #<class '__main__.Square'> obj. # <__main__.Square object at 0x1012c3390> type(obj) #<class '__main__.Square'> obj是一个实例,它的类型就是 class Square type(cls). # <class 'type'> cls是一个类,它的类型就是type
**26. os库和sys库的区别——腾讯有考!
1. os模块:——提供了一系列对操作系统进行操作的接口
1 os.getcwd() #获取当前工作目录,即当前python脚本工作的目录路径 2 os.chdir("dirname") #改变当前脚本工作目录;相当于shell下cd 3 os.curdir #返回当前目录: ('.') 4 os.pardir #获取当前目录的父目录字符串名:('..') 5 os.makedirs('dirname1/dirname2') #可生成多层递归目录 6 os.removedirs('dirname1') #若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 7 os.mkdir('dirname') #生成单级目录;相当于shell中mkdir dirname 8 os.rmdir('dirname') #删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname 9 os.listdir('dirname') #列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 10 os.remove() #删除一个文件 11 os.rename("oldname","newname") #重命名文件/目录 12 os.stat('path/filename') #获取文件/目录信息 13 os.sep #输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/" 14 os.linesep #输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n" 15 os.pathsep #输出用于分割文件路径的字符串 16 os.name #输出字符串指示当前使用平台。win->'nt'; Linux->'posix' 17 os.system("bash command") #运行shell命令,直接显示 18 os.environ #获取系统环境变量 19 os.path.abspath(path) #返回path规范化的绝对路径 20 os.path.split(path) #将path分割成目录和文件名二元组返回 21 os.path.dirname(path) #返回path的目录。其实就是os.path.split(path)的第一个元素 22 os.path.basename(path) #返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素 23 os.path.exists(path) #如果path存在,返回True;如果path不存在,返回False 24 os.path.isabs(path) #如果path是绝对路径,返回True 25 os.path.isfile(path) #如果path是一个存在的文件,返回True。否则返回False 26 os.path.isdir(path) #如果path是一个存在的目录,则返回True。否则返回False 27 os.path.join(path1[, path2[, ...]]) #将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 28 os.path.getatime(path) #返回path所指向的文件或者目录的最后存取时间 29 os.path.getmtime(path) #返回path所指向的文件或者目录的最后修改时间
例如:如何判断一个文件是否存在,如果存在统计她的行数;不存在则建立这个文件并输入“hello”
>>> import os >>> if not(os.path.exists('test.txt')): ... with open('test.txt', 'wt') as file: ... file.write('hello\n') ... else: ... lines = readlines() ... print('the len of file is %d' %len(lines)) ...
在Linux下实现:
songbowendeMacBook-Pro:~ lesley$ ls *.txt|grep 'test'|wc
2 2 20
注意:
- read 读取整个文件
- readline 读取下一行,使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
2. sys:来处理Python运行时配置以及资源,从而可以与当前程序之外的系统环境交互
例如:与解释器交互
我们可以用dir(sys)来查看sys模块下面的built-in函数:
常见的命令有:
1 sys.argv #命令行参数List,第一个元素是程序本身路径 2 sys.exit(n) #退出程序,正常退出时exit(0) 3 sys.version #获取Python解释程序的版本信息 4 sys.maxint #最大的Int值 5 sys.path #返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 6 sys.platform #返回操作系统平台名称 7 sys.stdout.write('please:') 8 val = sys.stdin.readline()[:-1]
补充:
3. Shutil模块:高级的文件、文件夹、压缩包处理模块
1 shutil.copyfileobj(force, fdst[, lenght]). #将文件内容拷贝到另一个文件中,可以部分内容 2 shutil.copyfile(src, dst). # 拷贝文件 3 shutil.copymode(src, dst) # 仅拷贝权限。内容、组、用户都不变 4 shutil.copystat(src, dst) # 拷贝状态信息,包括:包括:mode bits, atime, mtime, flags 5 6 shutil.copy(src, dst) # 拷贝文件和权限 7 shutil.copy2(src, dst) # 拷贝文件和状态信息 8 9 shutil.copytree(src, dst, symlinks=False, ignore=None) #递归的拷贝文件 10 shutil.rmtree(path[, ignore_errors[, onerror]]) # 递归的删除文件 11 shutil.move(src, dst) # 递归的移动文件 12 13 shutil.make_archive(base_name, format,...) # 创建压缩包并且返回路径
shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:
import tarfile # 压缩 tar = tarfile.open('your.tar','w') tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip') tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip') tar.close() # 解压 tar = tarfile.open('your.tar','r') tar.extractall() # 可设置解压地址 tar.close() zipfile 压缩解压
import tarfile # 压缩 tar = tarfile.open('your.tar','w') tar.add('/Users/wupeiqi/PycharmProjects/bbs2.zip', arcname='bbs2.zip') tar.add('/Users/wupeiqi/PycharmProjects/cmdb.zip', arcname='cmdb.zip') tar.close() # 解压 tar = tarfile.open('your.tar','r') tar.extractall() # 可设置解压地址 tar.close() tarfile 压缩解压
27. read,readline和readlines
- read 读取整个文件
- readline 读取下一行,使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
https://zhuanlan.zhihu.com/p/23526961?refer=passer
https://github.com/taizilongxu/interview_python/blob/master/Readme.md#11-面向切面编程aop和装饰器