Python字符串的运算-数据类型

大家可能很奇怪,只听说过数的运算,字符串还能运算呢?是的,而且字符串的运算还是相当灵活、比较复杂的,熟练掌握了字符串的运算,可以极大提高将来编程的效率和质量,尤其涉及字符串的各种应用场景。

字符串的连接

x = "您好"
y = "Python"
z = x+y
print(z)

上述代码的输出结果如下所示,采用加号(“+”)运算符直接把两个字符串连接了起来。

您好Python

重复字符串的连接

x = "Python"
z = x*3
print(z)

上述代码的输出结果如下所示,采用乘号(“*”)运算符,字符串“x”作为被乘数,数字“3”作为乘数,最终把3个x字符串连接了起来。

PythonPythonPython

字符串的关系运算

print('abc'=='abc')
print('ac'>'abc')
print('a'<'abc')
print('abc'!='ABC')

上述代码的输出结果如下所示,均为True。

True
True
True
True

采用“<”、“<=”、“>”、“>=”、“==”、“!=”关系运算符对两个字符串进行比较,其实是依次对两字符串同位置上字符的ASCII码进行比较。在ASCII码标准中,用从65至90的数,依次代表了26个大写英文字母(从A、B、C,一直到Z,与字母表顺序一致)的ASCII码;用从97至122的数,依次代表了26个小写英文字母(从a、b、c,一直到z,与字母表顺序一致)的ASCII码。需要说明两点:第一,因为ASCII码标准对于字母编码的设计与英文字母表的顺序一致,所以,基于ASCII码的比较,其实也相当于基于字母表排序的比较;第二,需要注意的是,大小写字母是分别编码的,且大写字母的编码段整体比小写字母的编码段要偏小,一般情况下,若要进行不区分字母大小的字符串比较,需要事先把参与比较的字符串全部转换成大写或者小写后再进行比较。

第1行代码:两字符串所有位置上的字符均一样,两字符串相等;
第2行代码:在比较第2个字符时,因为“c”的ASCII码为99,“b”的ASCII码为98,因此前者字符串大于后者字符串;
第3行代码:第1个字符相同,但前者字符串短、后者长,因此前者小于后者;
第4行代码:在比较第1个字符时,因为小写“a”的ASCII码为97,大写“A”的ASCII码为65,因此前者字符串大于后者字符串。

另外,解释一下含中文字符串的比较,因为字符串的比较其实是基于系统中对英文字符或中文字的编码进行的比较,跟中文字的拼音顺序或者笔画顺序没有关系,所以,对于中文字符串的关系运算,一般用到的主要只有“==”和“!=”。补充一点,可采用Python内部函数ord,查询英文字符或中文字的编码,把要查询的字符或中文字作为字符串参数传入即可,需要注意的是,这个函数传入的字符串的长度只能为1,即只能包含1个英文字符或者一个中文字。

print(ord("a"),ord("A"),ord("字"),ord("关"))
print("字符串"=="字符串")
print("字符串"!="关系比较")
print("字符串">"关系比较")

输出结果如下:

97 65 23383 20851
True
True
True

字符串的包含关系

x = "t" in "Python"
y = "包含" in "字符串的包含关系"
z = "不包含" not in "字符串的包含关系"
print(x,y,z)

上述代码的输出结果如下所示,均为True。当“in”及“not in”前后均为字符串时,会进行字符串包含关系的运算:

in:如果“in”前面的字符串完整的包含在“in”后面的字符串里面(包含一次或多次),则返回True,否则返回False;
not in:如果“not in”前面的字符串没有被完整的包含在“not in”后面的字符串里面,则返回True,否则返回False。

True True True

字符串的访问
对字符串的访问有三种方式:
1.通过字符串变量直接访问整个字符串,如下所示:

x = "Python"
print(x)

上述print函数通过把x变量作为参数,因此输出如下,打印输出了整个字符串。

Python

2.通过采用索引(下标)可以访问字符串中任意一个字符,如下所示,需要说明的是:单个字符仍然属于字符串类型,只不过其是一个长度为1的字符串而已。

x = "Python"
print(x[2],x[0],x[5])
print(x[-4],x[-6],x[-1])

输出结果如下所示:

t P n
t P n

这里需要说明几点:

按索引的方式访问时,在变量后的中括号(英文中括号)内标示出要访问的字符所在的索引即可;
字符串的索引可以采用正向(从首至尾)的计数方式,此时,索引的值从0开始,因此,字符串最后一个字符的索引为其长度减1,比如上述“Python”字符串,按正向方式计数的索引值依次为:0、1、2、3、4、5。因此,字符串的索引值不允许大于等于字符串长度值,否则会报错:“IndexError: string index out of range”。
字符串的索引也可以采用逆向(从尾至首)的计数方式,此时,字符串最后一个字符的索引值为-1(即我们经常提到的倒数第一),依次逆向从尾至首,-2、-3、-4…因此,字符串第一个字符的索引为其长度值取负,比如上述“Python”字符串,按逆向方式计数的索引值依次为(从尾至首排序):-1、-2、-3、-4、-5、-6。因此,字符串的索引值不允许小于字符串长度值取负,否则同样会报错:“IndexError: string index out of range”。

3.切片的访问方式。上述第2种访问方式只能访问一个字符,而切片方式则可以访问字符串中相应范围内的子字符串(字符串的全部或部分字符)。之所以称之为“切片”,因为如同切面包片一样,两刀切下去,去掉头、去掉尾,把中间的字符串取出来,如下所示。

x = "Python"
print(x[2:5])
print(x[-4:5])
print(x[-4:-1])
print(x[2:])
print(x[-4:])
print(x[:5])
print(x[:-1])
print(x[:])

输出的结果如下所示:

tho
tho
tho
thon
thon
Pytho
Pytho
Python

这里说明几点:

切片的访问方式仍然采用英文中括号的形式,但是中括号里面多了一个冒号(英文冒号),冒号前面表示要取的起始字符(包括该字符)、冒号后面表示截止字符(注意:不包括该字符),需要注意的是:起始字符应在截止字符的左侧,否则输出会是空字符串;
冒号前后的索引值均可以选择按正向、逆向任一种计数方式,但是要注意不要超过了上述提到的索引取值范围;
冒号前面要是不输入索引值,则默认表示从首字符开始;冒号后面要是不输入索引值,则默认表示截至到最后一个字符(包括最后的这个字符)。

Python简单爬取猫眼电影排名

Python爬取猫眼电影排名

1 导入需要的库

import requests
import re
import json

requests 用来实现URL的请求,就相当于我们输入网址,浏览网页。
re 使用正则表达式抓取我们想要的数据。
json 因为请求连接返回的是 json 类型的字符串,因此我们需要用JSON库转换成普通字符串保存。

2 想要爬取到我们想要的数据,我们第一步当然是要获取目标网页的html代码。
这里我们以爬取猫眼电影电影排名榜单为例,它所在网页的URL为 https://maoyan.com/board/4 。
废话不多说,先上代码。

def get_one_page(url):
headers={
'user-Agent':'Mozilla/5.0(Macintosh;Intel Mac OS X 10_13_3) AppleWebkit/537.36 (KHTML,like Gecko) Chrome/65.0.3325.162 Safari/537.36'
}
response=requests.get(url,headers=headers)
if response.status_code==200:
return response.text

上面的代码中,构造了一个输入URL,获取目标网页HTML文档的函数。
headers信息包含了user-Agent字段信息,它的意思是代表一个浏览器,相当于我们伪装成一个浏览器进行访问。
然后使用requests库中的get()函数实现一个GET请求。
response.status_code==200 的意思是网页请求成功。
然后,返回请求网页的HTML文档,即return response.text,部分返回结果如下图:
获取的部分HTML文档

2 爬取到目标网页的HTML后,需要对它进行解析,才能得到我们想要的数据。

def parse_one_page(html):
pattern=re.compile('<dd>.*?board-index.*?>(.*?)</i>.*?name.*?a.*?>(.*?)</a>',re.S)
items=re.findall(pattern,html)
print(items)
for item in items:
yield {
'index':item[0],
'title':item[1]
}

上面的代码中,是我们构造的解析HTML的函数。
在这里,我们使用了正则表达式匹配我们的数据,这个正则表达式,可以抓取电影排名和电影名称。
然后,用生成器输出结果,部分结果如下图:
在这里插入图片描述

3 得到解析结果后,下一步就是保存数据。

def write_to_file(content):
with open('result1.txt','a') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')

使用JSON库的dumps函数,可以把json类型的数据,转换成普通的字符串。

4 构造主函数,调用上面的函数。

def main(offset):
url='https://maoyan.com/board/4?offset='+str(offset)
html=get_one_page(url)
tt=parse_one_page(html)
for n in tt:
write_to_file(n)

5 爬取的部分结果
在这里插入图片描述
6 完整的代码

