个人笔记——Python高级语法

编程入门 行业动态 更新时间:2024-10-24 02:32:21

个人笔记——Python高级<a href=https://www.elefans.com/category/jswz/34/1770552.html style=语法"/>

个人笔记——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高级语法

本文发布于:2024-02-25 15:35:50,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1699456.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:语法   高级   笔记   Python

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!