画布 1
第三模块
  面向对象
    基础
      核心概念
        类
          基本定义方法 ： class Message:
          定义类，在类中定义方法，在方法中去实现具体的功能
        对象
          基本语法 ： msg_object = Message()
          实例化类并的个一个对象，通过对象去调用并执行方法
          对象，基于类实例化出来”一块内存“，默认里面没有数据；经过类的 `__init__`方法，可以在内存中初始化一些数据。
          对象，让我们可以在它的内部先封装一部分数据，以后想要使用时，再去里面获取。 （可生成多个对象）
        self
          self，类中的方法需要由这个类的对象来触发并执行（ 对象.方法名 ），且在执行时会自动将对象当做参数传递给self，以供方法中获取对象中已封装的值。
          self，本质上就是一个参数。这个参数是Python内部会提供，其实本质上就是调用当前方法的那个对象。
          除了self默认参数以外，方法中参数的定义和执行与函数是相同的
          举例：
            class Message:
def __init__(self, content):
self.data = content
def send_email(self, email):
data = "给{}发邮件，内容是：{}".format(email, self.data)
print(data)
def send_wechat(self, vid):
data = "给{}发微信，内容是：{}".format(vid, self.data)
print(data)
# 对象 = 类名() # 自动执行类中的 __init__ 方法。
# 1. 根据类型创建一个对象，内存的一块 区域 。
# 2. 执行__init__方法，模块会将创建的那块区域的内存地址当self参数传递进去。 往区域中(data="注册成功")
msg_object = Message("注册成功")
msg_object.send_email("wupeiqi@live.com") # 给wupeiqi@live.com发邮件，内容是：注册成功
msg_object.send_wechat("武沛齐") # 给武沛齐发微信，内容是：注册成功
        注意：
          1.类名称首字母大写&驼峰式命名；
          2.py3之后默认类都继承object；
          3.在类种编写的函数称为方法；
          4.每个方法的第一个参数是self。
      三大特性
        封装
          总的来说，就是将数据和方法封装到类中。
          将数据封装到了对象中，在实例化一个对象时，可以通过`__init__`初始化方法在对象中封装一些数据，便于以后使用。
          将同一类方法封装到了一个类中
        继承
          子类可以继承父类中的方法和类变量（不是拷贝一份，父类的还是属于父类，子类可以继承而已）
          注意：
            1：执行对象.方法时，优先在自己的类中找，自己的没有才去父类找
              class Base:
def func(self):
print("Base.func")
class Son(Base):
def show(self):
print("Son.show")
s1 = Son()
s1.show()
s1.func() # 优先在自己的类中找，自己没有才去父类。
s2 = Base()
s2.func()
            2：Python支持多继承：先继承左边、再继承右边的（从左到右依次查找）
              class TCPServer:
def f1(self):
print("TCPServer")
class ThreadingMixIn:
def f1(self):
print("ThreadingMixIn")
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
def run(self):
print('before')
self.f1()
print('after')
obj = ThreadingTCPServer()
obj.run()
>>> before
>>> ThreadingMixIn
>>> after
            self到底是谁？去self对应的那个类中去获取成员，没有就按照继承关系向上查找 。 （不论什么时候，都是从self对应的那个类中开始找）
              class Base:
def f1(self):
print('before')
self.f2() # slef是obj对象（Foo类创建的对象） obj.f2
print('base.f1')
def f2(self):
print('base.f2')
class Foo(Base):
def f2(self):
print('foo.f2')
obj = Foo()
obj.f1() # 优先去Foo类中找f1，因为调用f1的那个对象是Foo类创建出来的。
>>> before
>>> foo.f2
>>> base.f1
b1 = Base()
b1.f1()
>>> before
>>> base.f2
>>> base.f1
        多态
          Python默认支持多态（这种方式称之为鸭子类型）
          在程序设计中，鸭子类型（duck typing）是动态类型的一种风格。在鸭子类型中，关注点在于对象的行为，能作什么；而不是关注对象所属的类型，例如：一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子，那么这只鸟可以被称为鸭子。
          示例：
            def func(arg):
v1 = arg.copy() # 浅拷贝
print(v1)
func("武沛齐")
func([11,22,33,44])
    进阶
      成员
        变量
          类变量
            - 类变量，属于类，可以被所有对象共享，一般用于给对象提供公共数据（类似于全局变量）。
          实例变量
            - 实例变量，属于对象，每个对象中各自维护自己的数据。
          关于实例变量和类变量的例子：
            class Person(object):
country = "中国"
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
# message = "{}-{}-{}".format(Person.country, self.name, self.age)
message = "{}-{}-{}".format(self.country, self.name, self.age)
print(message)
print(Person.country) # 中国
p1 = Person("武沛齐",20)
print(p1.name)
print(p1.age)
print(p1.country) # 中国
p1.show() # 中国-武沛齐-20
          提示：当把每个对象中都存在的相同的示例变量时，可以选择把它放在类变量中，这样就可以避免对象中维护多个相同数据。
          关于实例变量和类变量中的一些数据修改方法：
            p1.name = "root" # 在对象p1中讲name重置为root
p1.num = 19 # 在对象p1中新增实例变量 num=19
p1.country = "china" # 在对象p1中新增实例变量 country="china" #只修改p1这个对象中的值
            Person.country = "美国" #修改类变量
        方法
          绑定方法
            绑定方法，默认有一个self参数，由对象进行调用（此时self就等于调用方法的这个对象）【对象&类均可调用】
          静态方法
            静态方法，无默认参数，用类和对象都可以调用。【对象&类均可调用】
          类方法
            类方法，默认有一个cls参数，用类或对象都可以调用（此时cls就等于调用方法的这个类）【对象&类均可调用】
          例子：
            class Foo(object):
def __init__(self, name,age):
self.name = name
self.age = age
def f1(self):
print("绑定方法", self.name)
@classmethod #类方法
def f2(cls):
print("类方法", cls)
@staticmethod #静态方法
def f3():
print("静态方法")
# 绑定方法（对象）
obj = Foo("武沛齐",20)
obj.f1() # Foo.f1(obj)
# 类方法 #可通过类来直接调用
Foo.f2() # cls就是当前调用这个方法的类。（类）
obj.f2() # cls就是当前调用这个方法的对象的类。
# 静态方法
Foo.f3() # 类执行执行方法（类）
obj.f3() # 对象执行执行方法
        属性
          属性其实是由绑定方法 + 特殊装饰器 组合创造出来的，让我们以后在调用方法时可以不加括号。 （仅此而已）
          例子：
            class Foo(object):
def __init__(self, name):
self.name = name
def f1(self):
return 123
@property #写法：要@property
def f2(self):
return 123
obj = Foo("武沛齐")
v1 = obj.f1()
print(v1)
v2 = obj.f2
print(v2)
          编写的两种方式：
            方式一：基于装饰器
              class C(object):
@property #直接执行，不需要加括号
def x(self):
pass
@x.setter #需要传入一个变量
def x(self, value):
pass
@x.deleter #删除x方法
def x(self):
pass
obj = C()
obj.x
obj.x = 123
del obj.x
            方式二：基于定义变量
              class C(object):
def getx(self):
pass
def setx(self, value):
pass
def delx(self):
pass
x = property(getx, setx, delx, "I'm the 'x' property.")
#对应的分别是（@property，@x.setter，@x.deleter，注释）
obj = C()
obj.x
obj.x = 123
del obj.x
          关于属性要注意的一点：
            由于属性和实例变量的调用方式相同，所以在编写时需要注意：属性名称 不要 实例变量 重名
              class Foo(object):
def __init__(self, name, age):
self.name = name
self.age = age
@property
def name(self):
return "{}-{}".format(self.name, self.age) # 报错，循环调用自己（直到层级太深报错）
@name.setter
def name(self, value):
print(value)
obj = Foo("武沛齐", 123)
print(obj.name)
            如果真的想要在名称上创建一些关系，可以让实例变量加上一个下划线。
              class Foo(object):
def __init__(self, name, age):
self._name = name
self.age = age
@property
def name(self):
return "{}-{}".format(self._name, self.age)
obj = Foo("武沛齐", 123)
print(obj._name)
print(obj.name)
      成员修饰符
        公有
          公有，在任何地方都可以调用这个成员。
        私有
          私有，只有在类的内部才可以调用改成员（成员是以两个下划线开头，则表示该成员为私有）。
        例子：
          私有成员
            class Foo(object):
def __init__(self, name, age):
self.__name = name
self.age = age
def get_data(self):
return self.__name
def get_age(self):
return self.age
obj = Foo("武沛齐", 123)
# 公有成员
print(obj.age)
v1 = self.get_age()
print(v1)
# 私有成员
# print(obj.__name) # 错误，由于是私有成员，只能在类中进行使用。
v2 = obj.get_data()
print(v2)
          私有方法
            class Foo(object):
def get_age(self):
print("公有的get_age")
def __get_data(self):
print("私有的__get_data方法")
def proxy(self):
print("公有的proxy")
self.__get_data()
obj = Foo()
obj.get_age()
obj.proxy()
        特别提醒：父类中的私有成员，子类无法继承。
          class Base(object):
def __data(self):
print("base.__data")
def num(self):
print("base.num")
class Foo(Base):
def func(self):
self.num()
self.__data() # # 不允许执行父类中的私有方法
obj = Foo()
obj.func()
        特殊方法可调用私有成员，但不建议这样使用
          class Foo(object):
def __init__(self):
self.__num = 123
self.age = 19
def __msg(self):
print(1234)
obj = Foo()
print(obj.age)
print(obj._Foo__num) #调用的语法，在 __name 前面加上 _Foo 来调用
obj._Foo__msg()
      ”对象的嵌套“
        例子
          class Student(object):
""" 学生类 """
def __init__(self, name, age, class_object):
self.name = name
self.age = age
self.class_object = class_object
def message(self):
data = "我是一名{}班的学生，我叫:{},我今年{}岁".format(self.class_object.title, self.name, self.age)
print(data)
class Classes(object):
""" 班级类 """
def __init__(self, title, school_object):
self.title = title
self.school_object = school_object
class School(object):
""" 学校类 """
def __init__(self, name):
self.name = name
s1 = School("北京校区")
s2 = School("上海校区")
c1 = Classes("Python全栈", s1)
c2 = Classes("Linux云计算", s2)
user_object_list = [
Student("武沛齐", 19, c1),
Student("Alex", 19, c1),
Student("日天", 19, c2)
]
for obj in user_object_list:
print(obj.name, obj.class_object.title , obj.class_object.school_object.name)
      特殊成员
        __init__
          初始化方法
          例子
            class Foo(object):
def __init__(self, name):
self.name = name
obj = Foo("武沛齐")
        __new__
          构造方法
          例子
            class Foo(object):