import requests
import re
import json
def get_one_page(url):
headers={
'user-Agent':'Mozilla/5.0(Macintosh;Intel Mac OS X 10_13_3) AppleWebkit/537.36 (KHTML,like Gecko) Chrome/65.0.3325.162 Safari/537.36'
}
response=requests.get(url,headers=headers)
if response.status_code==200:
return response.text

def write_to_file(content):
with open('result1.txt','a') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n')

def main(offset):
url='https://maoyan.com/board/4?offset='+str(offset)
html=get_one_page(url)
tt=parse_one_page(html)
for n in tt:
write_to_file(n)

def parse_one_page(html):
pattern=re.compile('<dd>.*?board-index.*?>(.*?)</i>.*?name.*?a.*?>(.*?)</a>',re.S)
items=re.findall(pattern,html)
print(items)
for item in items:
yield {
'index':item[0],
'title':item[1]
}

if __name__=="__main__":
for i in range(10):
main(offset=i*10)
print(i)

Python:Scrapy实战项目手机App抓包爬虫

class DouyuspiderItem(scrapy.Item):
name = scrapy.Field()# 存储照片的名字
imagesUrls = scrapy.Field()# 照片的url路径
imagesPath = scrapy.Field()# 照片保存在本地的路径

2. spiders/douyu.py

import scrapy
import json
from douyuSpider.items import DouyuspiderItem

class DouyuSpider(scrapy.Spider):
name = "douyu"
allowd_domains = ["http://capi.douyucdn.cn"]

offset = 0
url = "http://capi.douyucdn.cn/api/v1/getVerticalRoom?limit=20&offset="
start_urls = [url + str(offset)]

def parse(self, response):
# 返回从json里获取 data段数据集合
data = json.loads(response.text)["data"]

for each in data:
item = DouyuspiderItem()
item["name"] = each["nickname"]
item["imagesUrls"] = each["vertical_src"]

yield item

self.offset += 20
yield scrapy.Request(self.url + str(self.offset), callback = self.parse)

3. 设置setting.py

ITEM_PIPELINES = {'douyuSpider.pipelines.ImagesPipeline': 1}

# Images 的存放位置,之后会在pipelines.py里调用
IMAGES_STORE = "/Users/Power/lesson_python/douyuSpider/Images"

# user-agent
USER_AGENT = 'DYZB/2.290 (iPhone; iOS 9.3.4; Scale/2.00)'

4. pipelines.py

import scrapy
import os
from scrapy.pipelines.images import ImagesPipeline
from scrapy.utils.project import get_project_settings

class ImagesPipeline(ImagesPipeline):
IMAGES_STORE = get_project_settings().get("IMAGES_STORE")

def get_media_requests(self, item, info):
image_url = item["imagesUrls"]
yield scrapy.Request(image_url)

def item_completed(self, results, item, info):
# 固定写法,获取图片路径,同时判断这个路径是否正确,如果正确,就放到 image_path里,ImagesPipeline源码剖析可见
image_path = [x["path"] for ok, x in results if ok]

os.rename(self.IMAGES_STORE + "/" + image_path[0], self.IMAGES_STORE + "/" + item["name"] + ".jpg")
item["imagesPath"] = self.IMAGES_STORE + "/" + item["name"]

return item

#get_media_requests的作用就是为每一个图片链接生成一个Request对象,这个方法的输出将作为item_completed的输入中的results,results是一个元组,每个元组包括(success, imageinfoorfailure)。如果success=true,imageinfoor_failure是一个字典,包括url/path/checksum三个key。

在项目根目录下新建main.py文件,用于调试

from scrapy import cmdline
cmdline.execute('scrapy crawl douyu'.split())

执行程序

py2 main.py

python之类的属性、类的实例、类方法、实例方法、静态方法(25)

# -*- coding: utf-8 -*-
# @File :
# @Author: dianxiaoer
# @Date : 2019/11/11
# @Desc : 类的属性、类的实例、类方法、实例方法、静态方法

