Python的学习之路

发布于:2024-04-09 ⋅ 阅读:(61) ⋅ 点赞:(0)

一、Python基础

1、编码

Python2中默认编码为ASCII,Python3中默认编码为UTF-8,如需指定编码则在开头加入# -*- coding: UTF-8 -*- 进行指定。

2、标识符

由字母、数字、下划线组成,首字符必须为字母或下划线;在Python3中,可以用中文作为变量名,非ASCII标识符也是允许的。

3、关键字

Python的标准库提供了一个keyword模块,可以输出当前版本所有关键字,如:

>>> import keyword

>>> keyword.kwlist

4、注释

单行注释以#开头,多行注释以'''或"""包裹。

5、缩进

Python是使用缩进来表示代码块,同一代码块缩进相同,不需要使用{},如:

if True:
    print ("Answer")
    print ("True")
else:
    print ("Answer")

6、语句

Python中通常时一行写完一条语句,无需分号,如果语句太长,可以使用反斜杠'\'连接,如:

total = item_one + \
           item_two + \
           item_three

在 [], {}, 或 () 中的多行语句,不需要使用反斜杠(\),如:

total = ['item_one', 'item_two', 'item_three',
           'item_four', 'item_five']

Python也可以在同一行中使用多条语句,语句之间使用分号分割,如:

import sys; x = 'runoob'; sys.stdout.write(x + '\n')

在Python中,if __name__ == '__main__': 是一个常见的结构,用于检查当前模块是否作为主程序运行。这个结构对于理解Python的模块和脚本运行方式非常重要。

__name__ : 这是一个内置变量,它表示当前模块的名字。当一个Python文件(通常称为模块)被直接运行时,__name__ 的值会被设置为 '__main__'。但是,如果该文件被导入为另一个模块的一部分,__name__ 的值就会被设置为该模块的名字。这个条件语句的意思是:“如果当前模块是作为主程序(而不是被其他模块导入)运行的,那么执行以下的代码块。”

使用这种结构的主要好处是,它允许一个Python文件既可以作为脚本直接运行,也可以作为模块被其他脚本导入,而无需修改其内容。当文件被导入时,if __name__ == '__main__': 下面的代码块不会被执行,这使得你可以在文件中定义函数、类等,而不用担心它们会在导入时被执行。

示例:

假设你有一个名为 my_module.py 的文件,内容如下:

def my_function():  
    print("This is a function in my_module.")  
  
if __name__ == '__main__':  
    print("my_module is being run directly.")  
    my_function()
  • 如果你直接运行 my_module.py(例如,通过命令行输入 python my_module.py),输出将是:
my_module is being run directly.  
This is a function in my_module.
  • 如果你在另一个Python文件中导入 my_module,例如:import my_module

那么不会有任何输出,因为 if __name__ == '__main__': 下的代码块不会被执行。但是,你仍然可以在那个文件中调用 my_module.my_function()

这种结构使得Python代码更加模块化和可重用。

7、字符串

单行字符串以单引号或双引号包裹,多行字符串以三引号包裹,如:

str1 = 'hello'

str2 = "world"

str3 = '''hello,

           world'''

字符串中使用反斜杠'\'可用来转义,如'\n'可转义为换行,但使用r可以让反斜杠不发生转义,如:r"this is a line with \n" 此时\n会显示,并不会换行。

字符串可以用+连接,用*重复,如:

str1 = 'hello' + "world"

str2 = "world" * 3

字符串截取的语法格式:变量[头下标:尾下标:步长]

8、导入

在python用import或者from...import来导入相应的模块;

如将整个模块导入,格式为:import  somemodule

如导入模块中的某个函数,格式为:from somemodule import somefunction

如导入模块中的多个函数,格式为:from somemodule import firstfunc, secondfunc

如导入模块中的所有函数,格式为:from somemodule import * 

9、函数

Python中使用def关键字来声明函数,格式如下:

def 函数名(参数):

        函数体

        return 返回值

当我们不确定参数个数时,可以使用不定长参数,在参数名前加*声明,格式如下:

def 函数名(*参数):

        函数体

我们还可以使用lambda定义匿名函数,格式如下:

lambda 参数 : 表达式

# 空函数
def my_empty():
    pass

# 无返回值
def my_print(name):
    print('Hello', name)

