008 12--python提高-1
12.1. GIL¶
GIL(全局解释器锁)¶
GIL面试题如下¶
描述Python GIL的概念, 以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。
Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235
he language doesn't require the GIL -- it's only the CPython virtual machine that has historically been unable to shed it.
参考答案:¶
- Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。
- GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
- 线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
- Python使用多进程是可以利用多核的CPU资源的。
- 多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
12.2. 深拷贝、浅拷贝¶
深拷贝、浅拷贝¶
1. 浅拷贝¶
- 浅拷贝是对于一个对象的顶层拷贝
通俗的理解是:拷贝了引用,并没有拷贝内容
2. 深拷贝¶
- 深拷贝是对于一个对象所有层次的拷贝(递归)
进一步理解深拷贝¶
3. 拷贝的其他方式¶
- 分片表达式可以赋值一个序列
- 字典的copy方法可以拷贝一个字典
4. 注意点¶
浅拷贝对不可变类型和可变类型的copy不同¶
- copy.copy对于可变类型,会进行浅拷贝
- copy.copy对于不可变类型,不会拷贝,仅仅是指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | In [88]: a = [11,22,33] In [89]: b = copy.copy(a) In [90]: id(a) Out[90]: 59275144 In [91]: id(b) Out[91]: 59525600 In [92]: a.append(44) In [93]: a Out[93]: [11, 22, 33, 44] In [94]: b Out[94]: [11, 22, 33] In [95]: a = (11,22,33) In [96]: b = copy.copy(a) In [97]: id(a) Out[97]: 58890680 In [98]: id(b) Out[98]: 58890680 |
copy.copy和copy.deepcopy的区别¶
copy.copy
copy.deepcopy
私有化¶
- xx: 公有变量
- _x: 单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
- __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)(子类不能继承)
- xx:双前后下划线,用户名字空间的魔法对象或属性。例如:
__init__
, __ 不要自己发明这样的名字(子类继承) - xx_:单后置下划线,用于避免与Python关键词的冲突
通过name mangling(名字重整(目的就是以防子类意外重写基类的方法或者属性)如:_Class__object)机制就可以访问private了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #coding=utf-8 class Person(object): def __init__(self, name, age, taste): self.name = name self._age = age self.__taste = taste def showperson(self): print(self.name) print(self._age) print(self.__taste) def dowork(self): self._work() self.__away() def _work(self): print('my _work') def __away(self): print('my __away') class Student(Person): def construction(self, name, age, taste): self.name = name self._age = age self.__taste = taste def showstudent(self): print(self.name) print(self._age) print(self.__taste) @staticmethod def testbug(): _Bug.showbug() # 模块内可以访问,当from cur_module import *时,不导入 class _Bug(object): @staticmethod def showbug(): print("showbug") s1 = Student('jack', 25, 'football') s1.showperson() print('*'*20) # 无法访问__taste,导致报错 # s1.showstudent() s1.construction('rose', 30, 'basketball') s1.showperson() print('*'*20) s1.showstudent() print('*'*20) Student.testbug() |
总结¶
- 父类中属性名为
__名字
的,子类不继承,子类不能访问 - 如果在子类中向
__名字
赋值,那么会在子类中定义的一个与父类相同名字的属性 _名
的变量、函数、类在使用from xxx import *
时都不会被导入
import导入模块¶
1. import 搜索路径¶
路径搜索¶
- 从上面列出的目录里依次查找要导入的模块文件
- '' 表示当前路径
- 列表中的路径的先后顺序代表了python解释器在搜索模块时的先后顺序
程序执行时添加新的模块路径¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | sys.path.append('/home/itcast/xxx') sys.path.insert(0, '/home/itcast/xxx') # 可以确保先搜索这个路径 In [37]: sys.path.insert(0,"/home/python/xxxx") In [38]: sys.path Out[38]: ['/home/python/xxxx', '', '/usr/bin', '/usr/lib/python35.zip', '/usr/lib/python3.5', '/usr/lib/python3.5/plat-x86_64-linux-gnu', '/usr/lib/python3.5/lib-dynload', '/usr/local/lib/python3.5/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3/dist-packages/IPython/extensions', '/home/python/.ipython'] |
2. 重新导入模块¶
模块被导入后,import module
不能重新导入模块,重新导入需用reload
3. 多模块开发时的注意点¶
common.py模块
1 2 | RECV_DATA_LIST = list() # 用来存储数据 HANDLE_FLAG = False # 用来标记是否处理完成 |
recv_msg.py模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from common import RECV_DATA_LIST # from common import HANDLE_FLAG import common def recv_msg(): """模拟接收到数据,然后添加到common模块中的列表中""" print("--->recv_msg") for i in range(5): RECV_DATA_LIST.append(i) def test_recv_data(): """测试接收到的数据""" print("--->test_recv_data") print(RECV_DATA_LIST) def recv_msg_next(): """已经处理完成后,再接收另外的其他数据""" print("--->recv_msg_next") # if HANDLE_FLAG: if common.HANDLE_FLAG: print("------发现之前的数据已经处理完成,这里进行接收其他的数据(模拟过程...)----") else: print("------发现之前的数据未处理完,等待中....------") |
handle_msg.py模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from common import RECV_DATA_LIST # from common import HANDLE_FLAG import common def handle_data(): """模拟处理recv_msg模块接收的数据""" print("--->handle_data") for i in RECV_DATA_LIST: print(i) # 既然处理完成了,那么将变量HANDLE_FLAG设置为True,意味着处理完成 # global HANDLE_FLAG # HANDLE_FLAG = True common.HANDLE_FLAG = True def test_handle_data(): """测试处理是否完成,变量是否设置为True""" print("--->test_handle_data") # if HANDLE_FLAG: if common.HANDLE_FLAG: print("=====已经处理完成====") else: print("=====未处理完成====") |
main.py模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from recv_msg import * from handle_msg import * def main(): # 1. 接收数据 recv_msg() # 2. 测试是否接收完毕 test_recv_data() # 3. 判断如果处理完成,则接收其它数据 recv_msg_next() # 4. 处理数据 handle_data() # 5. 测试是否处理完毕 test_handle_data() # 6. 判断如果处理完成,则接收其它数据 recv_msg_next() if __name__ == "__main__": main() |
导入模块中的数据的时候要使用 包名.变量 (类似于变量与引用变量的区别,直接从另一个问价中导入变量,在新的文件中对这个变量进行改变类似于局部变量不会影响原来文件变量的值即使加上global也不行,列表可以进行数据的添加但是不能赋值,所以以后用的时候多个文件导入共享就直接使用包名.变量名)
再议 封装、继承、多态¶
封装、继承、多态 是面向对象的3大特性
为啥要封装¶
好处¶
- 在使用面向过程编程时,当需要对数据处理时,需要考虑用哪个模板中哪个函数来进行操作,但是当用面向对象编程时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(class)能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据处理时,可以很快速的定位到需要的方法是谁 这样更方便
- 全局变量是只能有1份的,多很多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更方便
- 代码划分更清晰
面向过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 全局变量1 全局变量2 全局变量3 ... def 函数1(): pass def 函数2(): pass def 函数3(): pass def 函数4(): pass def 函数5(): pass |
面向对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class 类(object): 属性1 属性2 def 方法1(self): pass def 方法2(self): pass class 类2(object): 属性3 def 方法3(self): pass def 方法4(self): pass def 方法5(self): pass |
为啥要继承¶
说明¶
- 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
- 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
怎样理解多态¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class MiniOS(object): """MiniOS 操作系统类 """ def __init__(self, name): self.name = name self.apps = [] # 安装的应用程序名称列表 def __str__(self): return "%s 安装的软件列表为 %s" % (self.name, str(self.apps)) def install_app(self, app): # 判断是否已经安装了软件 if app.name in self.apps: print("已经安装了 %s,无需再次安装" % app.name) else: app.install() self.apps.append(app.name) class App(object): def __init__(self, name, version, desc): self.name = name self.version = version self.desc = desc def __str__(self): return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc) def install(self): print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version)) class PyCharm(App): pass class Chrome(App): def install(self): print("正在解压缩安装程序...") super().install() linux = MiniOS("Linux") print(linux) pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境") chrome = Chrome("Chrome", "2.0", "谷歌浏览器") linux.install_app(pycharm) linux.install_app(chrome) linux.install_app(chrome) print(linux) |
运行结果
1 2 3 4 5 6 | Linux 安装的软件列表为 [] 将 PyCharm [1.0] 的执行程序复制到程序目录... 正在解压缩安装程序... 将 Chrome [2.0] 的执行程序复制到程序目录... 已经安装了 Chrome,无需再次安装 Linux 安装的软件列表为 ['PyCharm', 'Chrome'] |
更加超详细的解释Python的封装,继承,多态¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 | ''' 继承: 一种创造新类的方法,新建的类可以继承一个或多个父类的属性 父类又可以称为 基类或超类;子类又称为派生类 继承的目的: 为了减少类与类之间的代码冗余 python中继承的特点: 1.可以遗传、重用父类的属性 2.一个子类可以继承多个父类 3.继承背景下,python中的类分为两种:新式类、经典类 新式类:但凡继承了object的类,以及该类的子类、子子类 在python3中一个类即便是没有显式的继承任何类,则默认继承object类。 即,python3中所有的类都是新式类 经典类:没有继承object 在python2中取分新式、经典类 ''' class Parent1: pass # 在python3中写入object继承,为了能在Python2中兼容 class Parent2(object): pass class Sub1(Parent1): pass class Sub2(Parent1, Parent2): pass # 查看类的父类(基类),访问属性__bases__ print(Parent1.__bases__) print(Parent2.__bases__) # 默认继承 object类 print(Sub1.__bases__) print(Sub2.__bases__) # 判断是否是另一个类的子类,使用内置方法 issubclass() print(issubclass(Sub1,Parent1)) # 判断对象是否是特定类的实例,使用方法isinstance() s = Sub1() # 对象是所属类的实例 print(isinstance(s,Sub1)) # 对象是所属类的父类的实例 print(isinstance(s,Parent1)) # 对象是object类的实例 print(isinstance(s,object)) # 获悉对象属于哪个类,使用属性__class__ print(s.__class__) ''' 子类如何重用父类? 方式一、指名道姓的应用某一个类中的函数 (1、与继承无关 2、且访问的函数,没有自动传值的效果) 方式二、使用内置方法 super() 返回一个特殊的对象,该对象用来专门访问父类中的属性 **完全参照mro列表** python2中:类中使用super(当前类名,self) python3中:super() (1、严格依赖mro列表 2、访问的是绑定方法,有自动传值的效果) 在继承背景下,属性的查找优先级: 1.单继承背景: 对象内部 --》 对象的类 --》 父类 --》 父父类 …… --》object 2.多继承背景: 1)若一个子类继承多个分支,非菱形结构(多个分支没有共同继承经典类、新式类(非object的类)) 对象内部 --》 对象的类 --》父类(从左往右,各分支查找) --》 object 2)菱形继承问题 -1 py3,py2 多父类最终指向同一个类,此类为新式类: 广度优先查找:各父类分支查找,在最后一个父类查找完毕,再去顶级类查找 -2 py2 多父类最终指向同一个类,此类为经典类: 深度优先查找:在第一个父类分支查找,第一次就去找顶级类查找 python查找原理: 使用C3线性算法,计算出一个方法解析顺序(MRO),此列表是一个简单的所有积累的线性顺序列表。 如:>>> f.mro() [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] 所有父类的MRO列表并遵循如下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类 ''' # 利用继承解决代码冗余 class CollegePeople: school = 'college' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Stu(CollegePeople): def __init__(self, name, age, sex, score): # 指名道姓的应用父类中的函数 CollegePeople.__init__(self, name, age, sex) self.score = score def choose_course(self, y): print('%s choose course' % self.name, y) class Teacher(CollegePeople): def __init__(self, name, age, sex, level): # CollegePeople.__init__(self, name, age, sex) # 使用内置方法 super()重用父类 super().__init__( name, age, sex) self.level = level def score(self, stu, num): stu.score = num s1 = Stu('name',18,'male',99) print(s1.__dict__) t1 = Teacher('teacher',28,'female',10) print(t1.__dict__) 方式一、指名道姓的应用某一个类中的函数 (1、与继承无关 2、且访问的函数,没有自动传值的效果) class A: def __init__(self, a): self.a = a class B: def __init__(self,b): self.b = b class C(A): def __init__(self, a,b,c): A.__init__(self,a) B.__init__(self,b) self.c = c c1 = C('a', 'b','c') print(c1.__dict__) # ------------------------------- # 单继承下的查找顺序 class Pra: def f1(self): print('P --> f1') def f2(self): print('P ----> f2') self.f1() class Fo(Pra): def f1(self): print('F ---> f1') a = Fo() a.f2() # ----------------------------------------------- ''' 组合:某一个对象,拥有一个属性,其值来自于另一个类的对象 class Foo: xxx = 222 class Bar: yyy = 111 obj = Foo() b = Bar() obj.attr = Bar() obj.a = b obj.xxxx #调用Foo内属性 obj.attr.yyyy #调用Bar内属性 使用组合的目的: 通过为某一个对象添加属性的方式,间接将两个类进行关联,减少类与类代码冗余 ''' # 使用组合减少代码冗余 class CollegePeople(): school = 'college' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class CollegeStudent(CollegePeople): def __init__(self, name, age, sex, score=0): super().__init__(name, age, sex) self.score = score self.courses = [] def tell_all_course(self): print(('学生[%s]选修的课程如下' % self.name).center(50, '=')) for obj in self.courses: obj.tell_course() print('=' * 60) class CollegeTeacher(CollegePeople): def __init__(self, name, age, sex, level): super().__init__(name, age, sex) self.level = level self.courses = [] def tell_all_course(self): print(('老师[%s]教授的课程如下' % self.name).center(50, '*')) for obj in self.courses: obj.tell_course() print('*' * 70) # 创建课程 class CollegeCourse: def __init__(self, c_name, c_price, c_period): self.c_name = c_name self.c_price = c_price self.c_period = c_period def tell_course(self): print('course:' + self.c_name, self.c_price, self.c_period) python = CollegeCourse('python全栈开发', 1900, '5mons') linux = CollegeCourse('linux架构师', 900, '3mons') # 学生添加课程 stu1 = CollegeStudent('STU1', 38, 'male') stu1.courses.append(python) stu1.courses.append(linux) stu1.tell_all_course() # 教师添加课程 tea1 = CollegeTeacher('TEA1',18,'male',10) tea1.courses.append(linux) tea1.tell_all_course() # ------------------------------------------- ''' 多态:一类事物存在多种形态 ### Python中多态的`作用` **让具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容(功能)的函数。** ### Python中多态的`特点` 1、只关心对象的实例方法是否同名,不关心对象所属的类型; 2、对象所属的类之间,继承关系可有可无; 3、多态的好处可以增加代码的外部调用灵活度,让代码更加通用,兼容性比较强; 4、多态是调用方法的技巧,不会影响到类的内部设计。 例:动物存在多种形态<人,狗,猫> 多态性:在多态的背景下(统一),不考虑实例类型的情况下使用实例 即不同的实例,调用相同的方法,实现不同的结果 例:不同种类的动物,都可以调用吃喝拉撒的方法,但是结果不同。 多态性分为静态多态性和动态多态性 抽象类:一个特殊的类,只能被继承,不能被实例化。 若类是一堆对象中抽取相同的内容而来,抽象类则是从一堆类中抽取相同的内容而来,内容包括了属性和方法。 抽象类和接口:抽象类的本质还是累,指的是一组类的相似性,包括属性和方法,而接口只强调函数属性的相似性。 abc模块:使子类强制遵循父类的方法,即子类必须重写父类方法 注:父类只能用来建立规范,不能用来实例化,无需实现内部方法 例: class Animal(metaclass = abc.ABCMeta): @abc.abstractmethod def speak(self): pass 鸭子类型:如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子 python程序员通常根据这种行为来编写程序。 例如,linux对硬盘的操作看起来像文件操作,那么就用操作文件的方法操作硬盘 ''' ''' 封装:明确的取分内外,封装的属性可以直接在内部调用,而不能被外界使用。 隐藏方式:定义的属性以__开头。例:__name 注意:__xxx__为内置属性而不是影藏 隐藏存储的__dict__总结: 1.__开头的属性,实现的隐藏仅仅只是一种语法意义上的变形,并不会真正限制类外部的访问 2.改变形操作只在类定义阶段检测语法时发生一次,类定义阶段结束后(对象、类的添加修改等操作)定义的属性不会变形 3.属性添加__开头可以防止子类覆盖父类的方法。且父类内只会查找到自身的隐藏属性 封装数据属性目的:将数据属性隐藏,类外部无法直接操作属性,通过类内部接口间接操作 接口实现各种逻辑,控制外部使用接口对属性的操作 封装方法的目的:隔离复杂度 ''' class Foo: __x = 111 # _Foo__x __y = 222 # _Foo__y def __init__(self, name, age): self.__name = name self.__age = age def __func(self): # _Foo__func pass def get_info(self): print(self.__name, self.__age, self.__x) # print(self._Foo__name,self._Foo__age,self._Foo__x) # 外界无法调用隐藏属性 print(Foo.__x) print(Foo.__func) # 查看类名称空间,发现属性名称在定义检测阶段修改 print(Foo.__dict__) # 仍然可以调用修改后名字的属性,不建议使用 print(Foo._Foo__x) print(Foo._Foo__y) # 在类定义检测阶段之后定义的属性,不被修改就放入空间 Foo.__z=333 print(Foo.__dict__) print(Foo.__z) # 对象中也会修改对应的隐藏属性 obj=Foo('123',18) print(obj.__dict__) print(obj.__name) print(obj.__age) obj.get_info() # 对象创建之后,新定义的属性,不会被修改 obj.__sex='male' print(obj.__dict__) print(obj.__sex) # ------------------------------------------- # 封装数据属性的目的: # 通过接口,实现控制再间接修改私有类型 class People: def __init__(self, name, age): self.__name = name self.__age = age def get_info(self): print(self.__name, self.__age) def set_info(self, name, age): if type(name) is not str: print('name type error!') return if type(age) is not int: print('age type error') return self.__name = name self.__age = age # 创建对象 obj = People('lin',12) obj.get_info() # 使用接口作为控制用户输入的判断,达到不影响隐藏属性的作用 obj.set_info(18,'LIN') obj.set_info('LIN','18') # 成功修改对象 obj.set_info('LIN',18) obj.get_info() # -------------------- ''' 特性:通过存取方法存取的属性称之为特性 property装饰器:使类内的函数属性伪装成数据属性 被property装是的函数可以使用@xxx.setter和@xxx.deleter进行set、del操作 注:仅可使用与新式类 ''' class People1: def __init__(self, name, weight, height): self.__name = name self.__weight = weight self.__height = height @property def bmi(self): return self.__weight / (self.__height ** 2) # 凡是被property装饰过的函数,则可以使用函数名.setter作为装饰器,让私有属性可以被修改 @bmi.setter def bmi(self, obj): self.__name = obj @bmi.deleter def bmi(self): del self.__name p1 = People1('p1', 75, 1.85) print(p1.bmi) |