# # 1、定义一个类
# class Person:
# pass
# # 2、创建实例
# zhangsan = Person()
# lisi = Person()
# # 3、实例属性
# zhangsan.name = "python"
# lisi.name = "C++"
# # 4、创建实例时候,__init__方法会被自动调用
# class Person:
# def __init__(self,name,age,gender):
# self.name = name
# self.age = age
# self.gender = gender
# # 5、属性的访问限制
# # python中定义的普通变量,可以被外部访问。但是有时候,定义的变量不希望被外部访问;
# # 如单下划线"_变量名",双下划綫"__变量名";
# # 6、类属性
# class Person:
# address = "Mars"
# def __init__(self,name):
# self.name = name
# print(Person.address) # 类属性直接绑定在类上面,可以不实例化直接进行 类名.属性 进行访问
# p = Person("C#")
# print(p.address) # 也可以实例化对象来访问
# # 7、类方法
# # @classmethod来标识,对于类方法,第一个参数必须是类对象;
# # 一般以cls作为第一个参数(当然可以用其他名称的变量,但我们一般习惯使用'cls'了);
# # 能够通过实例对象和类对象去访问;【此处注意】
# class People:
# # 类属性
# country = 'Mars'
# # 类方法
# @classmethod
# def getCountry(cls):
# return cls.country
#
# p = Person() # 实例化一个对象
# print(p.getCountry()) # 通过实例对象访问
# print(Person.country) # 通过类对象访问
# 7.1 类方法修改类的属性
# class People:
# # 类属性
# country = 'Mars'
#
# # 类方法
# @classmethod
# def getCountry(cls):
# return cls.country
#
# @classmethod # 修改类的属性,不要通过实例对象修改类的属性,没意义!!!而是给实例绑定了一个实例属性!!!
# def setCountry(cls,country):
# cls.country = country
#
#
# p = People() # 实例化一个对象
# print(p.getCountry()) # 通过实例对象访问
# print(People.country) # 通过类对象访问
# p.setCountry("Earth")
# print("#"*10)
# print(p.getCountry()) # 通过实例对象访问
# print(People.country) # 通过类对象访问
# 8、静态方法
# class People:
# country = 'Mars'
#
# @staticmethod
# def getCountry():
# return People.country
#
# p = People()
# print(p.getCountry()) # 通过实例对象访问静态方法
# print(People.getCountry()) # 通过类访问静态方法
# 9、为什么用静态方法???
# 静态方法用来存放逻辑性的代码,这些逻辑属于某个类但是和类本身没有交互,即在静态方法中,不会涉及到类中的方法和属性的操作;
# 可以理解为将静态方法存在此类的名称空间中;
# 事实上,在python引入静态方法之前,通常是在全局名称空间中创建函数,但这样会导致代码难以维护;
# import time
#
# class Timer:
# def __init__(self):
# pass
#
# @staticmethod
# def showTime():
# pass
# t = Timer()
# now = t.showTime()
# print(now)
# 10、总结
# (1) 类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;
# (2)实例方法的第一个参数是实例对象self,通过self引用的可能是类属性、也有可能是实例属性;
# 不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高;
# (3)静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用;

Python多线程爬取表情包,1秒下载五十张表情包

前言

文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。

PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取

Python学习资料免费领取

从 QQ 到微信,表情包一直都是中国互联网用户的「心头好」,时至今日,甚至发展到了「无表情包不聊天」的地步。

无论是正事儿还是闲侃,表情包都必不可少。聊天到一半发现表情包不够用怎么办?今天正心老师带你彻底解决这个问题!

知识点:

requests
css选择器

开发环境:

版 本:anaconda5.2.0(python3.6.5)
编辑器:pycharm

第三方库:

requests
parsel

进行网页分析

目标站点
在这里插入图片描述

开发者工具的使用
network
element

concurrent.futures

Python3.2带来了 concurrent.futures 模块,这个模块具有线程池和进程池、管理并行编程任务、处理非确定性的执行流程、进程/线程同步等功能。

此模块由以下部分组成:

concurrent.futures.Executor: 这是一个虚拟基类,提供了异步执行的方法。
submit(function, argument): 调度函数(可调用的对象)的执行,将 argument 作为参数传入。
map(function, argument): 将 argument 作为参数执行函数,以 异步 的方式。
shutdown(Wait=True): 发出让执行者释放所有资源的信号。
concurrent.futures.Future: 其中包括函数的异步执行。Future对象是submit任务(即带有参数的functions)到executor的实例。

Executor是抽象类,可以通过子类访问,即线程或进程的 ExecutorPools 。因为,线程或进程的实例是依赖于资源的任务,所以最好以“池”的形式将他们组织在一起,作为可以重用的launcher或executor。
使用线程池

线程池或进程池是用于在程序中优化和简化线程/进程的使用。通过池,你可以提交任务给executor。池由两部分组成,一部分是内部的队列,存放着待执行的任务;另一部分是一系列的进程或线程,用于执行这些任务。池的概念主要目的是为了重用:让线程或进程在生命周期内可以多次使用。它减少了创建创建线程和进程的开销,提高了程序性能。重用不是必须的规则,但它是程序员在应用中使用池的主要原因。

current.Futures 模块提供了两种 Executor 的子类,各自独立操作一个线程池和一个进程池。这两个子类分别是:

concurrent.futures.ThreadPoolExecutor(max_workers)
max_workers 参数表示最多有多少个worker并行执行任务。

下面的示例代码展示了线程池和进程池的功能。这里的任务是,给一个list number_list ,包含1到10。

通过有5个worker的线程池执行

线程池运行

然后,我们使用了 futures.ThreadPoolExecutor 模块的线程池跑了一次::

if __name__ == "__main__":
start_time_1 = time.time()
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
for item in number_list:
executor.submit(evaluate_item, item)
print("线程池计算的时间:" + str(time.time() - start_time_1), "秒")

# executor1 = concurrent.futures.ThreadPoolExecutor(max_workers=5)
# for item in number_list:
# executor1.submit(evaluate_item, item)
# executor1.shutdown()
# print("线程池计算的时间:" + str(time.time() - start_time_1), "秒")

ThreadPoolExecutor 使用线程池中的一个线程执行给定的任务。池中一共有5个线程,每一个线程从池中取得一个任务然后执行它。当任务执行完成,再从池中拿到另一个任务。

当所有的任务执行完成后,打印出执行用的时间:

print("线程池计算的时间:" + str(time.time() - start_time_1), "秒")

Python3 MongoDB

写在最前:

MongoDB 是目前最流行的 NoSQL 数据库之一,使用的数据类型 BSON(类似 JSON)。

Python 要连接 MongoDB 需要 MongoDB 驱动,这里我们使用 PyMongo 驱动来连接。
一、安装
1、pip 安装

小知识:pip 是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。

安装 pymongo:$ python3 -m pip3 install pymongo

也可以指定版本:$ python3 -m pip3 install pymongo==3.5.1

更新 pymongo:$ python3 -m pip3 install --upgrade pymongo
2、conda 安装

小知识:Anaconda 指的是一个开源的 Python 发行版本,其包含了 conda、Python 等 180 多个科学包及其依赖项。里面所包含的 Jupyter Notebook 是数据挖掘领域中最热门的工具。

废话不多说,安装 pymongo:$ conda install -n showufei pymongo
二、测试 PyMongo

from pymongo import MongoClient

# single mongo
myclient01 = MongoClient('mongodb://127.0.0.1:27017')
dblist01 = myclient01.list_database_names()
print(dblist01)

myclient02 = MongoClient(host='127.0.0.1', port=27017)
dblist02 = myclient02.list_database_names()
print(dblist02)

# mongo cluster
mycluster03 = MongoClient('mongodb://xx.xx.5.58,xx.xx.5.27,xx.xx.5.29,xx.20.5.46,xx.xx.5.47,xx.xx.5.48', port=27017)
dblist03 = mycluster03.list_database_names()
print(dblist03)

mycluster04 = MongoClient(host='xx.xx.5.58,xx.xx.5.27,xx.xx.5.29,xx.20.5.46,xx.xx.5.47,xx.xx.5.48', port=27017)
dblist04 = mycluster04.list_database_names()
print(dblist04)

二叉搜索树的后序遍历序列-牛客网剑指offer

题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

思路
(1)题中首先给出了一数组并要求判断是不是二叉搜索树的后序遍历结果,那么可知,如果他真的时一个二叉搜索树的后序遍历结果,那么对这个数组排序之后得到的一定是这个二叉搜索树的中序遍历结果。
(2)现在就相当于已知了一个二叉搜索树的中序和后序遍历结果。通过先序遍历和后序遍历的特点,此时后序序列的最后一个数一定是这个二叉树的根节点。在中序遍历的结果中找到这个根节点,则中序序列的根节点左右两边的序列分别是这个二叉树的左右子树的中的节点。
(3)而二叉搜索数的根节点一定小于它所有右子树的值,大于所有左子树的值,所以就循环判断一下是否符合,如果不符合这一条件则返回False。如果符合则
递归调用判断函数分别对左右子树进行(2)。

Python代码:

class Solution:
def VerifySquenceOfBST(self, sequence):
# write code here
if len(sequence) == 0: #如果长度为0返回False
return False
pre = sorted(sequence) #排序得到中序
root = sequence[len(sequence) - 1] #找到根节点
cur = 0 #记录根节点在中序中的位置
for count, num in enumerate(pre):
if num == root:
cur = count
break
for num in sequence[:cur]: #左子树中节点的判断
if num > root:
return False
for num in sequence[cur + 1:]: #右子树中节点的判断
if num < root:
return False
flag = True #flag记录左右子树判断的情况
if len(sequence[:cur]) != 0: #序列长度为0不需要判断
flag = self.VerifySquenceOfBST(sequence[:cur])
if len(sequence[cur + 1:] ) != 0:
flag = self.VerifySquenceOfBST(sequence[cur + 1:])
return flag