# 有返回值
def my_sum(x, y):
    s = x + y
    print('s-->', s)
    return s
    
# 不定长参数
def my_variable(*params):
    for p in params:
        print(p)

# 匿名函数
my_sub = lambda x, y: x - y


# 调用
my_empty()
my_print('Jhon')
result = my_sum(1, 2)
my_variable(1, 2, 3, 4, 5, 6)
print(my_sub(2, 1))

10、字典

Python中的数据结构字典(dict),它的内容以键值对的形式存在,dict拥有良好的查询速度,其中的值可以是任意Python对象。

创建字典方式如下:

d = {'name':'小明', 'age':'18'}

# 使用 dict 函数
# 方式一
l = [('name', '小明'), ('age', 18)]
d = dict(l)
# 方式二
d = dict(name='小明', age='18')

# 空字典
d = dict()
d = {}

访问字典方式如下:

>>> d = dict(name='小明', age='18')
>>> d['name']
'小明'

# 使用 get 方法
>>> d.get('name')
'小明'

修改字典方式如下:

>>> d = dict(name='小明', age='18')
>>> d['age'] = '20'
>>> d['age']
'20'

11、集合

集合(set)中的值不可重复,且无序。添加元素可以使用add或update方法,如果元素已经存在,则不进行操作。删除元素用remove方法。

创建集合方式如下:

s = {'a', 'b', 'c'}

# 使用 set 函数
s = set(['a', 'b', 'c'])

# 空集合
s = set()


>>> s = {'a', 'b', 'c'}
>>> s.add('d')
>>> s
{'a', 'd', 'c', 'b'}
>>> s.update('e')
>>> s
{'a', 'b', 'e', 'd', 'c'}
# 添加已经存在的元素 a
>>> s.add('a')
>>> s
{'a', 'b', 'e', 'd', 'c'}


#删除元素
>>> s = {'a', 'b', 'c'}
>>> s.remove('c')
>>> s
{'a', 'b'}

12、类

Python中类的定义使用class关键字,语法格式为:​ 

class 类名:	属性	...	方法	...
#定义一个Cat类
class Cat:
	# 属性
    color = 'black'
    # 构造方法
    def __init__(self, name):
        self.name = name
    # 自定义方法
    def eat(self, food):
        self.food = food
        print(self.name, '正在吃'+food)

构造方法__init__()会在类实例化时自动调用,无论构造方法还是其他方法都需要将self作为第一个参数,它代表类的实例。

类中定义的属性和方法默认都是公开的,如果不想外部访问和调用,则需要定义成私有属性和方法,声明时在属性名和方法名前加两条下划线即可,如下:

class Cat:
    __cid = '1'
    def __run(self):
        pass

创建对象也称类的实例化,创建对象后可以访问属性和方法调用了,如下:

 # 创建对象
c = Cat('Tom')


# 访问属性
print('name-->', c.name)
print('color-->', c.color)
# 调用方法
c.eat('鱼')

Python支持类的继承,而且支持多继承,语法格式为:

class 基类(子类1, 子类2 ...):	...

 示例代码:

# 波斯猫类
class PersianCat(Cat):
    def __init__(self, name):
        self.name = name
    def eat(self, food):
        print(self.name, '正在吃'+food)
#加菲猫类
class GarfieldCat(Cat):
    def __init__(self, name):
        self.name = name
    def run(self, speed):
        print(self.name, '正在以'+speed+'的速度奔跑')
# 单继承
class SingleCat(PersianCat):
    pass
# 多继承
class MultiCat(PersianCat, GarfieldCat):
    pass

#调用
sc = SingleCat('波斯猫1号')
sc.eat('鱼')

mc = MultiCat('波斯加菲猫1号')
mc.eat('鱼')
mc.run('50迈')

如果继承的父类方法不能满足我们的要求,这时子类可以重写父类方法,如下:

class SingleCat(PersianCat):
    def eat(self, food ):
        print(self.name, '正在吃'+food, '十分钟后', self.name+'吃饱了')
sc = SingleCat('波斯猫1号')
sc.eat('鱼')

13、文件基本操作

利用Python中内置的一些函数实现文件的基本操作:创建、打开、读、写、关闭。话不多说,直接上代码:

# 打开或创建文件
f = open('test.txt', 'w', encoding='utf-8')
# 写入一行
f.write('Tom\n')
# 写入多行
f.writelines(['Hello\n', 'Python'])
# 文件对象位置
print(f.tell())
# 移动到文件的第四个字节
f.seek(3)
# 读取一行
f.readline()
# 读取指定字节数
f.read(6)
# 读取所有行
f.readlines()
# 关闭文件
f.close()

创建或打开文件:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
# file:表示将要打开的文件的路径,也可以是要被封装的整数类型文件描述符。
# mode:用于指定打开文件的模式。
        r    读取(默认)
        w    写入,并先截断文件
        x    排它性创建,如果文件已存在则失败
        a    写入,如果文件存在则在末尾追加
        b    二进制模式
        t    文本模式(默认)
        +    更新磁盘文件(读取并写入)
# buffering:是一个可选的整数,用于设置缓冲策略。
# encoding:用于解码或编码文件的编码的名称。
# errors:用于指定如何处理编码和解码错误(不能在二进制模式下使用)。
# newline:区分换行符。
# closefd:如果 closefd 为 False 并且给出了文件描述符而不是文件名,那么当文件关闭时,底层文件描述符将保持打开状态;如果给出文件名,closefd 为 True (默认值),否则将引发错误。
# opener:可以通过传递可调用的 opener 来使用自定义开启器。

上面我们使用了 close() 函数进行关闭操作,如果忘记了关闭,可能会对程序造成一些隐患,为了避免这个问题的出现,可以使用 with as 语句,通过这种方式,程序执行完成后会自动关闭已经打开的文件。如:
with open('test.txt', 'w', encoding='utf-8') as wf:
    wf.write('Tom\n')
    wf.writelines(['Hello\n', 'Python'])

在Python中,with as语句用于确保一段代码执行完毕时,可以正确地清理所使用的资源。这通常用于管理文件、网络连接、数据库连接、线程锁等资源的生命周期。使用with语句可以确保资源在使用完毕后被正确关闭或释放,即使在代码块执行过程中发生异常也是如此。

with expression [as variable]:  
    with-block

其中,expression必须返回一个上下文管理对象,该对象必须实现__enter__()__exit__()两个方法。当with语句开始时,__enter__()方法被调用,其返回值(如果有的话)会被赋值给as关键字后面的变量。当with语句块执行完毕(无论正常结束还是由于异常退出)时,__exit__()方法被调用。

二、Python进阶

1、多线程

Python解释器的作用是将.py文件中的代码翻译后交给机器去执行,常见解释器有:CPython、Jython、IronPython、IPython、PyPy,而CPython是官方解释器,通过GIL(全局解释器锁)机制来确保同一时间只有一个线程来执行Python代码,解决并发访问的线程安全问题,但却牺牲了多处理器上的并行性,所有CPython解释器上的多线程并不是真正意义上的多线程。

Python(CPython)提供了_thread和_threading两个线程模块,_threading是对_thread进行了封装,这里只介绍_threading模块。

守护线程:当一个线程被设置为守护线程时,程序会在所有线程都为守护线程时退出,资源可能不被正确释放。

非守护线程:默认创建的线程为非守护线程,程序退出时会等待所有非守护线程运行完毕。

常用方法如下:

threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

创建线程对象,参数说明如下所示。

  • group:通常默认即可,作为日后扩展 ThreadGroup 类实现而保留。

  • target:用于 run() 方法调用的可调用对象,默认为 None。

  • name:线程名称,默认是 Thread-N 格式构成的唯一名称,其中 N 是十进制数。

  • args:用于调用目标函数的参数元组,默认为 ()。

  • kwargs:用于调用目标函数的关键字参数字典,默认为 {}。

  • daemon:设置线程是否为守护模式,默认为 None。

start()

启动线程。

run()

线程执行具体功能的方法。

join(timeout=None)

当 timeout 为 None 时,会等待至线程结束;当 timeout 不为 None 时,会等待至 timeout 时间结束,单位为秒。

is_alive()

判断线程是否存活。

getName()

返回线程名。

setName()

设置线程名。

isDaemon()

判断线程是否为守护线程。

setDaemon()

设置线程是否为守护线程。

import threading
import time

def target(sleep):
    time.sleep(sleep)
    print('当前线程为:', threading.current_thread().name,' ', 'sleep:', sleep)

