python教程

2022-05-20 01:00:44 浏览数 (1)

1 相关链接

1.1 学习链接

https://github.com/jackfrued/Python-100-Days

python中68个内置函数的总结

内置函数 - Python 3.10.2 文档

https://docs.python.org/zh-cn/3.6/library/index.html

Python 3 官方教程

2 用法总结

多进程和多线程

参考:

https://docs.python.org/zh-cn/3.6/library/multiprocessing.html

https://docs.python.org/zh-cn/3.6/library/threading.html

下面用一个下载文件的例子来说明使用多进程和不使用多进程到底有什么差别,先看看下面的代码。

代码语言:javascript复制
from random import randint
from time import time, sleep


def download_task(filename):
    print('开始下载%s...' % filename)
    time_to_download = randint(5, 10)
    sleep(time_to_download)
    print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))


def main():
    start = time()
    download_task('Python从入门到住院.pdf')
    download_task('Peking Hot.avi')
    end = time()
    print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
    main()

下面是运行程序得到的一次运行结果。

代码语言:javascript复制
开始下载Python从入门到住院.pdf...
Python从入门到住院.pdf下载完成! 耗费了6秒
开始下载Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了7秒
总共耗费了13.01秒.

从上面的例子可以看出,如果程序中的代码只能按顺序一点点的往下执行,那么即使执行两个毫不相关的下载任务,也需要先等待一个文件下载完成后才能开始下一个下载任务,很显然这并不合理也没有效率。接下来我们使用多进程的方式将两个下载任务放到不同的进程中,代码如下所示。

代码语言:javascript复制
from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleep


def download_task(filename):
    print('启动下载进程,进程号[%d].' % getpid())
    print('开始下载%s...' % filename)
    time_to_download = randint(5, 10)
    sleep(time_to_download)
    print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))


def main():
    start = time()
    p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))
    p1.start()
    p2 = Process(target=download_task, args=('Peking Hot.avi', ))
    p2.start()
    p1.join()
    p2.join()
    end = time()
    print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
    main()

在上面的代码中,我们通过Process类创建了进程对象,通过target参数我们传入一个函数来表示进程启动后要执行的代码,后面的args是一个元组,它代表了传递给函数的参数。Process对象的start方法用来启动进程,而join方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。下面是程序的一次执行结果。

总结,多进程的功能是给两个互不关联的程序提供并行运行的能力。

代码语言:javascript复制
from random import randint
from threading import Thread
from time import time, sleep


class DownloadTask(Thread):

    def __init__(self, filename):
        super().__init__()
        self._filename = filename

    def run(self):
        print('开始下载%s...' % self._filename)
        time_to_download = randint(5, 10)
        sleep(time_to_download)
        print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))


def main():
    start = time()
    t1 = DownloadTask('Python从入门到住院.pdf')
    t1.start()
    t2 = DownloadTask('Peking Hot.avi')
    t2.start()
    t1.join()
    t2.join()
    end = time()
    print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
    main()

因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。

代码语言:javascript复制
from time import sleep
from threading import Thread


class Account(object):

    def __init__(self):
        self._balance = 0

    def deposit(self, money):
        # 计算存款后的余额
        new_balance = self._balance   money
        # 模拟受理存款业务需要0.01秒的时间
        sleep(0.01)
        # 修改账户余额
        self._balance = new_balance

    @property
    def balance(self):
        return self._balance


class AddMoneyThread(Thread):

    def __init__(self, account, money):
        super().__init__()
        self._account = account
        self._money = money

    def run(self):
        self._account.deposit(self._money)


def main():
    account = Account()
    threads = []
    # 创建100个存款的线程向同一个账户中存钱
    for _ in range(100):
        t = AddMoneyThread(account, 1)
        threads.append(t)
        t.start()
    # 等所有存款的线程都执行完毕
    for t in threads:
        t.join()
    print('账户余额为: ¥%d元' % account.balance)


if __name__ == '__main__':
    main()

运行上面的程序,结果让人大跌眼镜,100个线程分别向账户中转入1元钱,结果居然远远小于100元。之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到new_balance = self._balance money这行代码,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了 1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。下面的代码演示了如何使用“锁”来保护对银行账户的操作,从而获得正确的结果。

代码语言:javascript复制
from time import sleep
from threading import Thread, Lock


class Account(object):

    def __init__(self):
        self._balance = 0
        self._lock = Lock()

    def deposit(self, money):
        # 先获取锁才能执行后续的代码
        self._lock.acquire()
        try:
            new_balance = self._balance   money
            sleep(0.01)
            self._balance = new_balance
        finally:
            # 在finally中执行释放锁的操作保证正常异常锁都能释放
            self._lock.release()

    @property
    def balance(self):
        return self._balance


class AddMoneyThread(Thread):

    def __init__(self, account, money):
        super().__init__()
        self._account = account
        self._money = money

    def run(self):
        self._account.deposit(self._money)


def main():
    account = Account()
    threads = []
    for _ in range(100):
        t = AddMoneyThread(account, 1)
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    print('账户余额为: ¥%d元' % account.balance)


if __name__ == '__main__':
    main()

比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。

多由于多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。因此我们要添加一个锁机制,控制线程对临界资源的使用。Python的多线程并不能发挥CPU的多核特性,因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,