def __init__(self, name):
print("第二步：初始化对象，在空对象中创建数据")
self.name = name
def __new__(cls, *args, **kwargs): #不管有没有写其实都会先执行这一个方法
print("第一步：先创建空对象并返回")
return object.__new__(cls)
obj = Foo("武沛齐")
        __call__
          可以用来返回这个对象的某些基本信息
          例子
            class Foo(object):
def __call__(self, *args, **kwargs):
print("执行call方法")
obj = Foo()
obj()
        __str__
          可以当作这个类的简介
          例子
            class Foo(object):
def __str__(self):
return "哈哈哈哈"
obj = Foo()
data = str(obj)
print(data)
#内部有def __str__(self)后，可以直接print(obj)
        __dict__
          会自动将该类的一些基本元素转化为字典返回
          例子
            class Foo(object):
def __init__(self, name, age):
self.name = name
self.age = age
obj = Foo("武沛齐",19)
print(obj.__dict__) #{'name': '武沛齐', 'age': 19}
        __getitem__、__setitem__、__delitem__
          保证了该类创建出的对象可以通过[]来获取、添加/更改、删除值 （相当于多了一部分的语法规则）
          例子
            class Foo(object):
def __getitem__(self, item):
pass
def __setitem__(self, key, value):
pass
def __delitem__(self, key):
pass
obj = Foo("武沛齐", 19)
obj["x1"]
obj['x2'] = 123
del obj['x3']
        __enter__，__exit__
          一般成对出现，在 with xxx as x 的语法中使用
          例子
            class Foo(object):
def __enter__(self):
print("进入了")
return 666
def __exit__(self, exc_type, exc_val, exc_tb):
print("出去了")
obj = Foo()
with obj as data: #__enter__中return的值给了data
print(data)
        __add__
          用处暂未想到
          例子
            class Foo(object):
def __init__(self, name):
self.name = name
def __add__(self, other):
return "{}-{}".format(self.name, other.name)
v1 = Foo("alex")
v2 = Foo("sb")
# 对象+值，内部会去执行 对象.__add__方法，并将+后面的值当做参数传递过去。
v3 = v1 + v2 #严格按照顺序来，调用前面的__add__方法，再将+后面的那个当作参数传入
print(v3)
        __iter__
          看下面的迭代器、生成器、可迭代对象！
      迭代器、生成器、可迭代对象
        迭代器
          迭代器类型的定义：
1.当类中定义了 __iter__ 和 __next__ 两个方法。 （需要同时有这两个，才是迭代器）
2.__iter__ 方法需要返回对象本身，即：self
3. __next__ 方法，返回下一个数据，如果没有数据了，则需要抛出一个StopIteration的异常。
          例子
            # 创建 迭代器类型 ：
class IT(object):
def __init__(self):
self.counter = 0
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == 3:
raise StopIteration()
return self.counter
# 根据类实例化创建一个迭代器对象：
obj1 = IT()
# v1 = obj1.__next__()
# v2 = obj1.__next__()
# v3 = obj1.__next__() # 抛出异常
v1 = next(obj1) # obj1.__next__()
print(v1)
v2 = next(obj1)
print(v2)
v3 = next(obj1)
print(v3)
obj2 = IT()
for item in obj2: # 首先会执行迭代器对象的__iter__方法并获取返回值，一直去反复的执行 next(对象)
print(item)
迭代器对象支持通过next取值，如果取值结束则自动抛出StopIteration。
for循环内部在循环时，先执行__iter__方法，获取一个迭代器对象，然后不断执行的next取值（有异常StopIteration则终止循环）。
        生成器
          yield（模块二有，这里不展开）
          生成器类也是一种特殊的迭代器类
        可迭代对象
          如果一个类中有__iter__方法且返回一个迭代器对象 ；则我们称以这个类创建的对象为可迭代对象。
          例子
            class Foo(object):
def __iter__(self):
return 迭代器对象(生成器对象)
obj = Foo() # obj是 可迭代对象。
# 可迭代对象是可以使用for来进行循环，在循环的内部其实是先执行 __iter__ 方法，获取其迭代器对象，然后再在内部执行这个迭代器对象的next功能，逐步取值。
for item in obj:
pass
        基于可迭代对象&迭代器实现：自定义range
          class IterRange(object):
def __init__(self, num):
self.num = num
self.counter = -1
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == self.num:
raise StopIteration()
return self.counter
class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
return IterRange(self.max_num)
obj = Xrange(100)
for item in obj:
print(item)
        基于可迭代对象&生成器 实现：自定义range
          class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
counter = 0
while counter < self.max_num:
yield counter
counter += 1
obj = Xrange(100)
for item in obj:
print(item)
        判断是否是迭代器/可迭代对象
          from collections.abc import Iterator, Iterable
v1 = [11, 22, 33]
print( isinstance(v1, Iterator) ) # false，判断是否是迭代器；判断依据是__iter__ 和 __next__。
v2 = v1.__iter__()
print( isinstance(v2, Iterator) ) # True
v1 = [11, 22, 33]
print( isinstance(v1, Iterable) ) # True，判断依据是是否有 __iter__且返回迭代器对象。
v2 = v1.__iter__()
print( isinstance(v2, Iterable) ) # True，判断依据是是否有 __iter__且返回迭代器对象。
          可通过上面两个结合用if语句来判断是否是迭代器/可迭代对象
    高级应用
      继承【补充】
        c3算法
          口诀：从左到右，深度优先，大小钻石，留住顶端
          分析过程：
            #(从第一个merge里面的[]从前往后取出，然后从后面的每个[]里的第二个开始比对，如果有重复就什么都不干，从第二个[]继续前面的过程；如果没有重复，就删掉该元素，后重复执行前面的操作) （不论多复杂只要一步步分析就行）
mro(A) = [A] + merge( mro(B), mro(C), [B,C] )
mro(A) = [A] + merge( [B], [C，D], [B,C] )
mro(A) = [A] + merge( [B], [C，D], [B,C] )
mro(A) = [A] + [B] merge( [], [C，D], [,C] )
mro(A) = [A] + [B,C] merge( [], [，D], [,] )
mro(A) = [A] + [B,C,D] merge( [], [，], [,] )
mro(A) = [A] + [B,C,D]
mro(A) = [A,B,C,D]
          经典继承图（以后看看就好，也可在以后看的时候对分析分析）
            简写为：A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
        mro
          可通过mro()获取当前类的继承关系
            class C(object):
pass
class B(object):
pass
class A(B, C):
pass
print( A.mro() ) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>] #获取列表形式
print( A.__mro__ ) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>) #获取元组形式
        py2和py3中继承的区别
          在python2.2之前，只支持经典类【从左到右，深度优先，大小钻石，不留顶端】
          后来，Python决定不再原来的经典类上进行修改了，而是再创建一个新式类来支持这个功能。【从左到右，深度优先，大小钻石，留住顶端。】
        super
          按照mro继承关系向上找成员
          例子
            class Top(object):
def message(self, num):
print("Top.message", num)
class Base(Top):
pass
class Foo(Base):
def message(self, num):
print("Foo.message", num)
super().message(num + 100)
obj = Foo()
obj.message(1)
>>> Foo.message 1
>>> Top.message 101
            class Base(object):
def message(self, num):
print("Base.message", num)
super().message(1000)
class Bar(object):
def message(self, num):
print("Bar.message", num)
class Foo(Base, Bar):
pass
obj = Foo()
obj.message(1)
>>> Base.message 1
>>> Bar.message 1000
          应用场景：
            假设有一个类，他原来已实现了某些功能，但我们想在他的基础上再扩展点功能，则可用该方法
            例子
              class MyDict(dict): #这里是继承了字典那个类
def get(self, k):
print("自定义功能")
return super().get(k)
info = MyDict()
info['name'] = "武沛齐" # __setitem__
info["age"] = 18 # __setitem__
print(info)
value = info.get("age")
print(value)
      内置函数【补充】
        isinstance
          判断对象是否是某个类或其子类的实例
          例子
            class Top(object):
pass
class Base(Top):
pass
class Foo(Base):
pass
v1 = Foo()
print( isinstance(v1, Foo) ) # True，对象v1是Foo类的实例
print( isinstance(v1, Base) ) # True，对象v1的Base子类的实例。
print( isinstance(v1, Top) ) # True，对象v1的Top子类的实例。
        issubclass
          判断类是否是某个类的子孙类
          例子
            class Top(object):
pass
class Base(Top):
pass
class Foo(Base):
pass
print(issubclass(Foo, Base)) # True
print(issubclass(Foo, Top)) # True
        type
          获取一个对象的类型
          例子
            v1 = "武沛齐"
result = type(v1)
print(result) # <class 'str'>
            v2 = "武沛齐"
print( type(v2) == str ) # True
v3 = [11, 22, 33] # list(...)
print( type(v3) == list ) # True
            class Foo(object):
pass
v4 = Foo()
print( type(v4) == Foo ) # True
        callable
          是否可在后面加括号执行
          例子
            函数
              def func():
pass
print( callable(func) ) # True
            类
              class Foo(object):
pass
print( callable(Foo) ) # True
            类中具有__call__方法的对象
              class Foo(object):
def __call__(self, *args, **kwargs):
pass
obj = Foo()
print( callable(obj) ) # True
      异常处理
        在程序开发中如果遇到一些 `不可预知`的错误 或 你懒得做一些判断 时，可以选择用异常处理来做
        基本格式
          try:
# 逻辑代码
except Exception as e:
# try中的代码如果有异常，则此代码块中的代码会执行。
finally:
# try中的代码无论是否报错，finally中的代码都会执行，一般用于释放资源。
print("end")
        特殊的finally
          在try或except中即使定义了return，也会执行最后的finally块中的代码。
          例子
            def func():
try:
return 123
except Exception as e:
pass
finally:
print(666)
a = func()
print(a)
#输出结果
666
123
          注意：如果return的是一个函数，则会先执行函数，最后才执行finally
            def func():
try:
return 123
except Exception as e:
pass
finally:
print(666)
return 234
a = func()
print(a)
#输出结果
666
234
      异常细分
        例子
          import requests
from requests import exceptions
while True:
url = input("请输入要下载网页地址：")
try:
res = requests.get(url=url)
print(res)
except exceptions.MissingSchema as e:
print("URL架构不存在")
except exceptions.InvalidSchema as e:
print("URL架构错误")
except exceptions.InvalidURL as e:
print("URL地址格式错误")
except exceptions.ConnectionError as e:
print("网络连接错误")
except Exception as e:
print("代码出现错误", e)
# 提示：如果想要写的简单一点，其实只写一个Exception捕获错误就可以了。
        常见的异常（看不看无所谓）
          常见异常：