if __name__ == '__main__':
    t1 = threading.Thread(name='t1', target=target, args=(1,))
    t2 = threading.Thread(name='t2', target=target, args=(2,))
    t1.start()
    t2.start()
    print('主线程结束')



#继承 threading.Thread

class MyThread(threading.Thread):
    def __init__(self, sleep, name):
        super().__init__()
        self.sleep = sleep
        self.name = name
    def run(self):
        time.sleep(self.sleep)
        print('name:' + self.name)

if __name__ == '__main__':
    t1 = MyThread(1, 't1')
    t2 = MyThread(1, 't2')
    t1.start()
    t2.start()

互斥锁:

多线程访问共享资源时会存在线程安全问题,此时就需要互斥锁来控制每一个线程访问顺序。

threading.Lock

实现原始锁对象的类,一旦一个线程获得一个锁,会阻塞随后尝试获得锁的线程,直到它被释放,通常称其为互斥锁,它是由 _thread 模块直接扩展实现的。它具有如下方法:

  • acquire(blocking=True, timeout=-1):可以阻塞或非阻塞地获得锁,参数 blocking 用来设置是否阻塞,timeout 用来设置阻塞时间,当 blocking 为 False 时 timeout 将被忽略。

  • release():释放锁。

  • locked():判断是否获得了锁,如果获得了锁则返回 True。

threading.RLock

可重入锁(也称递归锁)类,一旦线程获得了重入锁,同一个线程再次获取它将不阻塞,重入锁必须由获取它的线程释放。它具有如下方法:

  • acquire(blocking=True, timeout=-1):解释同上。

  • release():解释同上。

我们对上述代码进行加锁操作,如下所示:

import threading

# 创建锁
lock = threading.Lock()

a = 5
def oper(b):
    # 获取锁
    lock.acquire()
    global a
    a = a - b
    a = a + b
    # 释放锁
    lock.release()

def target(b):
    for i in range(100000):
        oper(b)

if __name__ == '__main__':
    m = 5
    while m > 0:
        t1 = threading.Thread(target=target, args=(1,))
        t2 = threading.Thread(target=target, args=(2,))
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        print(a)
        m = m - 1

 条件对象:

条件对象总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建。

threading.Condition(lock=None)

实现条件对象的类。它具有如下方法:

  • acquire(*args):请求底层锁。

  • release():释放底层锁。

  • wait(timeout=None):等待直到被通知或发生超时。

  • wait_for(predicate, timeout=None):等待直到条件计算为 True,predicate 是一个可调用对象且它的返回值可被解释为一个布尔值。

  • notify(n=1):默认唤醒一个等待该条件的线程。

  • notify_all():唤醒所有正在等待该条件的线程。

使用条件对象的典型场景是将锁用于同步某些共享状态的权限,那些关注某些特定状态改变的线程重复调用 wait() 方法,直到所期望的改变发生;对于修改状态的线程,它们将当前状态改变为可能是等待者所期待的新状态后,调用 notify() 方法或者 notify_all() 方法。

import time
import threading

# 创建条件对象
c = threading.Condition()
privilege = 0

def getPri():
    global privilege
    c.acquire()
    c.wait()
    print(privilege)
    c.release()

def updPri():
    time.sleep(5)
    c.acquire()
    global privilege
    privilege = 1
    c.notify()
    c.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=getPri)
    t2 = threading.Thread(target=updPri)
    t1.start()
    t2.start()

信号量对象:

和锁机制一样,信号量机制也是一种实现线程同步的机制,不过它比锁多了一个计数器,这个计数器主要用来计算当前剩余的锁的数量。

threading.Semaphore(value=1)

信号量实现类,可选参数 value 赋予内部计数器初始值,默认值为 1 。它具有如下方法:

  • acquire(blocking=True, timeout=None):获取一个信号量,参数 blocking 用来设置是否阻塞,timeout 用来设置阻塞时间。

  • release():释放一个信号量,将内部计数器的值增加1。

import threading

# 创建信号量对象
s = threading.Semaphore(10)

a = 5
def oper(b):
    # 获取信号量
    s.acquire()
    global a
    a = a - b
    a = a + b
    # 释放信号量
    s.release()

def target(b):
    for i in range(100000):
        oper(b)

if __name__ == '__main__':
    m = 5
    while m > 0:
        t1 = threading.Thread(target=target, args=(1,))
        t2 = threading.Thread(target=target, args=(2,))
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        print(a)
        m = m - 1

 事件对象:

一个线程发出事件信号,其他线程等待该信号,这是最简单的线程之间通信机制之一。

threading.Event

实现事件对象的类。它有如下方法:

  • is_set():当内部标志为 True 时返回 True。

  • set():将内部标志设置为 True。

  • clear():将内部标志设置为 False。

  • wait(timeout=None):阻塞线程直到内部变量为 True。

import time
import threading

# 创建事件对象
event = threading.Event()

def dis_class():
    time.sleep(5)
    event.wait()
    print('同学们下课了')


def bell():
    time.sleep(3)
    print('下课铃声响了')
    event.set()

if __name__ == '__main__':
    t1 = threading.Thread(target=bell)
    t2 = threading.Thread(target=dis_class)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

2、多进程

进程:通常一个运行着的应用程序就是一个进程,比如:我启动了一个音乐播放器,现在它就是一个进程。线程:线程是进程的最小执行单元,比如:我在刚启动的音乐播放器上选了一首歌曲进行播放,这就是一个线程。

多线程一文中,我们说了因为 GIL 的原因,CPython 解释器下的多线程牺牲了并行性,为此 Python 提供了多进程模块 multiprocessing,该模块同时提供了本地和远程并发,使用子进程代替线程,可以有效的避免 GIL 带来的影响,能够充分发挥机器上的多核优势,可以实现真正的并行效果,并且它与threading模块的 API 基本类似,使用起来也比较方便。

Process类:

multiprocessing模块通过创建一个Process对象,然后调用它的start()方法来生成进程,

multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

  • group:仅用于兼容 threading.Thread,应该始终是 None。

  • target:由 run() 方法调用的可调用对象。

  • name:进程名。

  • args:目标调用的参数元组。

  • kwargs:目标调用的关键字参数字典。

  • daemon:设置进程是否为守护进程,如果是默认值 None,则该标志将从创建的进程继承。

multiprocessing.Process 对象具有如下方法和属性:

  • run():进程具体执行的方法。

  • start():启动进程。

  • join([timeout]):如果可选参数 timeout 是默认值 None,则将阻塞至调用 join() 方法的进程终止;如果 timeout 是一个正数,则最多会阻塞 timeout 秒。

  • name:进程的名称。

  • is_alive():返回进程是否还活着。

  • daemon:进程的守护标志,是一个布尔值。

  • pid:返回进程 ID。

  • exitcode:子进程的退出代码。

  • authkey:进程的身份验证密钥。

  • sentinel:系统对象的数字句柄,当进程结束时将变为 ready。

  • terminate():终止进程。

  • kill():与 terminate() 相同,但在 Unix 上使用 SIGKILL 信号。

  • close():关闭 Process 对象,释放与之关联的所有资源。

看一个使用多进程的示例:


from multiprocessing import Process
import time, os

def target():
    time.sleep(2)
    print ('子进程ID:', os.getpid())

if __name__=='__main__':
    print ('主进程ID:', os.getpid())
    ps = []
    for i in range(10):
        p = Process(target=target)
        p.start()
        ps.append(p)
    for p in ps:
        p.join()

 进程池:

当进程数量比较多时,我们可以利用进程池方便、高效的对进程进行使用和管理。

multiprocessing.pool.Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])

  • processes:工作进程数目,如果 processes 为 None,则使用 os.cpu_count() 返回的值。

  • initializer:如果 initializer 不为 None,则每个工作进程将会在启动时调用 initializer(*initargs)。

  • maxtasksperchild:一个工作进程在它退出或被一个新的工作进程代替之前能完成的任务数量,为了释放未使用的资源。

  • context:用于指定启动的工作进程的上下文。

有如下两种方式向进程池提交任务:

  • apply(func[, args[, kwds]]):阻塞方式。

  • apply_async(func[, args[, kwds[, callback[, error_callback]]]]):非阻塞方式。

import multiprocessing, time

def target(p):
    print('t')
    time.sleep(2)
    print(p)

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes = 5)
    for i in range(3):
        p = 'p%d'%(i)
        # 阻塞式
        pool.apply(target, (p, ))
        # 非阻塞式
        # pool.apply_async(target, (p, ))
    pool.close()
    pool.join()

 3、进程间通信

管道:

共享队列:

互斥锁和信号量:

共享内存:

 

持续更新中。。。