个人笔记——Python高级语法

GIL(全局解释器锁)

GIL

例1:单线程死循环

while True:
	pass

在这里插入图片描述
会占用满单核cpu

例2:双线程死循环

import threading

# 子线程死循环
def test():
	while True:
		pass

t1 = threading.Thread(target=test)
t1.start()

# 主线程死循环
while True
	pass

在这里插入图片描述
双核差不多各占用50%CPU

例3:双进程死循环

import multiprocessing

def test():
	while True:
		pass

t1 = multiprocessing.Process(target=test)
t1.start()

while True:
	pass

在这里插入图片描述
双核CPU都被占满

以上例子表明,真正能实现并发肯定是多进程

而多线程实际上在一个时刻真正运行的只有一个线程,其他在等待休息,而GIL就是这个现象的原因
GIL保证多线程程序同一时刻只有一个线程在执行

GIL不是Python语言自带的,而是Python的解释器(cpython)中带有的

GIL的优劣:
GIL不适用于计算密集型,即没有等待时间呢那种,且计算量、操作量极大,无法发挥出多核优势,推荐使用多进程
GIL适用于IO密集型,如网络通信、文件读写等有等待时间的程序,可以再等待过程中运行其他线程

避免GIL的方法

1.使用非cpython的解释器,如用java写的jpython
2.使用其他语言编写,例如可以用c语言编写部分程序,然后再python内调用,如下例
(1)用c语言编写死循环

#include<stdio.h>
void DeadLoop()
{
	while(1)
	{
		;
	}
}

上方执行完后的文件名未loop.c

(2)在终端将c文件编译成一个动态库的命令(Linux平台下)使其能被python调用

gcc loop.c -shared -o libdead_loop.so

(3)在python中调用上方生成的动态库

from ctypes import *
from threading import Thread

# 加载动态库
lib = cdll.LoadLibrary("./libdead_loop.so")

# 创建一个子进程,让其执行c语言编写的函数,次函数是一个死循环
t = Thread(target = lib.DeadLoop)
t.start()

# 主线程
while True:
	pass

常见面试题

描述Python GIL的概念,以及它对python多线程的影响?编写一个多线程抓取网页的程序,并阐明多线程抓取程序是否可比单线程性能有提升,并解释原因。
答案:
1.Python语言和GIL并没有关系,仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL
2.GIL:全局解释器锁,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码
3.线程释放GIL锁的情况:在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间到达阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
4.Python使用多进程是可以利用多核的CPU资源的
5.多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁

深拷贝和浅拷贝

浅拷贝

浅拷贝是对于一个对象的顶层拷贝
例1:先对对象与引用的关系有一定概念

>>a = [11, 22]
>>b = a
>>a = [1, 2]
>>b
>[11, 22]
>>b = a
>>a.append(3)
>>b
>[1, 2, 3]

例2:浅拷贝示例:

>>import copy
>>a = [11, 22]
>>b = [33, 44]
>>c = [a, b]
>>d = copy.copy(c)
>>id(c)
>2689410213320
>>id(d)
>2689410102344
>>id(c[0])
>2689410144008
>>id(d[0])
>2689410144008

上例说明浅拷贝只是对c这一层进行了拷贝(即c、d指向地址不同),内部依旧是原先的指向(即依旧是指向原先a、b的地址)
注1:copy.copy()如果是copy 元组,则会指向同一个地址,因为元组是不可变类型,意味着数据一定不能修改,copy没有意义(前提是元组内的都是指向不可变的数据)
注2:通过列表的切片也能进行copy,如b = a[:], 并且是浅拷贝
例:

import copy
>>a = [11, 22]
>>b = [33, 44]
>>c = (a, b)
>>d = copy.copy(c)
>>e = copy.deepcopy(c)
>>id(c)
>2689410213320
>>id(d)
>2689410213320
>id(e)
>2689410102344
>>a.append(88)
>>c
>([11, 22, 88], [33, 44])
>>d
>([11, 22, 88], [33, 44])
>>e
>([11, 22], [33, 44])

深拷贝

例1:

import copy

>>a = [11, 22]
>>b = a
>>id(a)
>2689406558856
>>id(b)
>2689406558856
>>c = copy.deepcopy(a)
>>id(c)
>2689410144008
>>a.append(33)
>>a
>[11, 22, 33]
>>b
>11, 22, 33]
>>c
>[11, 22]

即通过深拷贝,给c重新指向了一个新的地址,数据和原先的相同

例2:深拷贝说明