python使用Thread的setDaemon启动后台线程

什么是线程

线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
为什么要使用多线程

线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄和其他进程应有的状态。

因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性多个线程共享同一个进程的虚拟空间。线程共享的环境包括进程代码段、进程的公有数据等,利用这些共享的数据,线程之间很容易实现通信。

操作系统在创建进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程来实现并发比使用多进程的性能要高得多。

总结起来,使用多线程编程具有如下几个优点:

进程之间不能共享内存,但线程之间共享内存非常容易。
操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此,使用多线程来实现多任务并发执行比使用多进程的效率高。
Python 语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了 Python 的多线程编程。

多线程共享全局变量

线程是进程的执行单元,进程是系统分配资源的最小单位,所以在同一个进程中的多线程是共享资源的。

import threading
import time

g_num = 100

def work1():
global g_num
for i in range(3):
g_num += 1
print("in work1 g_num is : %d" % g_num)

def work2():
global g_num
print("in work2 g_num is : %d" % g_num)

if __name__ == '__main__':
t1 = threading.Thread(target=work1)
t1.start()
time.sleep(1)
t2 = threading.Thread(target=work2)
t2.start()

----------------------------------

>>> in work1 g_num is : 103
>>> in work2 g_num is : 103

在创建新线程时,子线程会从其父线程继承其线程属性,主线程是普通的非守护线程,默认情况下,它所创建的任何线程都是非守护线程。本文将介绍Python中的另一类后台线程,守护线程(Daemon Thread)。

参考:https://zhuanlan.zhihu.com/p/81582297

多线程编程当中, 线程的存在形态比较抽象. 通过前台线程\后台线程, 可以有效理解线程运行顺序.(复杂的多线程程序可以通过设置线程优先级实现)
后台线程与前台线程的直接区别是:
1)setDaemon(True): 当主线程退出时,后台线程随机退出;
2)setDaemon(False)(默认情况): 当主线程退出时,若前台线程还未结束,则等待所有线程结束,相当于在程序末尾加入join().
总结

如果需要子线程随主线程一同退出-设置它为守护线程。
如果需要子线程运行结束后,主线程才能退出-设置子线程为非守护线程。

实例

主线程启动两个子线程:

子线程0-守护线程,运行10秒退出
子线程1-非守护线程,运行1秒退出。

根据我们上面的总结,我们会知道:

主线程启动完子线程,等待所有非守护线程运行
非守护子线程1运行1秒退出
此时没有非守护线程运行,主线程退出
子线程0虽然任务还未完成,但是它是守护线程,会紧跟主线程退出。

import time
import threading

def sub(num):
if i % 2 == 0:
time.sleep(10)
else:
time.sleep(1)
print("thread-{0}: done\n".format(num))

if __name__ == "__main__":
for i in range(2):
t = threading.Thread(target=sub, args=(i, ))
if i % 2 == 0:
t.setDaemon(True)
t.start()
print("thread-{0} started".format(i))
————————————————
版权声明:本文为CSDN博主「Mein_Augenstern」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Maisie_Nan/article/details/103011324

python3自学之路-笔记24—生命周期方法

#!/usr/bin/env python3
# _*_ coding: utf-8 _*_
# File : 生命周期方法.py
# Date : 2019/11/10

class Person:
__personCount = 0 #定义一个类方法,并且私有化,确保安全性。用于计数实例化了多少个对象
def __init__(self): #实力初始化时调用这里的方法
print("计数 +1")
Person.__personCount +=1

def __del__(self):#实力被释放时调用这里的方法
print("计数 -1")
self.__class__.__personCount -=1

@staticmethod #定义一个静态方法
def log():
print(f"当前的人数是{Person.__personCount}")

@classmethod #定义一个类方法
def log1(cls):
print(f"当前的人数是{cls.__personCount}")

Person.personCount = 100 #尝试外部改变类方法(安全性测试)
p = Person()
p2 = Person()
Person.log()
del p
Person.log()
Person.log1()

利用threading模块开线程

一多线程的概念介绍

threading模块介绍

threading模块和multiprocessing模块在使用层面,有很大的相似性。

二、开启多线程的两种方式

1.创建线程的开销比创建进程的开销小,因而创建线程的速度快
from multiprocessing import Process
from threading import Thread
import os
import time
def work():
print('<%s> is running'%os.getpid())
time.sleep(2)
print('<%s> is done'%os.getpid())