"""
AttributeError 试图访问一个对象没有的树形，比如foo.x，但是foo没有属性x
IOError 输入/输出异常；基本上是无法打开文件
ImportError 无法引入模块或包；基本上是路径问题或名称错误
IndentationError 语法错误（的子类） ；代码没有正确对齐
IndexError 下标索引超出序列边界，比如当x只有三个元素，却试图访问n x[5]
KeyError 试图访问字典里不存在的键 inf['xx']
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法，代码不能编译(个人认为这是语法错误，写错了）
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量，基本上是由于另有一个同名的全局变量，
导致你以为正在访问它
ValueError 传入一个调用者不期望的值，即使值的类型是正确的
"""
更多异常：
"""
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError
"""
      自定义异常&抛出异常
        触发条件：必须有 raise xxx()
        例子
          class MyException(Exception):
def __init__(self, msg, *args, **kwargs):
super().__init__(*args, **kwargs)
self.msg = msg
try:
raise MyException("xxx失败了")
except MyException as e:
print("MyException异常被触发了", e.msg) #MyException异常被触发了 xxx失败了
except Exception as e:
print("Exception", e)
        案例（以后可能可以用到的地方）
          **案例一**：你我合作协同开发，你调用我写的方法。
          **案例二**：在框架内部已经定义好，遇到什么样的错误都会触发不同的异常。
          **案例三**：按照规定去触发指定的异常，每种异常都具备被特殊的含义。
      反射
        提供了一种更加灵活的方式让你可以实现去 对象 中操作成员（以字符串的形式去 `对象` 中进行成员的操作）
        反射的四个内置函数
          getattr
            去对象中获取成员
            v1 = getattr(对象,"成员名称")
v2 = getattr(对象,"成员名称", 不存在时的默认值)
          setattr
            去对象中设置成员
            setattr(对象,"成员名称",值)
          hasattr
            对象中是否包含成员
            v1 = hasattr(对象,"成员名称") # True/False
          delattr
            删除对象中的成员
            delattr(对象,"成员名称")
        由于python里面一切皆对象（对象，类，模块...）,则由于反射支持以字符串的形式去对象中操作成员【等价于 对象.成员 】，所以，基于反射也可以对类、模块中的成员进行操作。 【简而言之，只要看到 xx.oo 都可以用反射实现。】
          例子
            class Person(object):
title = "武沛齐"
v1 = Person.title
print(v1)
v2 = getattr(Person,"title")
print(v2)
            import re
v1 = re.match("\w+","dfjksdufjksd")
print(v1)
func = getattr(re,"match")
v2 = func("\w+","dfjksdufjksd")
print(v2)
        import_module + 反射
          在Python中如果想要导入一个模块，可以通过import语法导入；企业也可以通过字符串的形式导入。
          例子
            导入模块
              # 导入模块
from importlib import import_module
m = import_module("random")
v1 = m.randint(1,100)
            导入模块内的类
              # 导入模块exceptions，获取exceptions中的InvalidURL类。
from requests.exceptions import InvalidURL
              # 错误方式
from importlib import import_module
m = import_module("requests.exceptions.InvalidURL") # 报错，import_module只能导入到模块级别。
              # 导入模块
from importlib import import_module
m = import_module("requests.exceptions")
# 去模块中获取类
cls = m.InvalidURL
          作用：提高代码的可拓展性
    单例模式
      创建代码（类）
        class Singleton:
instance = None
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
# 返回空对象
if cls.instance:
return cls.instance
cls.instance = object.__new__(cls)
return cls.instance
obj1 = Singleton('alex')
obj2 = Singleton('SB')
print(obj1,obj2)
  网络编程
    上篇
      网络基础
        网络架构
          小型企业基础网络架构
          家庭网络架构
          互联网
        设备
          二层交换机
            当电脑接入交换机之后，我们需要为每台电脑分配一个IP，例如：
- 电脑1：192.168.10.1
- 电脑2：192.168.10.2
- 电脑3：192.168.10.3
- 电脑4：192.168.10.4
            局域网内容个电脑之间是基于ARP协议来进行通信，例如：A电脑向 IP为192.168.10.3的另一个电脑发送消息。
第一步：A封装数据包，此时只知道目标IP不知道目标mac地址（未知mac地址时默认会设置为FF）。
第二步：将数据包发送到交换机，交换机通过广播的形式将数据发送给所有电脑。
第三步：目标电脑接收到数据包后，监测自己是否是目标IP。
- 是，收到数据并回复。
- 不是，则丢弃包。
为防止每次发送消息都是广播形式，每台电脑的内部都为维护了一个ARP表，接受到数据时（无论是否自己的）都会记录自己了解的IP和MAC的对应关系，例如：
Internet地址 物理地址
192.168.10.1 14-9d-da-2a-dd-0a
192.168.10.3 14-9d-da-2a-dd-0c
...
以便于下次在发送消息时，就知道了目标的mac地址，直接让交换机转发给指定的电脑（单播）。
同时，当有消息发送经过二层交换机时他的内容也会维护记录了交换机接口和连接的电脑的mac地址的对应关系，例如：
接口（网卡） mac地址
接口1 14-9d-da-2a-dd-0A
接口2 14-9d-da-2a-dd-0B
接口3 14-9d-da-2a-dd-0C
...
这样一来，交换机在进行数据转发时，效率就更高了。
注意：每台电脑出厂时在网卡中都设置了唯一的mac地址（不重复），网卡集成在主板上，如果更换了主板则mac地址也会变更。
          路由器
            划分好网络结构之后，其实会给各宿舍的电脑分配IP和网关，例如：
宿舍A：
- 电脑1：192.168.10.1 网关：192.168.10.254
- 电脑2：192.168.10.2 网关：192.168.10.254
- 电脑3：192.168.10.3 网关：192.168.10.254
- 电脑4：192.168.10.4 网关：192.168.10.254
宿舍B
- 电脑1：192.168.20.1 网关：192.168.20.254
- 电脑2：192.168.20.2 网关：192.168.20.254
- 电脑3：192.168.20.3 网关：192.168.20.254
- 电脑4：192.168.20.4 网关：192.168.20.254
然后再在路由器中配置路由表（包含网段和路由器上的接口的对应关系），例如：
接口 IP
eth0 192.168.10.254（192.168.10网段）
eth1 192.168.20.254（192.168.20网段）
想与外部网络通信，需要配置网关，网关就是路由表中配置的指向此网段的IP。其实就类似于贸易出口都需要经过海关。
            数据通信的过程结合了APR协议和IP协议，例如：宿舍A的电脑1向宿舍B的电脑3发送消息（目标IP：192.168.20.3）。
简化过程：
- 宿舍A的电脑1，通过广播或单播将数据发送到网管（路由器）
- 路由器接收到数据之后，再通过对应的接口把数据通过广播的形式发送到宿舍B。
注意：各自局域网内通过学习并记录相关mac地址后，就可以不再使用广播形式，而是使用单播来发送消息了。
          三层交换机
            三层交换机集成了 交换机 & 路由器的功能（大部分路由器功能）
            按照下图，在三层交换机上分别做如下几件事：
1. 划分两个vlan，模拟出来路由器的两个接口。
2. 将交换机的接口划分给指定的vlan，例如：
接口1、2、3划分给一个vlan，相当于交换机连接上了路由器。
接口4、5、6划分给一个vlan，相当于交换机连接上了路由器。
3. 电脑连接上交换机。
4. 进行相应的配置。
宿舍A（左边）电脑配置：
- 电脑1：192.168.10.1 网关：192.168.10.254 对应交换机接口：1
- 电脑2：192.168.10.2 网关：192.168.10.254 对应交换机接口：2
- 电脑3：192.168.10.3 网关：192.168.10.254 对应交换机接口：3
宿舍B（右边）电脑配置：
- 电脑1：192.168.20.1 网关：192.168.20.254 对应交换机接口：4
- 电脑2：192.168.20.2 网关：192.168.20.254 对应交换机接口：5
- 电脑3：192.168.20.3 网关：192.168.20.254 对应交换机接口：6
交换机中的路由配置：
接口 IP
左vlan 192.168.10.254（192.168.10网段）
右vlan 192.168.20.254（192.168.20网段）
通过上述的配置之后，就可以实现宿舍A和宿舍B的网络通信了。
        专有名词
          mac地址
            每台电脑出厂时在网卡中都设置了唯一的mac地址（不重复），网卡集成在主板上，如果更换了主板则mac地址也会变更。
          网关
            网关就是路由表中配置的指向此网段的IP。其实就类似于贸易出口都需要经过海关。
          子网掩码和IP
            IP
              IP其是一个32位的二进制
              二进制表示：00000000.10010111.11111111.00001111
十进制表示：251.151.255.15
0~255 （只能在这个范围内）
192.178.11.211
192.178.11.311 （错的）
              一个IP地址可以划分为 网络地址+主机地址 两个部分
            子网掩码
              如何确定网络地址和主机地址呢？
                通过子网掩码就可以确定IP的网络地址和主机地址。
示例1：
IP：192.168.1.199 11000000.10101000.00000001.11000111
子网掩码：255.255.255.0 11111111.11111111.11111111.00000000
此时，网络地址就是前24位 + 主机地址是后8位。你可能见过有些IP这样写 192.168.1.199/24，意思也是前24位是网络地址。
示例2：
IP：192.168.99.254 11000000.10101000.01100011.11111110
子网掩码：255.255.240.0 11111111.11111111.11111100.00000000
此时，网络地址就是前22位 + 主机地址是后10位。你可能见过有些IP这样写 192.168.99.254/22，意思也是前22位是网络地址。
              划分 网络地址 + 主机地址 的意义是什么？
                网络地址相同的IP，也称为属于同一个网段。
在局域网内只有同一个网段的IP才能相互通信，不同网段IP想要通信需要借助路由的转发才能通信。
当了解子网掩码之后，其实就可以确定某个网段可以容纳的主机个数，例如：
【IP: 192.168.10.2 掩码：255.255.255.0】 和 【192.168.10.251 掩码：255.255.255.0】 数据同一个网段。
示例网段的主机范围：11000000.10101000.00001010. 00000001 ~ 11000000.10101000.00001010. 11111110
-------------------------- --------------------------
网络地址 网络地址
192.168.10.1 ~ 192.168.10.254
【IP: 192.168.8.1 掩码：255.255.240.0】 和 【192.168.11.254 掩码：255.255.240.0】 数据同一个网段。
子网掩码：255.255.240.0
示例网段的主机范围：11000000.10101000.000010 00.00000001 ~ 11000000.10101000.000010 11.11111110
11111111.11111111.111111 00.00000000
------------------------ ------------------------
网络地址 网络地址
192.168.8.1 ~ 192.168.11.254
【IP: 192.168.96.1 掩码：255.255.240.0】 和 【192.168.99.254 掩码：255.255.240.0】 数据同一个网段。
示例网段的主机范围：11000000.10101000.011000 00.00000001 ~ 11000000.10101000.011000 11.11111110
------------------------ ------------------------
网络地址 网络地址
192.168.96.1 ~ 192.168.99.254
          DHCP
            - 在电脑端，IP地址获取方式设置为自动。
