语法"/>
个人笔记——Python高级语法
Python高级语法
- GIL(全局解释器锁)
- GIL
- 避免GIL的方法
- 常见面试题
- 深拷贝和浅拷贝
- 浅拷贝
- 深拷贝
- 私有化、import、封装继承多态
- 私有化
- import路径
- 多模块开发时的注意点
- 封装、继承、多态
- 多继承以及MRO顺序
- 类属性、实例属性、静态方法、类方法补充
- property属性
- property属性的两种方式
- 装饰器方式
- 类属性方式
- 私有属性添加getter和setter方法
- 使用property升级getter和setter方法
- 使用property取代getter和setter方法
- 私有属性
- 魔法属性
- __doc__
- __class__和__module__
- __call__
- \_\_getitem__;\_\_setitem__;\_\_delitem__
- \_\_getslice__;\_\_setslice__;\_\_delslice__
- 上下文管理器
- 实现上下文管理器的另外方式
GIL(全局解释器锁)
GIL
例1:单线程死循环
while True:pass
会占用满单核cpu
例2:双线程死循环
import threading# 子线程死循环
def test():while True:passt1 = threading.Thread(target=test)
t1.start()# 主线程死循环
while Truepass
双核差不多各占用50%CPU
例3:双进程死循环
import multiprocessingdef test():while True:passt1 = 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 = nameprint("parent的init结束被调用")class Son1(Parent):def __init__(self, name, age, *args, **kwargs):print("Son1的init开始被调用")self.age = agesuper().__init__(name, *args, **kwargs)print("Son1的init结束被调用")class Son2(Parent):def __init__(self, name, gender, *args, **kwargs):print("Son2的init开始被调用")self.gender = gendersuper().__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 = nametest = 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:@propertydef size(self):return 100
obj = Goods()
ret - obj.size
print(ret)
>>100
property最主要的作用就在于方便程序员辨认,这个函数可以直接获取值,不需要考虑是否需要传参
property属性的两种方式
1.装饰器 即:在方法上应用装饰器,如上例
2.类属性 即:在类中定义值为property对象的类属性
装饰器方式
在python3中因为默认继承object类,即是新式类,具有三种@property装饰器
例1:
class Goods:@propertydef price(self):print('@property') @price.setterdef price(self, value):print('@price.setter')@price.deleterdef 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 = 100self.discount = 0.8@ propertydef price(self):new_price = self.original_price * self.discountreturn new_price@ price.setterdef price(self, value):self.original_price = value@ price.deleterdef price(self):del self.original_priceobj = Goods()
------------------------------------------------------------
In [11]: obj.price
Out[11]: 80.0In [12]: obj.price = 200In [13]: obj.price
Out[13]: 160.0In [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" + valuedef 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 = 0def getMoney(self):return self.__moneydef setMoney(self, value):if isinstance(value, int):self.__money = valueelse:print("errer:不是整型数字")
使用property升级getter和setter方法
例:
class Money(object):def __init__(self):self.__money = 0def getMoney(self):return self.__moneydef setMoney(self, value):if isinstance(value, int):self.__money = valueelse:print("errer:不是整型数字")money = property(getMoney, setMoney)
a = Money()
---------------------------------------------
In [32]: a.money = 100In [33]: a.money
Out[33]: 100
使用property取代getter和setter方法
class Money(object):def __init__(self):self.__money = 0@propertydef money(self):return self.__money@money.setterdef money(self, value):if isinstance(value, int):self.__money = valueelse:print("errer:不是整型数字")
a = Money()
---------------------------------------------
In [32]: a.money = 100In [33]: a.money
Out[33]: 100
私有属性
私有属性无法被调用,但可以使用 实例对象.__dict__进行查看
例:
class Test(object):def __init__(self, name):self.__name = namea = Test("laowang")
a.__dict__
>>{"_Test__name": "laowang"}
注:也可以直接通过a._Test__name直接查看到
魔法属性
doc
用于获取类描述
例:
class Test(object):"""aaabbbccc"""def fucn:passprint(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 Personobj = Person()
print(obj.__module__)
print(obj.__class__)
>>test
test.Person
call
用于在对象之后加括号执行
注:__init__方法的执行是由创建对象触发的,而对于__call__方法的执行是由对象后加括号触发的,即:对象() 或 类()()
例:
class Foo:def __init__(self):passdef __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 = filenameself.mode = modedef __enter__(self):print("entering")self.f = open(self.filename, self.mode)return self.fdef __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 ff.close
调用
with my_open("out.txt", "w") as f:f.write("Hello, the simplest context manager")
更多推荐
个人笔记——Python高级语法
发布评论