if __name__ == '__main__':
t=Thread(target=work,)
# t= Process(target=work,)
t.start()
print('主',os.getpid())

开启进程的第一种方式

from threading import Thread
import time
class Work(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
# time.sleep(2)
print('%s say hell'%self.name)
if __name__ == '__main__':
t = Work('egon')
t.start()
print('主')

开启线程的第二种方式(用类)

在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

from multiprocessing import Process
from threading import Thread
import time
def work():
time.sleep(2)
print('hello')
if __name__ == '__main__':
t = Thread(target=work)#如果等上几秒,他会在开启的过程中先打印主,如果不等会先打印hello
# t = Process(target=work) #子进程会先打印主,
t.start()
print('主')

线程的开启速度大于进程的开启速度

# 2.----------
from multiprocessing import Process
from threading import Thread
import os
def work():
print('hello',os.getpid())
if __name__ == '__main__':
#在主进程下开启多个线程,每个线程都跟主进程的pid一样
t1= Thread(target=work)
t2 = Thread(target=work)
t1.start()
t2.start()
print('主线程pid',os.getpid())

#来多个进程,每个进程都有不同的pid
p1 = Process(target=work)
p2 = Process(target=work)
p1.start()
p2.start()
print('主进程pid', os.getpid())

在同一个进程下开多个进程和开多个线程的pid的不同

from threading import Thread
from multiprocessing import Process
import os
def work():
global n
n-=1
print(n) #所以被改成99了
n = 100
if __name__ == '__main__':
# p = Process(target=work)
p = Thread(target=work) #当开启的是线程的时候,因为同一进程内的线程之间共享进程内的数据
#所以打印的n为99
p.start()
p.join()
print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,
# 但改的仅仅是它自己的,查看父进程的n仍然为100

同一进程内的线程共享该进程的数据

进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据)

三、练习

练习一:多线程实现并发

from socket import *
from threading import Thread
s = socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口重用
s.bind(('127.0.0.1',8081))
s.listen(5)
print('start running...')
def talk(coon,addr):
while True: # 通信循环
try:
cmd = coon.recv(1024)
print(cmd.decode('utf-8'))
if not cmd: break
coon.send(cmd.upper())
print('发送的是%s'%cmd.upper().decode('utf-8'))
except Exception:
break
coon.close()
if __name__ == '__main__':
while True:#链接循环
coon,addr = s.accept()
print(coon,addr)
p =Thread(target=talk,args=(coon,addr))
p.start()
s.close()

服务端

from socket import *
c = socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))
while True:
cmd = input('>>:').strip()
if not cmd:continue
c.send(cmd.encode('utf-8'))
data = c.recv(1024)
print('接受的是%s'%data.decode('utf-8'))
c.close()

客户端

练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

from threading import Thread
import os
input_l = []
format_l = []
def talk(): #监听输入任务
while True:
cmd = input('>>:').strip()
if not cmd:continue
input_l.append(cmd)

def format():
while True:
if input_l:
res = input_l.pop()#取出来
format_l.append(res.upper()) #取出来后变大写
def save():
while True:
if format_l: #如果format_l不为空
with open('db','a') as f:
f.write(format_l.pop()+'\n') #写进文件
f.flush()
if __name__ == '__main__':
t1=Thread(target=talk)
t2=Thread(target=format)
t3=Thread(target=save)
t1.start()
t2.start()
t3.start()

答案

四、多线程共享同一个进程内的地址空间

from threading import Thread
from multiprocessing import Process
import os
n = 100
def talk():
global n
n-=100
print(n)
if __name__ == '__main__':
t = Thread(target=talk) #如果开启的是线程的话,n=0
# t = Process(target=talk) #如果开启的是进程的话,n=100
t.start()
t.join()
print('主',n)

五、线程对象的其他属性和方法

Thread实例对象的方法
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。

threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

from threading import Thread
from multiprocessing import Process
import time,os,threading
def work():
time.sleep(2)
print('%s is running' % threading.currentThread().getName())
print(threading.current_thread()) #其他线程
print(threading.currentThread().getName()) #得到其他线程的名字
if __name__ == '__main__':
t = Thread(target=work)
t.start()

print(threading.current_thread().getName()) #主线程的名字
print(threading.current_thread()) #主线程
print(threading.enumerate()) #连同主线程在内有两个运行的线程
time.sleep(2)
print(t.is_alive()) #判断线程是否存活
print(threading.activeCount())
print('主')

线程的其他属性和方法

六、join与守护线程