- 在路由器或三层交换机，开启DHCP服务，并设置IP地址池。（家用路由器上也是基于DHCP服务自动分配的IP）
这样，电脑只要连接只该网络，DHCP服务就会为它自动分配IP、子网掩码、网关。
          内网和公网IP
            一般情况下，内网IP都用这些（潜规则）：
- 10.0.0.0 到 10.255.255.255
- 172.16.0.0 到172.31.255.255
- 192.168.0.0 到192.168.255.255
            内网IP
              之前我们自己在一个局域网内为电脑分配的IP都称为`内网IP`，基于内网IP可以在一个局域网内进行相互通信（也需要相关的配置）。
            公网（外网）IP
              如果想要通过互联网进行通信，就必须借助公网IP
            如何开发一个网站供全球的用户访问
              - 拉专线，申请固定公网IP
- 买一台服务器（就是性能好的电脑）
- 公网IP绑定至此服务器
- 将写好的代码放在服务器上并运行起来
          IPv4和IPv6
            IPv4，长度为 32 位（4 个字节）， 格式：A.B.C.D
IPv6，长度为 128 位（16 个字节），用":"分成8段，格式：XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX
（每个X是一个16进制数）。
          云服务器
            简单的说：他们造了一个机房（网吧），买了很多很多的服务器（高性能电脑），然后将他们放在机房，然后通电+通网，主要对外去租赁这些服务器资源，让用户不必再自己 拉专线+配置网络+买服务器。
          端口
            假设，你在腾讯租了一台云服务器（外网IP:123.206.15.88），然后又开发了 2 个网站运行在服务器上。
那么问题来了，用户在自己的电脑或手机上如何来分别访问同一台服务器上两个程序呢？
其实，在计算机中有一个 `端口` 的概念，每个程序想要通过网络进行通讯都必须要指定一个端口，例如：
- 网站A：使用8001端口，那么用户在自己电脑上或手机上访问时指定 IP和端口 即可，如： `123.206.15.88:8001`
- 网站B：使用8002端口，那么用户在自己电脑上或手机上访问时指定 IP和端口 即可，如： `123.206.15.88:8002`
注意：端口的取值范围：0 ~ 65535，很多端口在计算机的内部已被使用，我们平时自定义时尽量选择5000之后的端口。
            提示：如果在浏览器上只写IP不写端口，则默认是80端口。
          域名
            域名和IP创建对应关系，用户只需要记住域名就可以了
            www.baidu.com --> 110.242.68.3
www.taobao.com --> 121.18.239.232
            注意：域名只是和IP创建了对应关系，与端口无关 www.baidu.com:80
            用域名访问的步骤
              在用户在自己的电脑或手机上输入域名去访问时，其实要执行两个步骤：
- 根据域名寻找IP。（寻找IP）
- 获得IP之后，再通过IP再去访问指定服务器。
              第一步：在自己电脑的DNS缓存记录中寻找 域名对应的IP，如果未命中，则执行下一步。
              第二步：在自己电脑的hosts文件中寻找，如果未命中，则执行下一步。
- mac系统：/etc/hosts 文件中
- win系统：C:\Windows\System32\drivers\etc\hosts 文件中
# 内容示例
127.0.0.1 localhost
255.255.255.255 broadcasthost
127.0.0.1 kubernetes.docker.internal
192.168.1.55 www.pythonav.com
              第三步：在自己电脑上找到DNS配置的地址（本地域名服务器），去这个地址寻找域名对应的IP，如果未命中，则执行下一步。
常见的DNS服务器地址：
114.114.114.114（114 DNS）
223.5.5.5（阿里 AliDNS）
8.8.8.8（Google DNS，随着Google在中国的没落和国内官方的限制，已经不是太好用了）
...
各大运营商也有相应的DNS服务器...
如果你选择的是自动获得DNS，那么就会使用本地运营商的DNS服务器了。
              第四步：去根域名服务器中询问（全球共13台根域名服务器，距离中国最近的一台是在日本）
      初识网络编程
        Python中内置了一个socket模块，可以快速实现网络之间进行传输数据。
        服务端，放在左边云服务器中（有固定IP）
          例子
            import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('123.206.15.88', 8001)) # IP,端口
sock.listen(5) # 支持排队等待5人
while True:
# 2.等待，有人来连接（阻塞）
conn, addr = sock.accept() # 等待客户端来连接（阻塞）
# 3.等待，连接者发送消息（阻塞）
client_data = conn.recv(1024) # 等待接收客户端发来数据
print(client_data.decode('utf-8')) # 字节
# 4.给连接者回复消息
conn.sendall("hello world".encode('utf-8'))
# 5.关闭连接
conn.close()
# 6.停止服务端程序
sock.close()
        客户端，放在右边用户电脑上
          例子
            import socket
# 1. 向指定IP发送连接请求
client = socket.socket()
client.connect(('123.206.15.88', 8001)) # 向服务端发起连接（阻塞）10s
# 2. 连接成功之后，发送消息
client.sendall('hello'.encode('utf-8'))
# 3. 等待，消息的回复（阻塞）
reply = client.recv(1024)
print(reply)
# 4. 关闭连接
client.close()
        注意3种IP
          本机
            服务端IP：127.0.0.1
          局域网
            服务端IP：（通过命令行ipconfig来查看）
          互联网
            服务端IP：（通过浏览器查看）（外网IP）
        案例
          智障客服
            服务端
              import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001)) # 127.0.0.1 或 查看自己局域网本地IP地址
sock.listen(5)
while True:
# 2.等待，有人来连接（阻塞）
conn, addr = sock.accept()
print("有人来连接了...")
# 3.连接成功后立即发送
conn.sendall("欢迎使用xx系统，请输入您想要办理的业务！".encode("utf-8"))
while True:
# 3.等待接受信息
data = conn.recv(1024)
if not data:
break
data_string = data.decode("utf-8")
# 4.回复消息
conn.sendall("你说啥？".encode("utf-8"))
print("断开连接了")
# 5.关闭与此人的连接
conn.close()
# 6.停止服务端程序
sock.close()
            客户端
              import socket
# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8001))
# 2.连接成功后，获取系统登录信息
message = client.recv(1024)
print(message.decode("utf-8"))
while True:
content = input("请输入(q/Q退出)：")
if content.upper() == 'Q':
break
client.sendall(content.encode("utf-8"))
# 3. 等待，消息的回复
reply = client.recv(1024)
print(reply.decode("utf-8"))
# 关闭连接，关闭连接时会向服务端发送空数据。
client.close()
          文件上传
            服务端
              import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001)) # 127.0.0.1 或 查看自己局域网本地IP地址
sock.listen(5)
conn, addr = sock.accept()
# 接收文件大小
data = conn.recv(1024)
total_file_size = int(data.decode('utf-8'))
# 接收文件内容
file_object = open('xxx.png', mode='wb')
recv_size = 0
while True:
# 每次最多接收1024字节
data = conn.recv(1024)
file_object.write(data)
file_object.flush()
recv_size += len(data)
# 上传完成
if recv_size == total_file_size:
break
# 接收完毕，关闭连接
conn.close()
sock.close()
            客户端
              import time
import os
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001))
file_path = input("请输入要上传的文件：")
# 先发送文件大小
file_size = os.stat(file_path).st_size
client.sendall(str(file_size).encode('utf-8'))
print("准备...")
time.sleep(2)
print("开始上传..")
file_object = open(file_path, mode='rb')
read_size = 0
while True:
chunk = file_object.read(1024) # 每次读取1024字节
client.sendall(chunk)
read_size += len(chunk)
if read_size == file_size:
break
client.close()
      BS和CS架构
        C/S架构，是Client和Server的简称。开发这种架构的程序意味着你即需要开发客户端也需要开发服务端。
        B/S架构，是Browser和Server的简称。开发这种架构的程序意味着你开发服务端即可，客户端用用户电脑上的浏览器来代替。
        简而言之，B/S架构就是开发网站；C/S架构就是开发安装在电脑的软件。
    下篇
      OSI 7层模型
        7层：应用层 表示层 会话层 传输层 网络层 数据链路层 物理层
        图片示例
        每一层分别介绍（由内至外）
          1：应用层
            规定数据的格式
            "GET /s?wd=你好 HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n"
          2：表示层
            对应用层数据的编码、压缩（解压缩）、分块、加密（解密）等任务
            "GET /s?wd=你好 HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n你好".encode('utf-8')
          3：会话层
            负责与目标建立、中断连接
            在发送数据之前，需要会先发送 “连接” 的请求，与远程建立连接后，再发送数据。当然，发送完毕之后，也涉及中断连接的操作。
          4：传输层
            建立端口到端口的通信，其实就确定双方的端口信息
            数据："GET /s?wd=你好 HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n你好".encode('utf-8')
端口：
- 目标：80
- 本地：6784
          5：网络层
            标记目标IP信息（IP协议层）
            数据："GET /s?wd=你好 HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n你好".encode('utf-8')
端口：
- 目标：80
- 本地：6784
IP：
- 目标IP：110.242.68.3（百度）
- 本地IP：192.168.10.1
          6：数据链路层
            对数据进行分组并设置源和目标mac地址
            数据："POST /s?wd=你好 HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n你好".encode('utf-8')
端口：
- 目标：80
- 本地：6784
IP：
- 目标IP：110.242.68.3（百度）
- 本地IP：192.168.10.1
MAC：
- 目标MAC：FF-FF-FF-FF-FF-FF
- 本机MAC：11-9d-d8-1a-dd-cd
          7：物理层
            将二进制数据在物理媒体上传输
            通过网线将二进制数据发送出去
        在编程中的体现
          在开发过程中其实只能体现：应用层、表示层、会话层、传输层，其他层的处理都是在网络设备中自动完成的
          代码
            import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('110.242.68.3', 80)) # 向服务端发送了数据包
key = "你好"
# 应用层
content = "GET /s?wd={} http1.1\r\nHost:www.baidu.com\r\n\r\n".format(key)
# 表示层
content = content.encode("utf-8")
client.sendall(content)
result = client.recv(8196)
print(result.decode('utf-8'))
# 会话层 & 传输层
client.close()
      TCP和UDP
        TCP
          TCP（Transmission Control Protocol，传输控制协议）是面向连接的协议，也就是说，在收发数据前，必须和对方建立可靠的连接，然后再进行收发数据。
          常见的有：语音通话、视频通话、实时游戏画面 等。
        UDP
          UDP（User Data Protocol）用户数据报协议， 是⼀个⽆连接的简单的⾯向数据报的传输层协议。 UDP不提供可靠性， 它只是把应⽤程序传给IP层的数据报发送出去， 但是并不能保证它们能到达⽬的地。 由于UDP在传输数据报前不⽤在客户和服务器之间建⽴⼀个连接， 且没有超时重发等机制， 故⽽传输速度很快。
          常见的有：网站、手机APP数据获取等。
        代码示例
          TCP（正常写的都是这个）
            服务端
              import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