>>import copy
>>a = [11, 22]
>>b = [33, 44]
>>c = [a, b]
>>d = copy.deepcopy(c)
>>id(c)
>2689410213320
>>id(d)
>2689410102344
>>id(c[0])
>2689410144008
>>id(d[0])
>2689410213256

即深拷贝包括原先c列表内部的指向也重新进行了指向,所有id都不相同
深拷贝的应用:防止数据污染,例如要对这一部分数据做实验,可以深拷贝一份进行,防止破坏原有数据

私有化、import、封装继承多态

私有化

  • xx:公有变量
  • _x:单前置下划线,私有化属性或方法,from somemodule import * 禁止导入,类对象和子类可以访问
  • __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
  • __xx__:双前后下划线,用户名字空间的魔法对象或属性,例如:init
  • xx_:单后置下划线,用于避免与Python关键词的冲突

import路径

通过sys模块下的path方法可以获取python引用模块的地址列表,在使用import的时候,会按照列表顺序在对应路径下搜索相应模块,如果都没有找到就会提示错误。通过sys.path.insert()可以往列表里添加路径
例:

>>import sys
>>sys.path
>['', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages\\win32', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages\\win32\\lib', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages\\Pythonwin']
>>sys.paht.insert(0,"/home/python/xxxx" )

如果要对已经导入的模块做修改,然后重新导入,可以使用imp模块下的reload方法
例:

>>from imp import reload
>>reload(xxx)

多模块开发时的注意点

多模块开发时一般一个main()模块负责程序主体运行,一个common()模块用于存储公用的变量,其他为处理模块
在使用common的时候一定要注意import common和from common import xxx 的区别!!!
例:
common模块:

AAA = 123
BBB = [1, 3, 5]

deal1模块:

from common import AAA
from common import BBB
AAA = 2222
BBB = [5, 6, 7]

deal2模块:

from common import AAA
from common import BBB
AAA = 2222
BBB.append(666)

deal3模块:

import common
common.AAA = 888
common.BBB = [9, 6, 6]

main模块:

import common
deal1()
print(common.AAA)
print(common.BBB)
deal2()
print(common.AAA)
print(common.BBB)
deal3()
print(common.AAA)
print(common.BBB)

最后运行的结果为:

123
[1, 3, 5]

123
[1, 3, 5, 666]

888
[9, 6, 6]

因为在import common的时候,对common.AAA做出修改实际上指向的就是common内的AAA,而使用from common import AAA的时候,其实是创建了一个变量指向common内AAA所指向的值,修改后就指向了另外的对象,不影响原AAA的指向。而当BBB是列表的时候,如果用的是append,则是在指向的值上进行修改,可以影响到common内的BBB,如果是用=则是指向了另外的列表,不会对common内的BBB造成影响。

封装、继承、多态

为什么要用封装?
之所以要面向对象编程,对代码进行封装,就是为了让代码显得更加简洁
为什么要用继承?
提高代码的重复利用率
如何理解多态
根据需求可以对子类进行修改,然后调用,调用的函数代码都是相同的,但根据被调用的类是完全继承父类还是修改后的子类不同,可以产生不同的效果,这就是多态。

多继承以及MRO顺序

当一个子类继承多个父类的时候,如果子类要继承父类的方法,使用**父类.方法()super().方法()**需要注意不同,super一条语句只能调用一个父类,不会全部调用,可以使用 子类.__mro__来查看调用的顺序
例:

class Parent(object):
	def __init__(self, name, *args, **kwargs):  # 为避免多继承出错,使用不定长参数,接受参数
		print("parent的init开始被调用")
		self.name = name
		print("parent的init结束被调用")

class Son1(Parent):
	def __init__(self, name, age, *args, **kwargs):
		print("Son1的init开始被调用")
		self.age = age
		super().__init__(name, *args, **kwargs)
		print("Son1的init结束被调用")


class Son2(Parent):
	def __init__(self, name, gender, *args, **kwargs):
		print("Son2的init开始被调用")
		self.gender = gender
		super().__init__(name, *args, **kwargs)
		print("Son2的init结束被调用")

class Grandson(Son1, Son2):
	def __init__(self, name, age, gender):
		print("Grandson的init开始被调用")
		# 多继承时,相对于使用类名.__init__方法,要把每个父名都写一遍
		# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
		super().__init__(name, age, gender)
		print("Grandson的init结束被调用")

print(Grandson.__mro__)
'''可以在结果看到,先运行的类是自身的Grandson,之后进入Son1,但在运行到Son1中的super时不会调到Parent,而是到了Son2
因为每次遇到调用super的时候都会按照mro元组内的顺序进行比对,
例如一开始是Grandson→比对成功后就进入到Son1→在Son1中遇到super时再次比对
→Grandson不对应→跳过→Son1对应→进入下一个,Son2
→Son2中遇到super→Grandson不对应→Son1不对应→Son2对应→进入下一个Parent→……

如果在super()内加入参数,例如super(Son2, self).__init__
则会按照mro顺序直接跳过前面的Grandson,Son1,比对Son2成功后直接调用Parent类

mro使用的算法叫C3'''

gs = Grandson("grandson", 12, "男")
print("姓名", gs.name)
print("年龄", gs.age)
print("性别", gs.gender)

>>[out]:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名 grandson
年龄 12
性别 男

类属性、实例属性、静态方法、类方法补充

当需要通过子类来更改父类的类属性的时候,可以使用:子类.class.类属性 = xxx 来进行修改
例:

class Parent(object):
	country = '中国'
	def __init__(self, name):
		self.name = name

test = Parent('浙江')
test.__class__.country = "中国中国"
print(Parent.country)
>>"中国中国"

子类可以直接掉用父类中的静态方法和类方法
例:

   ...:class Parent(object):
   ...:     country = '中国'
   ...:     def __init__(self, name):
   ...:         self.name = name
   ...:     @classmethod
   ...:     def chg(cls):
   ...:         cls.country = 'babababa'
   ...:     @staticmethod
   ...:     def sta():
   ...:         print('agsagsfd')
   ...:
   ...: test = Parent('浙江')
   ...: test.__class__.country = "中国中国"
   ...:
   ...:

In [6]: test.chg()
In [7]: Parent.country
Out[7]: 'babababa'
In [8]: test.sta()
Out[8]: agsagsfd

property属性

property下定义的方法一般不传参,但一定要返回一个值,掉用该方法的时候直接写方法名即可,不需要添加括号
例:

class Goods:
	@property
	def size(self):
		return 100
obj = Goods()
ret - obj.size
print(ret)
>>100

property最主要的作用就在于方便程序员辨认,这个函数可以直接获取值,不需要考虑是否需要传参

property属性的两种方式

1.装饰器 即:在方法上应用装饰器,如上例
2.类属性 即:在类中定义值为property对象的类属性

装饰器方式

在python3中因为默认继承object类,即是新式类,具有三种@property装饰器
例1:

class Goods:
	@property
	def price(self):
		print('@property')	
		
	@price.setter
	def price(self, value):
		print('@price.setter')
	@price.deleter
	def price(self):
		print('@price.deleter')

obj = Goods()
obj.price  # 自动执行@property修饰的price方法,并获取方法的返回值
obj.price = 123  # 自动执行@price.setter修饰的price方法,并将123赋值给方法的参数
del obj.price  # 自动执行@price。deleter修饰的price方法

例2:实际应用

class Goods(object):
	def __init__(self):
		self.original_price = 100
		self.discount = 0.8
	@ property
	def price(self):
		new_price = self.original_price * self.discount
		return new_price
	@ price.setter
	def price(self, value):
		self.original_price = value
	@ price.deleter
	def price(self):
		del self.original_price

obj = Goods()
------------------------------------------------------------
In [11]: obj.price
Out[11]: 80.0

In [12]: obj.price = 200

In [13]: obj.price
Out[13]: 160.0

In [14]: del obj.price

类属性方式

例:

class Foo:
	def get_bar(self):
		return "laowang"
	BAR = propert(get_bar)
obj = Foo()
ret = obj.BAR  # 自动调用get_bar方法,并获取方法的返回值
print(ret)
>>laowang

property方法中有四个参数
第一个参数是方法名,调用 对象.属性 时自动触发执行方法
第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
第四个参数是字符串,调用 对象.属性.doc,此参数是该属性的描述信息
例:

class Foo(object):
	def get_bar(self):
		print("getter...")
		return "laowang"
	def set_bar(self, value):
		"""必须两个参数"""
		print("setter...")
		return "set value" + value
	def del_bar(self):
		print("deleter")
		return "laowang"
	BAR = property(get_bar, set_bar, del_bar, "description...")

obj = Foo()
------------------------------------------------
In [22]: obj.BAR
getter...
Out[22]: 'laowang'

In [23]: obj.BAR = "alex"
setter...

In [24]: Foo.BAR.__doc__
Out[24]: 'description...'

In [25]: del obj.BAR
deleter

通过property属性,能够简化调用者获取数据的流程

私有属性添加getter和setter方法

例:

class Money(object):
	def __init__(self)
		self.__money = 0
	def getMoney(self):
		return self.__money
	def setMoney(self, value):
		if isinstance(value, int):
			self.__money = value
		else:
			print("errer:不是整型数字")

使用property升级getter和setter方法

例:

class Money(object):
	def __init__(self):
		self.__money = 0
	def getMoney(self):
		return self.__money
	def setMoney(self, value):
		if isinstance(value, int):
			self.__money = value
		else:
			print("errer:不是整型数字")
	money = property(getMoney, setMoney)
a = Money()
---------------------------------------------
In [32]: a.money = 100

In [33]: a.money
Out[33]: 100

使用property取代getter和setter方法

class Money(object):
	def __init__(self):
		self.__money = 0
	@property
	def money(self):
		return self.__money
	@money.setter
	def money(self, value):
		if isinstance(value, int):
			self.__money = value
		else:
			print("errer:不是整型数字")
a = Money()
---------------------------------------------
In [32]: a.money = 100

In [33]: a.money
Out[33]: 100

私有属性

私有属性无法被调用,但可以使用 实例对象.__dict__进行查看
例:

class Test(object):
	def __init__(self, name):
		self.__name = name

a = Test("laowang")
a.__dict__
>>{"_Test__name": "laowang"}

注:也可以直接通过a._Test__name直接查看到

魔法属性

doc

用于获取类描述
例:

class Test(object):
	"""aaabbbccc"""
	def fucn:
		pass
		
print(Test.__doc__)
>>aaabbbccc

使用help(Test)也可以显示

class__和__module

__class__表示当前操作的对象的类是什么
__module__表示当前操作的对象在哪个模块
例:假设模块test.py下有个类Person如下

class Person(object):
	def __init__(self):
		self.name = "laowang"

从test调用Person模块

from test import Person

obj = Person()
print(obj.__module__)
print(obj.__class__)
>>test
test.Person

call

用于在对象之后加括号执行
注:__init__方法的执行是由创建对象触发的,而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或 类()()
例:

class Foo:
	def __init__(self):
		pass
	def __call__(self):
		print("__call__")
obj = Foo()  # 执行__init__
obj() # 执行__call__
>>__call__

__getitem__;__setitem__;__delitem__

用于索引操作,使类能用作字典
例:

class Foo(object):
	def __getitem__(self, key):
		print("__getitem__", key)
	def __setitem__(self, key, value):
		print("__setitem__", key, value)
	def __delitem__(self, key):
		print("__delitem__", key)

obj = Foo()
result = obj['k1']  # 自动触发执行__getitem__
obj['k1'] = 'laowang'  # 自动触发执行__setitem__
del obj['k1']  # 自动触发执行__delitem__

>>__getitem__ k1
__setitem__ k1 laowang
__delitem__ k1

__getslice__;__setslice__;__delslice__

这三个方法用于切片操作
例:

class Foo(object):
	def __getslice__(self, i, j):
		print("__getslice__", i, j)
	def __setslice__(self, i, j, sequence):
		print("__setslice__", i, j)
	def __delslice__(self, i, j):
		print("__delslice__", i, j)
obj = Foo()

obj[-1:1]  # 自动触发执行__getslice__
obj[0:1] = [11, 22, 33, 44]   # 自动触发执行__setslice__
del obj[0:2]  # 自动触发执行__delslice__

上下文管理器

任何实现了__enter__()和__exit__()方法的对象都可称之为上下文管理器,上下文管理器对象可以使用with关键字。显然,文件(file)对象也实现了上下文管理器
例:自己建立一个带有这两个方法的类

class File(object):
	def __init__(self, filename, mode):
		self.filename = filename
		self.mode = mode
	def __enter__(self):
		print("entering")
		self.f = open(self.filename, self.mode)
		return self.f
	def __exit__(self, *args):
		print("exiting")
		self.f.close()

with File("out.txt", "w") as f:
	print("writing")
	f.write("Hello World")

实现上下文管理器的另外方式

Python3还提供了一个contextmanager的装饰器,更一步简化了上下文管理器的实现方式,通过yield将函数分割成两部分,yield之前的语句在__enter__方法中执行,yield之后的语句在__exit__方法中执行
例:

from contextlib import contextmanager
@contextmanager
def my_open(path, mode):
	f = open(path, mode)
	yield f
	f.close

调用

with my_open("out.txt", "w") as f:
	f.write("Hello, the simplest context manager")
发布了33 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sinat_38354769/article/details/98048066
今日推荐