什么时候使用多进程,什么时候使用多线程呢?

计算密集型    

计算密集型,顾名思义就是应用需要非常多的CPU计算资源,在计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。

IO密集型

    对于IO密集型的应用,涉及到网络、磁盘IO的任务都是IO密集型任务,大多消耗都是硬盘读写和网络传输的消耗。

那么GIL多线程的不足,其实是对于计算密集型的不足,这个解决可以利用多进程进行解决,而对于IO密集型的任务,我们还是可以使用多多线程进行提升效率。

多线程和多进程用法总结

1、Pool 对象,它提供了一种方便的方法,可以跨多个输入值并行化函数的执行,跨进程分配输入数据(数据并行)。以下示例演示了在模块中定义此类函数的常见做法,以便子进程可以成功导入该模块。这个数据并行的基本例子使用 Pool

代码语言:javascript复制
from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3]))

2、Process

multiprocessing 中,通过创建一个 Process 对象然后调用它的 start() 方法来生成进程。 Processthreading.Thread API 相同。 一个简单的多进程程序示例是:

代码语言:javascript复制
from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

使用的时候,一定要同时运行多个Process,才能发挥此函数的作用。

3 threading

用法如下:

代码语言:javascript复制
from random import randint
from threading import Thread
from time import time, sleep


class DownloadTask(Thread):

    def __init__(self, filename):
        super().__init__()
        self._filename = filename

    def run(self):
        print('开始下载%s...' % self._filename)
        time_to_download = randint(5, 10)
        sleep(time_to_download)
        print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))


def main():
    start = time()
    t1 = DownloadTask('Python从入门到住院.pdf')
    t1.start()
    t2 = DownloadTask('Peking Hot.avi')
    t2.start()
    t1.join()
    t2.join()
    end = time()
    print('总共耗费了%.2f秒.' % (end - start))


if __name__ == '__main__':
    main()

退出执行

quit(code=None)exit(code=None)

使用场景:当需要程序自动退出时,使用该方法。

使用方法:当打印此对象时,会打印出一条消息,例如“Use quit() or Ctrl-D (i.e. EOF) to exit”,当调用此对象时,将使用指定的退出代码来引发 SystemExit。

代码语言:javascript复制
a = input("男朋友的态度:")
if a == "我错了":
    print("哪次不是你的错")
    exit()
else:
    while a != "我错了":
        print("你变了,你不爱我了")

可变数据类型和不可变数据类型

  • 不可变数据类型:数值类型(int、float、bool)、string(字符串)、tuple(元组)
  • 可变数据类型:list(列表)、dict(字典)
  • 可变数据类型更改值后,内存地址不发生改变;不可变数据类型更改值后,内存地址发生改变

内置函数

  • 数据类型相关:bool, float, int
  • 进制变换:bin, otc, hex(二进制、8进制、16进制)
  • 数学运算:abs, divmode(返回商和余), round(四舍五入), pow(幂), sum, min, max
  • 与数据结构相关:list, tuple, reversed, slice, str, bytes, bytearray, ord, chr, sorted,len,enumerate,all,any,zip,fiter,map,
  • 集合相关:dict, set, frozenset() 创建一个冻结的集合. 冻结的集合不能进行添加和删除操作
  • 与作用域相关:locals,globals
  • 与迭代器、生成器相关:range, iter, next
  • 字符串执行:eval, exec,compile
  • 输入、输出:print, input,format
  • 内存相关:hash
  • 文件操作相关:open
  • 模块相关:__ import__() 用于动态加载类和函数
  • 帮助:help
  • 调用相关:callable
  • 查看内置属性:dir

_, __ 和 __xx__的区别

沙师弟学Python之 _, __ 和 __xx__的区别

open()

用法:

代码语言:javascript复制
file_name = "/%s/hello.txt/"
date = "2022-05-12"
a = dict()
with open(file_name % date, 'r') as fin:
    for line in fin:
        (c, d, e) = line.strip().split('t')
        a[c] = d

data = {}
with open(file_name, 'w') as fout:
    for line in data:
        oid = str(line['oid'])
        freq_list = str(line['freq_list'])
        fout.write("%st%sn" % (oid, freq_list))

相关参数:

代码语言:javascript复制
 1 r:    以只读方式打开文件。文件的指针将会放在文件的开头。这是**默认模式**。
 2 rb: 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
 3 r : 打开一个文件用于读写。文件指针将会放在文件的开头。
 4 rb :以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
 5 w:    打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
 6 wb:    以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
 7 w :    打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
 8 wb :以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
 9 a:    打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
10 ab:    以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
11 a :    打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
12 ab :以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。参考文献

zip()

用途:在多个迭代器上并行迭代,从每个迭代器返回一个数据项组成元组。特色用法:实现行列互换

用法:

代码语言:javascript复制
for item in zip([1, 2, 3], ['sugar', 'spice', 'everything nice']):
    print(item)
result:
[(1, 'sugar')]
[(2, 'spice')]
[(3, 'everything nice')]
a = list(zip(range(3), ['fee', 'fi', 'fo', 'fum']))
print(a)
result:
[(0, 'fee'), (1, 'fi'), (2, 'fo')]
a = [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
b = list(zip(*a))
print(b)
result:
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

0 人点赞