while True:
# 2.等待，有人来连接（阻塞）
conn, addr = sock.accept()
# 3.等待，连接者发送消息（阻塞）
client_data = conn.recv(1024)
print(client_data)
# 4.给连接者回复消息
conn.sendall(b"hello world")
# 5.关闭连接
conn.close()
# 6.停止服务端程序
sock.close()
            客户端
              import socket
# 1. 向指定IP发送连接请求
client = socket.socket()
client.connect(('127.0.0.1', 8001))
# 2. 连接成功之后，发送消息
client.sendall(b'hello')
# 3. 等待，消息的回复（阻塞）
reply = client.recv(1024)
print(reply)
# 4. 关闭连接
client.close()
          UDP
            服务端
              import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8002))
while True:
data, (host, port) = server.recvfrom(1024) # 阻塞
print(data, host, port)
server.sendto("好的".encode('utf-8'), (host, port))
            客户端
              import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
text = input("请输入要发送的内容：")
if text.upper() == 'Q':
break
client.sendto(text.encode('utf-8'), ('127.0.0.1', 8002))
data, (host, port) = client.recvfrom(1024)
print(data.decode('utf-8'))
client.close()
      TCP三次握手和四次挥手
        0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y | I | Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        三次握手（连接）
          创建连接
            创建连接，客户端和服务端要进行三次握手。
            客户端 服务端
1. SYN-SENT --> <seq=100><CTL=SYN> --> SYN-RECEIVED
2. ESTABLISHED <-- <seq=300><ack=101><CTL=SYN,ACK> <-- SYN-RECEIVED
3. ESTABLISHED --> <seq=101><ack=301><CTL=ACK> --> ESTABLISHED
At this point, both the client and server have received an acknowledgment of the connection. The steps 1, 2 establish the connection parameter (sequence number) for one direction and it is acknowledged. The steps 2, 3 establish the connection parameter (sequence number) for the other direction and it is acknowledged. With these, a full-duplex communication is established.
        传输数据
          在收发数据的过程中，只有有数据的传送就会有应答（ack），如果没有ack，那么内部会尝试重复发送。
        四次挥手（关闭）
          关闭连接
            关闭连接，客户端和服务端要进行4次挥手
            TCP A TCP B
1. FIN-WAIT-1 --> <seq=100><ack=300><CTL=FIN,ACK> --> CLOSE-WAIT
2. FIN-WAIT-2 <-- <seq=300><ack=101><CTL=ACK> <-- CLOSE-WAIT
3. TIME-WAIT <-- <seq=300><ack=101><CTL=FIN,ACK> <-- LAST-ACK
4. TIME-WAIT --> <seq=101><ack=301><CTL=ACK> --> CLOSED
          多了一次服务器处理未完成进程的工序！
      粘包
        出现粘包的原因
          对于发送者，执行 `sendall/send` 发送消息时，是将数据先发送至自己网卡的 写缓冲区 ，再由缓冲区将数据发送给到对方网卡的读缓冲区。
对于接受者，执行 `recv` 接收消息时，是从自己网卡的读缓冲区获取数据。
所以，如果发送者连续快速的发送了2条信息，接收者在读取时会认为这是1条信息，即：2个数据包粘在了一起。
        示例代码
          # socket客户端（发送者）
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001))
client.sendall('alex正在吃'.encode('utf-8'))
client.sendall('翔'.encode('utf-8'))
client.close()
#上下应在两个.py文件中
# socket服务端（接收者）
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
conn, addr = sock.accept()
client_data = conn.recv(1024) #alex正在吃翔
print(client_data.decode('utf-8'))
conn.close()
sock.close()
        解决方法：
          每次发送的消息时，都将消息划分为 头部（固定字节长度） 和 数据 两部分。例如：头部，用4个字节表示后面数据的长度。
- 发送数据，先发送数据的长度，再发送数据（或拼接起来再发送）。
- 接收数据，先读4个字节就可以知道自己这个数据包中的数据长度，再根据长度读取到数据。
          struct
            详细用法图片
            示例代码
              import struct
# ########### 数值转换为固定4个字节，四个字节的范围 -2147483648 <= number <= 2147483647 ###########
content = "...".encode()
length = len(content)
v1 = struct.pack('i', length)
for item in v1:
print(item, bin(item))
# ########### 4个字节转换为数字 ###########
v2 = struct.unpack('i', v1)
print(v2)
          示例代码
            客户端
              import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8001))
# 第一条数据
data1 = 'alex正在吃'.encode('utf-8')
header1 = struct.pack('i', len(data1))
client.sendall(header1)
client.sendall(data1)
# 第二条数据
data2 = '翔'.encode('utf-8')
header2 = struct.pack('i', len(data2))
client.sendall(header2)
client.sendall(data2)
client.close()
            服务端
              import socket
import struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
conn, addr = sock.accept()
# 固定读取4字节
header1 = conn.recv(4)
data_length1 = struct.unpack('i', header1)[0] # 数据字节长度 21
has_recv_len = 0
data1 = b""
while True:
length = data_length1 - has_recv_len
if length > 1024:
lth = 1024
else:
lth = length
chunk = conn.recv(lth) # 可能一次收不完，自己可以计算长度再次使用recv收取，指导收完为止。 1024*8 = 8196
data1 += chunk
has_recv_len += len(chunk)
if has_recv_len == data_length1:
break
print(data1.decode('utf-8'))
# 固定读取4字节
header2 = conn.recv(4)
data_length2 = struct.unpack('i', header2)[0] # 数据字节长度
data2 = conn.recv(data_length2) # 长度
print(data2.decode('utf-8'))
conn.close()
sock.close()
          案例：信息 and 文件上传
            客户端
              import os
import json
import socket
import struct
def send_data(conn, content):
data = content.encode('utf-8')
header = struct.pack('i', len(data))
conn.sendall(header)
conn.sendall(data)
def send_file(conn, file_path):
file_size = os.stat(file_path).st_size
header = struct.pack('i', file_size)
conn.sendall(header)
has_send_size = 0
file_object = open(file_path, mode='rb')
while has_send_size < file_size:
chunk = file_object.read(2048)
conn.sendall(chunk)
has_send_size += len(chunk)
file_object.close()
def run():
client = socket.socket()
client.connect(('127.0.0.1', 8001))
while True:
"""
请发送消息，格式为：
- 消息：msg|你好呀
- 文件：file|xxxx.png
"""
content = input(">>>") # msg or file
if content.upper() == 'Q':
send_data(client, "close")
break
input_text_list = content.split('|')
if len(input_text_list) != 2:
print("格式错误，请重新输入")
continue
message_type, info = input_text_list
# 发消息
if message_type == 'msg':
# 发消息类型
send_data(client, json.dumps({"msg_type": "msg"}))
# 发内容
send_data(client, info)
# 发文件
else:
file_name = info.rsplit(os.sep, maxsplit=1)[-1]
# 发消息类型
send_data(client, json.dumps({"msg_type": "file", 'file_name': file_name}))
# 发内容
send_file(client, info)
client.close()
if __name__ == '__main__':
run()
            服务端
              import os
import json
import socket
import struct
def recv_data(conn, chunk_size=1024):
# 获取头部信息：数据长度
has_read_size = 0
bytes_list = []
while has_read_size < 4:
chunk = conn.recv(4 - has_read_size)
has_read_size += len(chunk)
bytes_list.append(chunk)
header = b"".join(bytes_list)
data_length = struct.unpack('i', header)[0]
# 获取数据
data_list = []
has_read_data_size = 0
while has_read_data_size < data_length:
size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
chunk = conn.recv(size)
data_list.append(chunk)
has_read_data_size += len(chunk)
data = b"".join(data_list)
return data
def recv_file(conn, save_file_name, chunk_size=1024):
save_file_path = os.path.join('files', save_file_name)
# 获取头部信息：数据长度
has_read_size = 0
bytes_list = []
while has_read_size < 4:
chunk = conn.recv(4 - has_read_size)
bytes_list.append(chunk)
has_read_size += len(chunk)
header = b"".join(bytes_list)
data_length = struct.unpack('i', header)[0]
# 获取数据
file_object = open(save_file_path, mode='wb')
has_read_data_size = 0
while has_read_data_size < data_length:
size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
chunk = conn.recv(size)
file_object.write(chunk)
file_object.flush()
has_read_data_size += len(chunk)
file_object.close()
def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# IP可复用
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
while True:
conn, addr = sock.accept()
while True:
# 获取消息类型
message_type = recv_data(conn).decode('utf-8')
if message_type == 'close': # 四次挥手，空内容。
print("关闭连接")
break
# 文件：{'msg_type':'file', 'file_name':"xxxx.xx" }
# 消息：{'msg_type':'msg'}
message_type_info = json.loads(message_type)
if message_type_info['msg_type'] == 'msg':
data = recv_data(conn)
print("接收到消息：", data.decode('utf-8'))
else:
file_name = message_type_info['file_name']
print("接收到文件，要保存到：", file_name)
recv_file(conn, file_name)
conn.close()
sock.close()
if __name__ == '__main__':
run()
      阻塞和非阻塞
        阻塞
          一般情况下都是阻塞的
          代码体现
            # ################### socket服务端（接收者）###################
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
# 阻塞
conn, addr = sock.accept()
# 阻塞
client_data = conn.recv(1024)
print(client_data.decode('utf-8'))
conn.close()
sock.close()
# ################### socket客户端（发送者） ###################
import socket
client = socket.socket()
# 阻塞
client.connect(('127.0.0.1', 8001))
client.sendall('alex正在吃翔'.encode('utf-8'))
client.close()
        非阻塞
          代码实现
            # ################### socket服务端（接收者）###################
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False) # 加上就变为了非阻塞
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
# 非阻塞
conn, addr = sock.accept()
# 非阻塞
client_data = conn.recv(1024)
print(client_data.decode('utf-8'))
conn.close()
sock.close()
# ################### socket客户端（发送者） ###################
import socket
client = socket.socket()
client.setblocking(False) # 加上就变为了非阻塞
# 非阻塞
client.connect(('127.0.0.1', 8001))
client.sendall('alex正在吃翔'.encode('utf-8'))
client.close()
          可用try来防止报 BlockingIOError的错误！
          一些注意的点
            如果代码变成了非阻塞，程序运行时一旦遇到 `accept`、`recv`、`connect` 就会抛出 BlockingIOError 的异常。