主进程等所有的非守护的子进程结束他才结束(回收它子进程的资源):(有父子关系)
主线程等非守护线程全都结束它才结束: (没父子关系)

from threading import Thread
import time,os
def talk():
time.sleep(3)
print('%s is running..'%os.getpid())
if __name__ == '__main__':
t = Thread(target=talk)
t.start()
t.join() #主进程在等子进程结束
print('主')

join

守护线程与守护进程的区别

1.守护进程:主进程会等到所有的非守护进程结束,才销毁守护进程。也就是说(主进程运行完了被守护的那个就干掉了)

2.守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束

from multiprocessing import Process
from threading import Thread,currentThread
import time,os
def talk1():
time.sleep(2)
print('hello')
def talk2():
time.sleep(2)
print('you see see')
if __name__ == '__main__':
t1 = Thread(target=talk1)
t2 = Thread(target=talk2)
# t1 = Process(target=talk1)
# t2 = Process(target=talk2)
t1.daemon = True
t1.start()
t2.start()
print('主线程',os.getpid())

守护进程和守护线程

#3 --------迷惑人的例子
from threading import Thread
import time
def foo():
print(123)
# time.sleep(10) #如果这个等的时间大于下面等的时间,就把不打印end123了
time.sleep(2) #如果这个等的时间小于下面等的时间,就把end123也打印了
print('end123')
def bar():
print(456)
# time.sleep(5)
time.sleep(10)
print('end456')
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True #主线程运行完了守护的那个还没有干掉,
# 主线程等非守护线程全都结束它才结束
t1.start()
t2.start()
print('main---------')

一个诱惑人的例子

七、GIL与Lock

1.python GIL(Global Interpreter Lock) #全局的解释器锁

2.锁的目的:牺牲了效率,保证了数据的安全
3.保护不同的数据加不同的锁()
4.python自带垃圾回收

5.谁拿到GIL锁就让谁得到Cpython解释器的执行权限

6.GIT锁保护的是Cpython解释器数据的安全,而不会保护你自己程序的数据的安全
7.GIL锁当遇到阻塞的时候,就被迫的吧锁给释放了,那么其他的就开始抢锁了,抢到
后吧值修改了,但是第一个拿到的还在原本拿到的那个数据的那停留着呢,当再次拿
到锁的时候,数据已经修改了,而你还拿的原来的,这样就混乱了,所以也就保证不了
数据的安全了。
8.那么怎么解决数据的安全ne ?
自己再给加吧锁:mutex=Lock()

同步锁

GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果

既然是串行,那我们执行

t1.start()

t1.join

t2.start()

t2.join()

这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。

因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。

from threading import Thread,Lock
import time
n=100
def work():
mutex.acquire()
global n
temp=n
time.sleep(0.01)
n=temp-1
mutex.release()
if __name__ == '__main__':
mutex=Lock()
t_l=[]
s=time.time()
for i in range(100):
t=Thread(target=work)
t_l.append(t)
t.start()
for t in t_l:
t.join()
print('%s:%s' %(time.time()-s,n))

全局解释锁

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

import threading
mutex = threading.Lock()
mutex.aquire()
'''
对公共数据的操作
'''
mutex.release()

锁的格式

1 分析:
2 2   1.100个线程去抢GIL锁,即抢执行权限
3 3 2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
4 4 3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
5 5 4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
如果不加锁:并发执行,速度快,数据不安全。

加锁:串行执行,速度慢,数据安全。

#不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os,time
def task():
global n
print('%s is running' %current_thread().getName())
temp=n
time.sleep(0.5)
n=temp-1

if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()

stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:0.5216062068939209 n:99
'''

#不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
from threading import current_thread,Thread,Lock
import os,time
def task():
#未加锁的代码并发运行
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
#加锁的代码串行运行
lock.acquire()
temp=n
time.sleep(0.5)
n=temp-1
lock.release()

if __name__ == '__main__':
n=100
lock=Lock()
threads=[]
start_time=time.time()
for i in range(100):
t=Thread(target=task)
threads.append(t)
t.start()
for t in threads:
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 is running
Thread-2 is running
......
Thread-100 is running
主:53.294203758239746 n:0
'''

#有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
#没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
#start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
#单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
from threading import current_thread,Thread,Lock
import os,time
def task():
time.sleep(3)
print('%s start to run' %current_thread().getName())
global n
temp=n
time.sleep(0.5)
n=temp-1

if __name__ == '__main__':
n=100
lock=Lock()
start_time=time.time()
for i in range(100):
t=Thread(target=task)
t.start()
t.join()
stop_time=time.time()
print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''

互斥锁与join的区别(重点!!!)