这不是代码编写的有错误，而是原来的IO阻塞变为非阻塞之后，由于没有接收到相关的IO请求抛出的固定错误。
非阻塞的代码一般与IO多路复用结合，可以迸发出更大的作用。
      IO多路复用
        服务端用于接受多个client的访问，用户端主要用于发送多个请求
        IO多路复用 + 阻塞
          服务端
            # ################### socket服务端 ###################
import select
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False) # 加上就变为了非阻塞
server.bind(('127.0.0.1', 8001))
server.listen(5)
inputs = [server, ] # socket对象列表 -> [server, 第一个客户端连接conn ]
while True:
# 当 参数1 序列中的socket对象发生可读时（accept和read），则获取发生变化的对象并添加到 r列表中。
# r = []
# r = [server,]
# r = [第一个客户端连接conn,]
# r = [server,]
# r = [第一个客户端连接conn，第二个客户端连接conn]
# r = [第二个客户端连接conn,]
r, w, e = select.select(inputs, [], [], 0.05)
for sock in r:
# server
if sock == server:
conn, addr = sock.accept() # 接收新连接。
print("有新连接")
# conn.sendall()
# conn.recv("xx")
inputs.append(conn)
else:
data = sock.recv(1024)
if data:
print("收到消息：", data)
else:
print("关闭连接")
inputs.remove(sock)
# 干点其他事 20s
"""
优点：
1. 干点那其他的事。
2. 让服务端支持多个客户端同时来连接。
"""
          客户端
            # ################### socket客户端 ###################
import socket
client = socket.socket()
# 阻塞
client.connect(('127.0.0.1', 8001))
while True:
content = input(">>>")
if content.upper() == 'Q':
break
client.sendall(content.encode('utf-8'))
client.close() # 与服务端断开连接（四次挥手），默认会想服务端发送空数据。
        IO多路复用 + 非阻塞
          可以实现让TCP的客户端同时发送多个请求
          客户端
            import socket
import select
import uuid
import os
client_list = [] # socket对象列表
for i in range(5):
client = socket.socket()
client.setblocking(False) #非阻塞
try:
# 连接百度，虽然有异常BlockingIOError，但向还是正常发送连接的请求
client.connect(('47.98.134.86', 80))
except BlockingIOError as e:
pass
client_list.append(client)
recv_list = [] # 放已连接成功，且已经把下载图片的请求发过去的socket
while True:
# w = [第一个socket对象,]
# r = [socket对象,]
r, w, e = select.select(recv_list, client_list, [], 0.1)
for sock in w:
# 连接成功，发送数据
# 下载图片的请求
sock.sendall(b"GET /nginx-logo.png HTTP/1.1\r\nHost:47.98.134.86\r\n\r\n")
recv_list.append(sock)
client_list.remove(sock)
for sock in r:
# 数据发送成功后，接收的返回值（图片）并写入到本地文件中
data = sock.recv(8196)
content = data.split(b'\r\n\r\n')[-1]
random_file_name = "{}.png".format(str(uuid.uuid4()))
with open(os.path.join("images", random_file_name), mode='wb') as f:
f.write(content)
recv_list.remove(sock)
if not recv_list and not client_list:
break
"""
优点：
1. 可以伪造除并发的现象。
"""
        好处
          基于 IO多路复用 + 非阻塞的特性，无论编写socket的服务端和客户端都可以提升性能。其中
- IO多路复用，监测socket对象是否有变化（是否连接成功？是否有数据到来等）。
- 非阻塞，socket的connect、recv过程不再等待。
注意：IO多路复用只能用来监听 IO对象 是否发生变化，常见的有：文件是否可读写、电脑终端设备输入和输出、网络请求（常见）。
        Linux操作系统化中的 IO多路复用 （现在没啥用）
          select
select最早于1983年出现在4.2BSD中，它通过一个select()系统调用来监视多个文件描述符的数组，当select()返回后，该数组中就绪的文件描述符便会被内核修改标志位，使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持，其良好跨平台支持也是它的一个优点，事实上从现在看来，这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制，在Linux上一般为1024，不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外，select()所维护的存储大量文件描述符的数据结构，随着文件描述符数量的增大，其复制的开销也线性增长。同时，由于网络响应时间的延迟使得大量TCP连接处于非活跃状态，但调用select()会对所有socket进行一次线性扫描，所以这也浪费了一定的开销。
poll
poll在1986年诞生于System V Release 3，它和select在本质上没有多大差别，但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是，包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间，而不论这些文件描述符是否就绪，它的开销随着文件描述符数量的增加而线性增大。
另外，select()和poll()将就绪的文件描述符告诉进程后，如果进程没有对其进行IO操作，那么下次调用select()和poll()的时候将再次报告这些文件描述符，所以它们一般不会丢失就绪的消息，这种方式称为水平触发（Level Triggered）。
epoll
直到Linux2.6才出现了由内核直接支持的实现方法，那就是epoll，它几乎具备了之前所说的一切优点，被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发（Edge Triggered，只告诉进程哪些文件描述符刚刚变为就绪状态，它只说一遍，如果我们没有采取行动，那么它将不会再次告知，这种方式称为边缘触发），理论上边缘触发的性能要更高一些，但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符，而且当我们调用epoll_wait()获得就绪文件描述符时，返回的不是实际的描述符，而是一个代表就绪描述符数量的值，你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可，这里也使用了内存映射（mmap）技术，这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中，进程只有在调用一定的方法后，内核才对所有监视的文件描述符进行扫描，而epoll事先通过epoll_ctl()来注册一个文件描述符，一旦基于某个文件描述符就绪时，内核会采用类似callback的回调机制，迅速激活这个文件描述符，当进程调用epoll_wait()时便得到通知。
          三种：select，poll，epoll
  并发编程
    进程和线程介绍
      线程，是计算机中可以被cpu调度的最小单元(真正在工作）。
进程，是计算机资源分配的最小单元（进程为线程提供资源）。
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
      多进程的开销比多线程大
      使用的选择
        - 计算密集型，用多进程，例如：大量的数据计算【累加计算示例】。
- IO密集型，用多线程，例如：文件读写、网络数据传输【下载抖音视频示例】。
        例子
          计算密集型
            import time
import multiprocessing
def task(start, end, queue):
result = 0
for i in range(start, end):
result += i
queue.put(result)
if __name__ == '__main__':
queue = multiprocessing.Queue()
start_time = time.time()
p1 = multiprocessing.Process(target=task, args=(0, 50000000, queue))
p1.start()
p2 = multiprocessing.Process(target=task, args=(50000000, 100000000, queue))
p2.start()
v1 = queue.get(block=True) #阻塞
v2 = queue.get(block=True) #阻塞
print(v1 + v2)
end_time = time.time()
print("耗时:", end_time - start_time) # 耗时: 2.6232550144195557
          IO密集型
            import multiprocessing
import threading
def thread_task():
pass
def task(start, end):
t1 = threading.Thread(target=thread_task)
t1.start()
t2 = threading.Thread(target=thread_task)
t2.start()
t3 = threading.Thread(target=thread_task)
t3.start()
if __name__ == '__main__':
p1 = multiprocessing.Process(target=task, args=(0, 50000000))
p1.start()
p2 = multiprocessing.Process(target=task, args=(50000000, 100000000))
p2.start()
    线程
      多线程开发
        概念
          - 一个**工厂**，创建一个**车间**，这个车间中创建 **3个工人**，并行处理任务。
- 一个**程序**，创建一个**进程**，这个进程中创建 **3个线程**，并行处理任务。
        代码实现
          import time
import requests
import threading
"""
def func(a1,a2,a3):
pass
t = threaing.Thread(target=func,args=(11,22,33))
t.start()
"""
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
def task(file_name, video_url):
res = requests.get(video_url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(time.time())
for name, url in url_list:
# 创建线程，让每个线程都去执行task函数（参数不同）
t = threading.Thread(target=task, args=(name, url))
t.start()
        常见方法
          t.start()
            当前线程准备就绪（等待CPU调度，具体时间是由CPU来决定
            代码
              import threading
loop = 10000000
number = 0
def _add(count):
global number
for i in range(count):
number += 1
t = threading.Thread(target=_add,args=(loop,))
t.start()
print(number)
          t.join()
            等待当前线程的任务执行完毕后再向下继续执行
            代码
              import threading
number = 0
def _add():
global number
for i in range(10000000):
number += 1
t = threading.Thread(target=_add)
t.start()
t.join() # 主线程等待中...
print(number)
          t.setDaemon(布尔值)
            守护线程（必须放在start之前）
            - `t.setDaemon(True)`，设置为守护线程，主线程执行完毕后，子线程也自动关闭。
- `t.setDaemon(False)`，设置为非守护线程，主线程等待子线程，子线程执行完毕后，主线程才结束。（默认）
            代码
              import threading
import time
def task(arg):
time.sleep(5)
print('任务')
t = threading.Thread(target=task, args=(11,))
t.setDaemon(True) # True/False
t.start()
print('END')
#输出
>>> END
          t.setName() / t,getName()
            线程名称的设置和获取
            代码
              import threading
def task(arg):
# 获取当前执行此代码的线程
name = threading.current_thread().getName()
print(name)
for i in range(10):
t = threading.Thread(target=task, args=(11,))
t.setName('日魔-{}'.format(i))
t.start()
          自定义线程类
            直接将线程需要做的事写到run方法中
            代码
              import threading
class MyThread(threading.Thread):
def run(self):
print('执行此线程', self._args) #源码里是这样的，所以加_
t = MyThread(args=(100,))
t.start()
            实例代码
              import requests
import threading
class DouYinThread(threading.Thread):
def run(self):
file_name, video_url = self._args
res = requests.get(video_url)
with open(file_name, mode='wb') as f:
f.write(res.content)
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
for item in url_list:
t = DouYinThread(args=(item[0], item[1]))
t.start()
      线程安全
        多个线程同时去操作一个"东西"，**可能**会存在数据混乱的情况
        出错的代码示例
          import threading
loop = 10000000
number = 0
def _add(count):
global number
for i in range(count):
number += 1
def _sub(count):
global number
for i in range(count):
number -= 1
t1 = threading.Thread(target=_add, args=(loop,))
t2 = threading.Thread(target=_sub, args=(loop,))
t1.start()
t2.start()
t1.join() # t1线程执行完毕,才继续往后走
t2.join() # t2线程执行完毕,才继续往后走
print(number)
        正确示例
          直接用
            import threading
num = 0
lock_object = threading.RLock()
def task():
print("开始")
lock_object.acquire() # 第1个抵达的线程进入并上锁，其他线程就需要再此等待。
global num
for i in range(1000000):
num += 1
lock_object.release() # 线程出去，并解开锁，其他线程就可以进入并执行了
print(num)
for i in range(2):
t = threading.Thread(target=task)
t.start()
          上下文管理器
            import threading
num = 0
lock_object = threading.RLock()
def task():
print("开始")
with lock_object: # 基于上下文管理，内部自动执行 acquire 和 release
global num
for i in range(1000000):
num += 1
print(num)
for i in range(2):
t = threading.Thread(target=task)
t.start()
        默认 线程安全
          在开发的过程中要注意有些操作默认都是 线程安全的（内部集成了锁的机制），我们在使用的时无需再通过锁再处理
          默认线程安全的一些方法
          示例代码
            import threading
data_list = []
lock_object = threading.RLock()
def task():
print("开始")
for i in range(1000000):
data_list.append(i)
print(len(data_list))
for i in range(2):
t = threading.Thread(target=task)
t.start()
      线程锁（GIL锁）
        Lock（同步锁/互斥锁）
          代码示例
            import threading
num = 0
lock_object = threading.Lock()
def task():
print("开始")
lock_object.acquire() # 第1个抵达的线程进入并上锁，其他线程就需要再此等待。
global num
for i in range(1000000):
num += 1
lock_object.release() # 线程出去，并解开锁，其他线程就可以进入并执行了
print(num)
for i in range(2):
t = threading.Thread(target=task)
t.start()
        RLock（递归锁）
          代码示例
            import threading
num = 0
lock_object = threading.RLock()
def task():
print("开始")
lock_object.acquire() # 第1个抵达的线程进入并上锁，其他线程就需要再此等待。
global num
for i in range(1000000):
num += 1
lock_object.release() # 线程出去，并解开锁，其他线程就可以进入并执行了
print(num)
for i in range(2):
t = threading.Thread(target=task)
t.start()
          RLock支持多次申请锁和多次释放；Lock不支持。
            代码示例
              import threading
import time
lock_object = threading.RLock()
def task():
print("开始")
lock_object.acquire()
lock_object.acquire()
print(123)
lock_object.release()
lock_object.release()
for i in range(3):
t = threading.Thread(target=task)
t.start()
            开发代码示例
              import threading
lock = threading.RLock()
# 程序员A开发了一个函数，函数可以被其他开发者调用，内部需要基于锁保证数据安全。
def func():
with lock:
pass
# 程序员B开发了一个函数，可以直接调用这个函数。
def run():
print("其他功能")
func() # 调用程序员A写的func函数，内部用到了锁。
print("其他功能")
# 程序员C开发了一个函数，自己需要加锁，同时也需要调用func函数。
def process():
with lock:
print("其他功能")
func() # ----------------> 此时就会出现多次锁的情况，只有RLock支持（Lock不支持）。
print("其他功能")
      死锁
        死锁，由于竞争资源或者由于彼此通信而造成的一种阻塞的现象
        示例代码
          Lock (只能锁一次的情况下却锁了两次及以上)
            import threading
num = 0
lock_object = threading.Lock()
def task():
print("开始")
lock_object.acquire() # 第1个抵达的线程进入并上锁，其他线程就需要再此等待。
lock_object.acquire() # 第1个抵达的线程进入并上锁，其他线程就需要再此等待。
global num
for i in range(1000000):
num += 1
lock_object.release() # 线程出去，并解开锁，其他线程就可以进入并执行了
lock_object.release() # 线程出去，并解开锁，其他线程就可以进入并执行了
print(num)
for i in range(2):
t = threading.Thread(target=task)
t.start()
          相互卡住
            import threading
import time
lock_1 = threading.Lock()
lock_2 = threading.Lock()
def task1():
lock_1.acquire()
time.sleep(1)
lock_2.acquire()
print(11)
lock_2.release()
print(111)
lock_1.release()
print(1111)
def task2():
lock_2.acquire()
time.sleep(1)
lock_1.acquire()
print(22)
lock_1.release()
print(222)
lock_2.release()
print(2222)
t1 = threading.Thread(target=task1)
t1.start()
t2 = threading.Thread(target=task2)
t2.start()
      线程池
        示例代码
          原始用法
            import time
from concurrent.futures import ThreadPoolExecutor
# pool = ThreadPoolExecutor(100)
# pool.submit(函数名,参数1，参数2，参数...)
def task(video_url,num):
print("开始执行任务", video_url)
time.sleep(5)
# 创建线程池，最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]
for url in url_list:
# 在线程池中提交一个任务，线程池中如果有空闲线程，则分配一个线程去执行，执行完毕后再将线程交还给线程池；如果没有空闲线程，则等待。
pool.submit(task, url,2)
print("END")
          等待线程池任务执行完成
            import time
from concurrent.futures import ThreadPoolExecutor
def task(video_url):
print("开始执行任务", video_url)
time.sleep(5)
# 创建线程池，最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(300)]
for url in url_list:
# 在线程池中提交一个任务，线程池中如果有空闲线程，则分配一个线程去执行，执行完毕后再将线程交还给线程池；如果没有空闲线程，则等待。
pool.submit(task, url)
print("执行中...")
pool.shutdown(True) # 等待线程池中的任务执行完毕后，在继续执行
print('继续往下走')
          执行完任务，再干点别的
            import time
import random
from concurrent.futures import ThreadPoolExecutor, Future
def task(video_url):
print("开始执行任务", video_url)
time.sleep(2)
return random.randint(0, 10)
def done(response):
print("任务执行后的返回值", response.result())
# 创建线程池，最多维护10个线程。
pool = ThreadPoolExecutor(10)
url_list = ["www.xxxx-{}.com".format(i) for i in range(15)]
for url in url_list:
# 在线程池中提交一个任务，线程池中如果有空闲线程，则分配一个线程去执行，执行完毕后再将线程交还给线程池；如果没有空闲线程，则等待。
future = pool.submit(task, url)
future.add_done_callback(done) # 是子主线程执行
# 可以做分工，例如：task专门下载，done专门将下载的数据写入本地文件。
      单例模式（扩展）
        用类来实现
        示例代码
          正常创建
            class Singleton:
instance = None
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
# 返回空对象
if cls.instance:
return cls.instance
cls.instance = object.__new__(cls)
return cls.instance
obj1 = Singleton('alex')
obj2 = Singleton('SB')
print(obj1,obj2)
          多线程执行单例模式，有BUG
            import threading
import time
class Singleton:
instance = None
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if cls.instance:
return cls.instance
time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
def task():
obj = Singleton('x')
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
#输出
<__main__.Singleton object at 0x000001FB5DE99790><__main__.Singleton object at 0x000001FB7F4611F0>
<__main__.Singleton object at 0x000001FB7F4612B0><__main__.Singleton object at 0x000001FB7F461310>
<__main__.Singleton object at 0x000001FB7F461160>
<__main__.Singleton object at 0x000001FB5DE99790>
<__main__.Singleton object at 0x000001FB7F452C10><__main__.Singleton object at 0x000001FB7F452AF0><__main__.Singleton object at 0x000001FB7F452B80>
<__main__.Singleton object at 0x000001FB7F452A00>
          加锁解决BUG
            import threading
import time
class Singleton:
instance = None
lock = threading.RLock()
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
with cls.lock:
if cls.instance:
return cls.instance
time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
def task():
obj = Singleton('x')
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
          加判断，提升性能
            import threading
import time
class Singleton:
instance = None
lock = threading.RLock()
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if cls.instance:
return cls.instance
with cls.lock:
if cls.instance:
return cls.instance
time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
def task():
obj = Singleton('x')
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
# 执行1000行代码
data = Singleton('asdfasdf')
print(data)
    进程
      多进程开发
        概念
          - 一个**工厂**，创建 **三个车间**，每个车间 **一个工人（共3人）**，并行处理任务。
- 一个**程序**，创建 **三个进程**，每个进程 **一个线程（共3人）**，并行处理任务。
        代码实现
          import time
import requests
import multiprocessing
# 进程创建之后，在进程中还会创建一个线程。
# t = multiprocessing.Process(target=函数名, args=(name, url))
# t.start()
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
def task(file_name, video_url):
res = requests.get(video_url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(time.time())
if __name__ == '__main__':
print(time.time())
for name, url in url_list:
t = multiprocessing.Process(target=task, args=(name, url))
t.start()
        常见方法
          p.start()
            当前进程准备就绪，等待被CPU调度（工作单元其实是进程中的线程）
          p.join()
            等待当前进程的任务执行完毕后再向下继续执行
            代码
              import time
from multiprocessing import Process
def task(arg):
time.sleep(2)
print("执行中...")
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
p = Process(target=task, args=('xxx',))
p.start()
p.join()
print("继续执行...")
          p.daemon = 布尔值
            守护进程（必须放在start之前）
            - `p.daemon =True`，设置为守护进程，主进程执行完毕后，子进程也自动关闭。
- `p.daemon =False`，设置为非守护进程，主进程等待子进程，子进程执行完毕后，主进程才结束。
            代码
              import time
from multiprocessing import Process
def task(arg):
time.sleep(2)
print("执行中...")
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
p = Process(target=task, args=('xxx',))
p.daemon = True
p.start()
print("继续执行...")
          p.name = xxx
            进程的名称的设置和获取
            代码
              import os
import time
import threading
import multiprocessing
def func():
time.sleep(3)
def task(arg):
for i in range(10):
t = threading.Thread(target=func)
t.start()
print(os.getpid(), os.getppid())
print("线程个数", len(threading.enumerate()))
time.sleep(2)
print("当前进程的名称：", multiprocessing.current_process().name)
if __name__ == '__main__':
print(os.getpid())
multiprocessing.set_start_method("spawn")
p = multiprocessing.Process(target=task, args=('xxx',))
p.name = "哈哈哈哈"
p.start()
print("继续执行...")
          自定义进程类
            直接将线程需要做的事写到run方法中
            代码
              import multiprocessing
class MyProcess(multiprocessing.Process):
def run(self):
print('执行此进程', self._args)
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
p = MyProcess(args=('xxx',))
p.start()
print("继续执行...")
          CPU个数（常常决定创建几个线程）
            代码
              import multiprocessing
if __name__ == '__main__':
count = multiprocessing.cpu_count()
for i in range(count - 1): #要除去主线程
p = multiprocessing.Process(target=xxxx)
p.start()
      进程数据共享
        正常情况下：进程是资源分配的最小单元，每个进程中都维护自己独立的数据，不共享
        共享的方法
          Value or Array
            数据类型
              'c': ctypes.c_char, 'u': ctypes.c_wchar,
'b': ctypes.c_byte, 'B': ctypes.c_ubyte,
'h': ctypes.c_short, 'H': ctypes.c_ushort,
'i': ctypes.c_int, 'I': ctypes.c_uint, （其u表示无符号）
'l': ctypes.c_long, 'L': ctypes.c_ulong,
'f': ctypes.c_float, 'd': ctypes.c_double
            代码示例
              Value
                from multiprocessing import Process, Value, Array
def func(n, m1, m2):
n.value = 888
m1.value = 'a'.encode('utf-8')
m2.value = "武"
if __name__ == '__main__':
num = Value('i', 666)
v1 = Value('c')
v2 = Value('u')
p = Process(target=func, args=(num, v1, v2))
p.start()
p.join()
print(num.value) # 888
print(v1.value) # a
print(v2.value) # 武
              Array
                from multiprocessing import Process, Value, Array
def f(data_array):
data_array[0] = 666
if __name__ == '__main__':
arr = Array('i', [11, 22, 33, 44]) # 数组：元素类型必须是int; 只能是这么几个数据。
p = Process(target=f, args=(arr,))
p.start()
p.join()
print(arr[:])
          Server process
            更常用
            代码示例
              from multiprocessing import Process, Manager
def f(d, l):
d[1] = '1'
d['2'] = 2
d[0.25] = None
l.append(666)
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
l = manager.list()
p = Process(target=f, args=(d, l))
p.start()
p.join()
print(d)
print(l)
        数据交换
          Queues
            类似于创建一个管道，让数据来传输。 放到该管道的数据是有顺序的，一个个放，一个个取.
            示意图
            示例代码
              import multiprocessing
def task(q):
for i in range(10):
q.put(i)
if __name__ == '__main__':
queue = multiprocessing.Queue()
p = multiprocessing.Process(target=task, args=(queue,))
p.start()
p.join()
print("主进程")
print(queue.get())
print(queue.get())
print(queue.get())
print(queue.get())
print(queue.get())
          Pipes
            类似于创建了两个管道，可以相互传输数据
            示意图
            示例代码
              import time
import multiprocessing
def task(conn):
time.sleep(1)
conn.send([111, 22, 33, 44])
data = conn.recv() # 阻塞
print("子进程接收:", data)
time.sleep(2)
if __name__ == '__main__':
parent_conn, child_conn = multiprocessing.Pipe()
p = multiprocessing.Process(target=task, args=(child_conn,))
p.start()
info = parent_conn.recv() # 阻塞
print("主进程接收：", info)
parent_conn.send(666)
      进程锁
        出现的地方
          如果多个进程抢占式去做某些操作时候，为了防止操作出问题，可以通过进程锁来避免。 （主要用在有进程之间数据共享的情况）
        出错的代码
          示例一
            import time
from multiprocessing import Process, Value, Array
def func(n, ):
n.value = n.value + 1
if __name__ == '__main__':
num = Value('i', 0)
for i in range(20):
p = Process(target=func, args=(num,))
p.start()
time.sleep(3)
print(num.value)
          示例二
            import time
from multiprocessing import Process, Manager
def f(d, ):
d[1] += 1
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
d[1] = 0
for i in range(20):
p = Process(target=f, args=(d,))
p.start()
time.sleep(3)
print(d)
          示例三
            import time
from multiprocessing import Process, Manager
def f(d, ):
d[1] += 1
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
d[1] = 0
for i in range(20):
p = Process(target=f, args=(d,))
p.start()
time.sleep(3)
print(d)
        示例代码
          示例一（spawn模式）
            import time
import multiprocessing
import os
def task(lock):
print("开始")
lock.acquire()
# 假设文件中保存的内容就是一个值：10
with open('f1.txt', mode='r', encoding='utf-8') as f:
current_num = int(f.read())
print(os.getpid(), "排队抢票了")
time.sleep(0.5)
current_num -= 1
with open('f1.txt', mode='w', encoding=' utf-8') as f:
f.write(str(current_num))
lock.release()
if __name__ == '__main__':
multiprocessing.set_start_method("spawn")
lock = multiprocessing.RLock()
process_list = []
for i in range(10):
p = multiprocessing.Process(target=task, args=(lock,))
p.start()
process_list.append(p)
# spawn模式，需要特殊处理。
for item in process_list:
item.join()
          示例二（fork模式）
            import time
import multiprocessing
def task(lock):
print("开始")
lock.acquire()
# 假设文件中保存的内容就是一个值：10
with open('f1.txt', mode='r', encoding='utf-8') as f:
current_num = int(f.read())
print("排队抢票了")
time.sleep(1)
current_num -= 1
with open('f1.txt', mode='w', encoding='utf-8') as f:
f.write(str(current_num))
lock.release()
if __name__ == '__main__':
multiprocessing.set_start_method('fork')
lock = multiprocessing.RLock()
for i in range(10):
p = multiprocessing.Process(target=task, args=(lock,))
p.start()
      进程池
        示例代码
          import time
from concurrent.futures import ProcessPoolExecutor
import multiprocessing
def task(num):
print("执行", num)
time.sleep(2)
return num
def done(res):
print(multiprocessing.current_process())
time.sleep(1)
print(res.result())
time.sleep(1)
if __name__ == '__main__':
pool = ProcessPoolExecutor(4)
for i in range(50):
fur = pool.submit(task, i)
# done的调用由主进程处理（与线程池不同）
fur.add_done_callback(done)
print(multiprocessing.current_process())
#等待进程池中的任务都执行完毕后，再继续往后执行。
pool.shutdown(True)
        若要用锁，则必须用Manager中的Lock和Rlock
          import time
import multiprocessing
from concurrent.futures.process import ProcessPoolExecutor
def task(lock):
print("开始")
# lock.acquire()
# lock.relase()
with lock:
# 假设文件中保存的内容就是一个值：10
with open('f1.txt', mode='r', encoding='utf-8') as f:
current_num = int(f.read())
print("排队抢票了")
time.sleep(1)
current_num -= 1
with open('f1.txt', mode='w', encoding='utf-8') as f:
f.write(str(current_num))
if __name__ == '__main__':
pool = ProcessPoolExecutor()
# lock_object = multiprocessing.RLock() # 不能使用
manager = multiprocessing.Manager()
lock_object = manager.RLock() # Lock
for i in range(10):
pool.submit(task, lock_object)
    协程
      初识协程
        协程也可以被称为微线程，是一种用户态内的上下文切换技术。
简而言之，其实就是通过一个线程实现代码块相互切换执行（来回跳着执行）。
      协程的实现
        greenlet
          from greenlet import greenlet
def func1():
print(1) # 第2步：输出 1
gr2.switch() # 第3步：切换到 func2 函数
print(2) # 第6步：输出 2
gr2.switch() # 第7步：切换到 func2 函数，从上一次执行的位置继续向后执行
def func2():
print(3) # 第4步：输出 3
gr1.switch() # 第5步：切换到 func1 函数，从上一次执行的位置继续向后执行
print(4) # 第8步：输出 4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 第1步：去执行 func1 函数
        yield
          def func1():
yield 1
yield from func2()
yield 2
def func2():
yield 3
yield 4
f1 = func1()
for item in f1:
print(item)
      协程的意义
        使用的常见场景
          不要让用户手动去切换，而是遇到IO操作时能自动切换。
        基于asyncio ↓
        示例代码
          import asyncio
async def func1():
print(1)
await asyncio.sleep(2)
print(2)
async def func2():
print(3)
await asyncio.sleep(2)
print(4)
tasks = [
asyncio.ensure_future(func1()),
asyncio.ensure_future(func2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
    协程、线程、进程的区别
      线程，是计算机中可以被cpu调度的最小单元。
进程，是计算机资源分配的最小单元（进程为线程提供资源）。
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
由于CPython中GIL的存在：
- 线程，适用于IO密集型操作。
- 进程，适用于计算密集型操作。
协程，协程也可以被称为微线程，是一种用户态内的上下文切换技术，在开发中结合遇到IO自动切换，就可以通过一个线程实现并发操作。
所以，在处理IO操作时，协程比线程更加节省开销（协程的开发难度大一些）。
  补充的知识点
    案例1：多线程socket服务端
      基于多线程实现socket服务端，实现同时处理多个客户端的请求。
      服务端
        import socket
import threading
def task(conn):
while True:
client_data = conn.recv(1024)
data = client_data.decode('utf-8')
print("收到客户端发来的消息：", data)
if data.upper() == "Q":
break
conn.sendall("收到收到".encode('utf-8'))
conn.close()
def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
while True:
# 等待客户端来连接（主线程）
conn, addr = sock.accept()
# 创建子线程
t = threading.Thread(target=task, args=(conn,))
t.start()
sock.close()
if __name__ == '__main__':
run()
      客户端
        import socket
# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1 ', 8001))
while True:
txt = input(">>>")
client.sendall(txt.encode('utf-8'))
if txt.upper() == 'Q':
break
reply = client.recv(1024)
print(reply.decode("utf-8"))
# 关闭连接，关闭连接时会向服务端发送空数据。
client.close()
    案例2：多进程socket服务端
      基于多进程实现socket服务端，实现同时处理多个客户端的请求。
      客户端
        import socket
# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8001))
while True:
txt = input(">>>")
client.sendall(txt.encode('utf-8'))
if txt.upper() == 'Q':
break
reply = client.recv(1024)
print(reply.decode("utf-8"))
# 关闭连接，关闭连接时会向服务端发送空数据。
client.close()
      服务端
        import socket
import multiprocessing
def task(conn):
while True:
client_data = conn.recv(1024)
data = client_data.decode('utf-8')
print("收到客户端发来的消息：", data)
if data.upper() == "Q":
break
conn.sendall("收到收到".encode('utf-8'))
conn.close()
def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))
sock.listen(5)
while True:
# 等待客户端来连接
conn, addr = sock.accept()
# 创建了子进程（至少有个线程）
t = multiprocessing.Process(target=task, args=(conn,))
t.start()
sock.close()
if __name__ == '__main__':
run()
    并发和并行
      串行
        串行，多个任务排队按照先后顺序逐一去执行。
      并发
        并发，假设有多个任务，只有一个CPU，那么在同一时刻只能处理一个任务，为了避免串行，可以让将任务切换运行(每个任务运行一点，然后再切换），达到并发效果（看似都在同时运行）。
        并发在Python代码中体现：协程、多线程（由CPython的GIL锁限制，多个线程无法被CPU调度）。
      并行
        并行，假设有多个任务，有多个CPU，那么同一时刻每个CPU都是执行一个任务，任务就可以真正的同时运行。
        并行在Python代码中的体现：多进程。
    单例模式
      基于`__new__`方法实现
        import threading
import time
class Singleton:
instance = None
lock = threading.RLock()
def __init__(self):
self.name = "武沛齐"
def __new__(cls, *args, **kwargs):
if cls.instance:
return cls.instance
with cls.lock:
if cls.instance:
return cls.instance
# time.sleep(0.1)
cls.instance = object.__new__(cls)
return cls.instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1 is obj2) # True
      基于模块导入方式
        # utils.py
class Singleton:
def __init__(self):
self.name = "武沛齐"
...
single = Singleton()
        from xx import single
print(single)
from xx import single
print(single)