Python面向对象进阶和socket网络编程-day08

写在前面




上课第八天,打卡:

    为什么坚持?想一想当初;



一、面向对象进阶

  - 1.反射补充

    - 通过字符串去操作一个对象的属性,称之为反射

    - 示例1:

class Chinese:
    def __init__(self,name):
        self.name=name
p = Chinese(‘standby‘)      # 实例化一个对象
print(p)                    # 打印这个对象

---
<__main__.Chinese object at 0x0000000000B3A978>

    - 示例2:

>>> l = list([1,2,3])       # 示例话一个对象(list 也是一个类)
>>> print(l)                # 打印这个对象,显然和示例1的对象结果不一样;显然是list类对这个对象l做了手脚
[1, 2, 3]
>>>

    - 示例3:__str__ 函数必须有返回值,且必须返回字符串类型

class Chinese:
    def __init__(self,name):
        self.name=name
    def __str__(self):      # 在示例1的基础上,重写 __str__ 函数,实现和示例2同样的效果;
        return self.name
p = Chinese(‘standby‘)
print(p)

---
standby

  - 2.__setattr__,__delattr__,__getattr__

__setattr__,__delattr__,__getattr__    #  这些函数默认都是有的,自己可以重写; 本质都是直接操作 obj.__dict__

__setattr__      # 添加/修改属性会触发它的执行
__delattr__      # 删除属性的时候触发执行
__getattr__      # 只有在使用点调用属性且属性不存在的时候才会触发

obj.attr    ‘.‘    就会触发 __setattr__,__delattr__,__getattr__ 这些函数的执行

    - 示例1:__setattr__ 和 __delattr__

 1 class Foo:
 2     def __init__(self,value):
 3         self.x = value        # 等价于 self.__dict__[‘x‘] = value
 4     def __setattr__(self, key, value):
 5         print(‘----> from setattr‘)
 6         # self.key=value      # 这就无限递归了!!!
 7         # self.__dict__[key]=value #应该使用它
 8         self.__dict__[key] = value     # 本质上: f.__dict__[‘x‘] = 100,即给f这个对象设置了属性值;
 9     def __getattr__(self, item):
10         print(‘++++> from getattr‘)
11     def __delattr__(self, item):
12         print(‘====> from delattr‘)
13         self.__dict__.pop(item)
14
15 f = Foo(100)          # 本质上就是在执行:Foo.__init__(f,100),然后执行里面的赋值语句:f.x = 100;设置了属性x的值,所以会自动调用 __setattr__函数
16 print(‘-------->>>‘)
17 print(f.__dict__)     # 查看对象f的属性字典
18 del f.x               # 删除f的x属性,这一步触发了 __delattr__函数,然后执行了 f.__dict__.pop(‘x‘)
19 print(f.__dict__)     # 查看删除后的属性字典
20 f.y = ‘hello‘         # 给对象 f 设置新的属性,触发了 __setattr__函数
21 f.z = 99              # 同上
22 print(f.__dict__)     # 在查看对象f的属性字典
23
24 ---结果:---
25 ----> from setattr
26 -------->>>
27 {‘x‘: 100}
28 ====> from delattr
29 {}
30 ----> from setattr
31 ----> from setattr
32 {‘z‘: 99, ‘y‘: ‘hello‘}

    - 示例2:__getattr__

 1 class Foo:
 2     def __init__(self,value):
 3         self.x = value   # 等价于 self.__dict__[‘x‘] = value
 4     def __setattr__(self, key, value):
 5         print(‘----> from setattr‘)
 6         # self.key=value #这就无限递归了,你好好想想
 7         # self.__dict__[key]=value #应该使用它
 8         self.__dict__[key] = value
 9     def __getattr__(self, item):
10         print(‘++++> from getattr‘)
11     def __delattr__(self, item):
12         print(‘====> from delattr‘)
13         self.__dict__.pop(item)
14
15 f = Foo(100)
16 print(f.__dict__)     # 查看对象 f 的属性字典
17 num = f.y             # 调用对象f的y属性,实际上是没有这个属性,所以触发了 __getattr__ 函数的执行;
18
19 ---结果---
20 ----> from setattr
21 {‘x‘: 100}
22 ++++> from getattr

  - 3.__setitem__,__getitem__,__delitem__

    - 按照字典的 key-value 方式进行操作;

    - 应用:把对对象属性操作的方法和字典类对象的操作统一起来,做一个统一的接口;

    - 示例1:

1 class Foo:
2     def __init__(self,name):
3         self.name = name
4
5 f = Foo(‘standby‘)
6 print(f.name)
7 # print(f[‘name‘])  # 这么调用会报错:TypeError: ‘Foo‘ object is not subscriptable

    - 示例2: __getitem__

 1 class Foo:
 2     def __init__(self,name):
 3         self.name = name
 4     def __getitem__(self, item):
 5         print(‘__getitem__ is called.‘)
 6         return self.__dict__[item]
 7 f = Foo(‘standby‘)
 8 print(f.name)        # 直接打印 standby
 9 print(f[‘name‘])     # 触发了 __getitem__ 函数的执行
10
11 ---
12 standby
13 __getitem__ is called.
14 standby

    - 示例3:__setitem__ 和 __delitem__

 1 class Foo:
 2     def __init__(self,name,age):
 3         self.name = name
 4         self.age = age
 5     def __setitem__(self, key, value):
 6         print(‘__setitem__ is called.‘)
 7         self.__dict__[key] = value
 8     def __delitem__(self, key):
 9         print(‘__delitem__ is called.‘)
10         self.__dict__.pop(key)
11     def __delattr__(self, item):
12         print(‘__delattr__ is called.‘)
13         self.__dict__.pop(item)
14
15 f = Foo(‘standby‘,18)       # 实例化一个对象
16 print(f.__dict__)
17 f.name = ‘standby666‘       # 通过 ‘obj.attr‘ 的方式设置对象的name属性
18 print(f.__dict__)
19 f[‘name‘] = ‘liulixin‘      # 通过字典的方式设置对象的name属性,触发了 __setitem__ 函数的执行
20 print(f.__dict__)
21 del f.age                   # 触发了 __delattr__ 函数的执行
22 print(f.__dict__)
23 del f[‘name‘]               # 通过字典的形式删除属性,触发了 __delitem__函数的执行
24 print(f.__dict__)
25
26 ---
27 {‘name‘: ‘standby‘, ‘age‘: 18}
28 {‘name‘: ‘standby666‘, ‘age‘: 18}
29 __setitem__ is called.
30 {‘name‘: ‘liulixin‘, ‘age‘: 18}
31 __delattr__ is called.
32 {‘name‘: ‘liulixin‘}
33 __delitem__ is called.
34 {}

    - 应用:做成统一接口

 1 # 不用 __setitem__ 的情况
 2 class Foo:
 3     def __init__(self,name):
 4         self.name = name
 5     def __setitem__(self, key, value):
 6         print(‘__setitem__ is called.‘)
 7         self.__dict__[key] = value
 8 f = Foo(‘standby‘)
 9
10 # 没有 __setitem__ 方法的话,就得这样写:
11 def func(obj,key,value):
12     if isinstance(obj,dict):
13         obj[key] = value
14     else:
15         setattr(obj,key,value)
16 print(f.__dict__)
17 func(f,‘name‘,‘liulixin‘)
18 print(f.__dict__)
19
20 ---
21 {‘name‘: ‘standby‘}
22 {‘name‘: ‘liulixin‘}
 1 # 用 __setitem__ 的情况
 2 class Foo:
 3     def __init__(self,name):
 4         self.name = name
 5     def __setitem__(self, key, value):
 6         print(‘__setitem__ is called.‘)
 7         self.__dict__[key] = value
 8 f = Foo(‘standby‘)
 9
10 # 有 __setitem__ 方法,就可以统一成如下接口:
11 def func(obj,key,value):
12     obj[key] = value
13 print(f.__dict__)
14 func(f,‘name‘,‘liulixin‘)
15 print(f.__dict__)
16
17 ---
18 {‘name‘: ‘standby‘}
19 __setitem__ is called.
20 {‘name‘: ‘liulixin‘}

  - 4.二次加工标准类(包装)

    - 利用类的继承和派生实现

python为大家提供了标准数据类型,以及丰富的内置方法;

其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法;

这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

      - 示例:列表类的继承与派生

 1 # 重写一个List类,继承了原来的 list类
 2 class List(list):
 3     def __init__(self,item,flag=False):
 4         super().__init__(item)
 5         self.flag = flag
 6     # 重写了list的 append函数,要去在append前判断元素类型,只有是str才可以append
 7     def append(self,item):
 8         if not isinstance(item,str):
 9             raise TypeError(‘%s must be str‘ % item)
10         # super().append(item)
11         super().append(item)
12     # 返回列表的中间元素
13     @property
14     def mid(self):
15         mid_index = len(self)//2
16         return self[mid_index]
17     # 清空列表,操作之前需要判断权限标识位:flag
18     def clear(self):
19         if not self.flag:
20             raise PermissionError(‘Permission Deny!‘)
21         super().clear()
22         self.flag = False
23
24 l = List([1,2,3,])
25 print(l)
26 # l.append(9)      # 报错:TypeError: 9 must be str
27 l.append(‘hello‘)  # 派生
28 print(l)
29 l.insert(0,7777)   # 继承原来list的insert方法
30 print(l)
31 print(l.mid)       # 调用property
32 # l.clear()        # 报错:PermissionError: Permission Deny!
33 l.flag = True
34 l.clear()          # 派生
35 print(l)
36
37 ---结果---
38 [1, 2, 3]
39 [1, 2, 3, ‘hello‘]
40 [7777, 1, 2, 3, ‘hello‘]
41 2
42 []

    - 授权方式(函数)实现

授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制;

这种做法可以新建、修改或删除原有产品的功能,其它的则保持原样。

应用:针对某些功能不是类,例如 open(),是一个函数,而不是类:
    如果要定制自己的open()函数,就不能用继承的方式了;

授权的过程:
    即是所有更新的功能都是由新类的某部分来处理;
    但已存在的功能就授权给对象的默认属性;

实现授权的关键点就是覆盖__getattr__方法

      - 示例:open函数的授权,关键在与 __getattr__函数的实现

 1 import time
 2 class Open():
 3     def __init__(self,filepath,mode=‘r‘,encoding=‘utf-8‘):
 4         self.filepath = filepath
 5         self.mode = mode
 6         self.encoding = encoding
 7         # 本质上还是调用原来的 open()函数打开文件
 8         self.f = open(self.filepath,mode=self.mode,encoding=self.encoding)
 9     # 这里,我们重写了write函数,在写入的内容前加上了操作的时间
10     def write(self,msg):
11         t = time.strftime(‘%Y-%m-%d %X‘)
12         self.f.write(‘%s : %s‘ % (t,msg))
13     # 找不到 attr 就会执行 __getattr__函数
14     # 利用这个特性,拿到 self.f.item (read、close、seek...)
15     def __getattr__(self, item):
16         print(‘--->>> %s‘ % item)
17         return getattr(self.f,item)
18 obj = Open(r‘1.txt‘,mode=‘w+‘,encoding=‘utf-8‘)
19 obj.write(‘11111\n‘)
20 obj.write(‘aaa\n‘)
21 obj.write(‘+++1213\n‘)
22 obj.seek(0)                     # Open类里没有重写seek,read以及close函数,所以在通过 ‘obj.attr‘ 方式调用的时候,就会触发 __getattr__函数;
23 res = obj.read()                # getattr(self.f,item) 即调用了真是的open函数的句柄: obj.f.read()
24 print(res)
25 print(‘----------------->>>‘)
26 print(type(obj),obj)            # obj 是 Open类的对象
27 print(type(obj.f),obj.f)        # obj.f 才是文件句柄,即原来的open()的对象
28 obj.close()                     # obj.f.close()
29
30 ---结果---
31 --->>> seek
32 --->>> read
33 2017-06-20 22:48:06 : 11111
34 2017-06-20 22:48:06 : aaa
35 2017-06-20 22:48:06 : +++1213
36
37 ----------------->>>
38 <class ‘__main__.Open‘> <__main__.Open object at 0x00000000010824E0>
39 <class ‘_io.TextIOWrapper‘> <_io.TextIOWrapper name=‘1.txt‘ mode=‘w+‘ encoding=‘utf-8‘>
40 --->>> close

  - 5.__str__,__repr__,__format__

str函数或者print函数--->obj.__str__()

repr或者交互式解释器--->obj.__repr__()

如果__str__没有被定义,那么就会使用__repr__来代替输出

注意:这俩方法的返回值必须是字符串,否则抛出异常;

    - __str__ 和 __repr__

      - 改变对象的字符串显示;

      - 示例1:没有定义 __str__,定义了 __repr__,则会用 __repr__来代替输出

 1 class School:
 2     def __init__(self,name,addr,type):
 3         self.name=name
 4         self.addr=addr
 5         self.type=type
 6     def __repr__(self):
 7         return ‘School(%s,%s)‘ %(self.name,self.addr)
 8 s1=School(‘Peking University‘,‘北京‘,‘公立‘)
 9 print(‘from repr: ‘,repr(s1))
10 print(‘from str: ‘,str(s1))
11 print(s1)
12
13 ---
14 from repr:  School(Peking University,北京)
15 from str:  School(Peking University,北京)
16 School(Peking University,北京)

      - 示例2:__str__ 和 __repr__ 都定义了的情况

 1 class School:
 2     def __init__(self,name,addr,type):
 3         self.name=name
 4         self.addr=addr
 5         self.type=type
 6     def __repr__(self):
 7         return ‘School(%s,%s)‘ %(self.name,self.addr)
 8     def __str__(self):
 9         return ‘(%s,%s)‘ %(self.name,self.addr)
10
11 s1=School(‘Peking University‘,‘北京‘,‘公立‘)
12 print(‘from repr: ‘,repr(s1))   # 触发 __repr__
13 print(‘from str: ‘,str(s1))     # 触发 __str__
14 print(s1)                       # 触发 __str__
15
16 ---
17 from repr:  School(Peking University,北京)
18 from str:  (Peking University,北京)
19 (Peking University,北京)

    - __format__

      - 自定义格式化字符串;

 1 format_dict={
 2     ‘nat‘:‘{obj.name}-{obj.addr}-{obj.type}‘,#学校名-学校地址-学校类型
 3     ‘tna‘:‘{obj.type}:{obj.name}:{obj.addr}‘,#学校类型:学校名:学校地址
 4     ‘tan‘:‘{obj.type}/{obj.addr}/{obj.name}‘,#学校类型/学校地址/学校名
 5 }
 6 class School:
 7     def __init__(self,name,addr,type):
 8         self.name=name
 9         self.addr=addr
10         self.type=type
11     def __format__(self, format_spec):
12         if not format_spec or format_spec not in format_dict:
13             format_spec=‘nat‘
14         fmt=format_dict[format_spec]
15         return fmt.format(obj=self)
16 s1=School(‘Peking University‘,‘北京‘,‘公立‘)
17 print(format(s1,‘nat‘))
18 print(format(s1,‘tna‘))
19 print(format(s1,‘tan‘))
20 print(format(s1,‘asfdasdffd‘))
21
22 ---
23 Peking University-北京-公立
24 公立:Peking University:北京
25 公立/北京/Peking University
26 Peking University-北京-公立

  - 6.__next__ 和 __iter__ 实现迭代器协议

重写 __next__ 和 __iter__ 实现迭代器协议

迭代器协议:必须具有 __next__ 和 __iter__

可迭代对象有 __iter__ 方法,执行__iter__方法得到的就是迭代器

    - 示例:

 1 from collections import Iterator
 2 class Foo:
 3     def __init__(self,n,end):
 4         self.n = n
 5         self.end = end
 6     def __next__(self):
 7         if self.n >= self.end:
 8             raise StopIteration   # 因为for循环捕捉的就是StopIteration,所以需要在迭代到边界值的时候抛出这个异常;
 9         tmp = self.n
10         self.n += 1
11         return tmp
12     def __iter__(self):
13         return self
14 f = Foo(5,10)
15 print(isinstance(f,Iterator))     # 如果Foo类中不写 __iter__ 函数就不是迭代器
16 print(next(f))                    # next(f) 等价于 f.__next__()
17 print(next(f))
18 print(‘--->‘)
19 for item in f:                    # 会捕捉到 StopIteration 异常,如果不加限制会导致无限递归!!!
20     print(item)
21
22 ---结果---
23 True
24 5
25 6
26 --->
27 7
28 8
29 9

    - 应用:斐波那契数列

class Fibonacci:
    def __init__(self):
        self._a = 0
        self._b = 1
    def __iter__(self):
        return self
    def __next__(self):
        self._a,self._b = self._b,self._a + self._b
        return self._a
fib = Fibonacci()
for i in fib:
    if i > 100:
        break
    print(‘%s ‘ % i, end=‘‘)

---结果---
1 1 2 3 5 8 13 21 34 55 89 

  - 7.__del__ (析构函数)

    - 当对象在内存中被释放时,自动触发执行;(关闭到跟对象有关的链接,做一些清理操作

    - 此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行;

    - 所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的;

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __del__(self): #析构方法
        print(‘执行了析构函数...‘)
obj=Foo(‘egon‘,18)
del obj.name
print(‘=============>‘)
del obj
print(‘=============>‘)

---结果---
=============>
执行了析构函数...
=============>

  - 8.__enter__ 和 __exit__ 实现上下文管理

    - 我们知道在操作文件对象的时候可以这么写:

with open(‘a.txt‘) as f:
    ‘代码块‘

    - 上述叫做上下文管理协议,即with语句;

    - 为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

    - __exit__() 中的三个参数分别代表 异常类型,异常值和追溯信息,  with语句中代码块出现异常,则with后的代码都无法执行

    - 示例1:

 1 class Foo:
 2     def __init__(self, name):
 3         self.name = name
 4     def __enter__(self):
 5         print(‘__enter__‘)
 6         return self
 7     def __exit__(self, exc_type, exc_val, exc_tb):
 8         print(‘__exit__‘)
 9 with Foo(‘standby‘) as x: # 出现 with语句,则对象的 __enter__函数被触发,返回值则赋给as声明的变量
10     print(x)
11     print(‘=>‘)
12     print(‘==>‘)
13     print(‘===>‘)         # with里的代码块执行完毕,则触发 __exit__函数执行
14
15 ---
16 __enter__
17 <__main__.Foo object at 0x00000000007334E0>
18 =>
19 ==>
20 ===>
21 __exit__

    - 示例2: __exit__ 里不加 return True

 1 class Open:
 2     def __init__(self,name,mode=‘r‘,encoding=‘utf-8‘):
 3         self.name = name
 4         self.mode = mode
 5         self.encoding = encoding
 6         self.f = open(self.name,mode=self.mode,encoding=self.encoding)
 7     def __enter__(self):
 8         print(‘__enter__‘)
 9         return self.f
10     def __exit__(self, exc_type, exc_val, exc_tb):
11         print(‘__exit__‘)
12         print(exc_type)
13         print(exc_val)
14         print(exc_tb)
15         self.f.close()
16 with Open(‘666.txt‘,mode=‘w+‘) as f:
17     print(type(f),f)
18     1/0
19 print(‘The end.‘)        # 1/0 异常抛出, 导致 with后的代码没有执行;
20
21 ---
22 __enter__
23 <class ‘_io.TextIOWrapper‘> <_io.TextIOWrapper name=‘666.txt‘ mode=‘w+‘ encoding=‘utf-8‘>
24 __exit__
25 <class ‘ZeroDivisionError‘>
26 division by zero
27 <traceback object at 0x00000000010615C8>
28 Traceback (most recent call last):
29   File "D:/soft/work/Python_17/day08/blog.py", line 301, in <module>
30     1/0
31 ZeroDivisionError: division by zero

    - 示例3: __exit__ 末尾有 return True

 1 class Open:
 2     def __init__(self,name,mode=‘r‘,encoding=‘utf-8‘):
 3         self.name = name
 4         self.mode = mode
 5         self.encoding = encoding
 6         self.f = open(self.name,mode=self.mode,encoding=self.encoding)
 7     def __enter__(self):
 8         print(‘__enter__‘)
 9         return self.f
10     def __exit__(self, exc_type, exc_val, exc_tb):
11         print(‘__exit__‘)
12         print(exc_type)
13         print(exc_val)
14         print(exc_tb)
15         self.f.close()
16         return True
17 with Open(‘666.txt‘,mode=‘w+‘) as f:   # Open(...) 触发 __init__的执行, with Open(...) 触发 __enter__的执行
18     print(type(f),f)
19     1/0                 # 1/0 抛出异常,但是由于 __exit__末尾 ‘return True‘,导致异常被清空了,所以后面的代码都可以照常执行;
20 print(‘The end.‘)
21
22 ---
23 __enter__
24 <class ‘_io.TextIOWrapper‘> <_io.TextIOWrapper name=‘666.txt‘ mode=‘w+‘ encoding=‘utf-8‘>
25 __exit__
26 <class ‘ZeroDivisionError‘>
27 division by zero
28 <traceback object at 0x00000000010925C8>
29 The end.

  - 9.__call__

    - 对象后面加括号,触发执行对象所属的类的 __call__方法执行(实现对象加括号,变成可调用的)

    - 所有的可调用,到最后都要转化成函数(__call__)去运行;

    - 构造方法的执行是由创建对象触发的,即:对象 = 类名() ;

    - 而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象()  或者  类()()

    - 示例1:

 1 class Foo:
 2     def __init__(self):
 3         print(‘Foo.__init__ is called.‘)
 4         print(type(self),self)
 5     def __call__(self, *args, **kwargs):
 6         print(‘Foo.__call__ is called.‘)
 7         print(type(self), self)
 8
 9 print(‘--->>>‘)
10 Foo()                # 相当于实例化了一个对象
11 print(‘---+++‘)
12 Foo()()              # 1.Foo()得到一个对象obj    2.obj加括号执行(obj()),触发了__call__函数
13
14 ---
15 --->>>
16 Foo.__init__ is called.
17 <class ‘__main__.Foo‘> <__main__.Foo object at 0x000000000103A898>
18 ---+++
19 Foo.__init__ is called.
20 <class ‘__main__.Foo‘> <__main__.Foo object at 0x000000000103A898>
21 Foo.__call__ is called.
22 <class ‘__main__.Foo‘> <__main__.Foo object at 0x000000000103A898>

    - 示例2:

 1 class Foo:
 2     def __init__(self):
 3         print(‘Foo.__init__ is called.‘)
 4         print(type(self),self)
 5     def __call__(self, *args, **kwargs):
 6         print(‘Foo.__call__ is called.‘)
 7         print(type(self), self)
 8
 9 obj = Foo()               # 执行 Foo.__init__(obj)
10 obj()                     # 执行 Foo.__call__(obj)
11 print(type(obj),obj)
12 print(type(Foo),Foo)
13
14 ---
15 Foo.__init__ is called.
16 <class ‘__main__.Foo‘> <__main__.Foo object at 0x00000000007CA978>
17 Foo.__call__ is called.
18 <class ‘__main__.Foo‘> <__main__.Foo object at 0x00000000007CA978>
19 <class ‘__main__.Foo‘> <__main__.Foo object at 0x00000000007CA978>
20 <class ‘type‘> <class ‘__main__.Foo‘>

    - 示例3:

 1 class Foo:
 2     def __call__(self, *args, **kwargs):
 3         print(‘======>‘)
 4
 5 obj=Foo()                 # obj是Foo类实例化得到,是Foo类的对象
 6 print(‘++++++++‘)
 7 obj()                     # obj(),即 ‘对象+()‘ 执行的时候就是触发了其所属的类的 __call__方法的执行;
 8 print(type(Foo),Foo)
 9 print(type(obj),obj)
10
11 ---
12 ++++++++
13 ======>
14 <class ‘type‘> <class ‘__main__.Foo‘>
15 <class ‘__main__.Foo‘> <__main__.Foo object at 0x0000000000B0A940>

  - 10.metaclass(元类)

    - 1.exec和eval介绍

    - 2.创造类的两种方法

      

    - 3.创建类相关的三个方法

       - 1.__call__

        - 2.__new__

      - 3.__init__

  

    - 4.元类(metaclass)

关于元类,就把握一个原则:

A() 找的就是产生A的那个类的 __call__方法

__call__() 做三件事:
  1.__new__ 造一个对象
  2.__init__ 初始化这个对象
  3.返回这个对象

  

    

更多细节参见:egon老师面向对象进阶

二、socket网络编程

参考:python基础之socket编程

  - 1.网络基础

  - 2.socket是什么?

  - 3.socket套接字分类

  - 4.socket套接字工作流程

  - 5.基于TCP的套接字

  - 6.基于UDP的套接字

  - 7.recv与recvfrom的区别

  - 8.粘包的产生和解决办法

    - 粘包产生原因

      - socket收发消息的原理示意图:

      - 粘包原因

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据;也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。

而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。

怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

      - 扩展

此外,发送方引起的粘包是由TCP协议本身造成的:
TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。
若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据;

1.TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。

2.UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务,不会使用块的合并优化算法;由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息);这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。

3.tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住;而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头;

      - 1.只有TCP有粘包现象,UDP永远不会粘包;

      - 2.所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性应该提取多少字节的数据所造成的;

      - 3.TCP是面向连接的、面向流的;面向流的通信是无消息保护边界的;

      - 4.UDP是无连接的、面向消息的;面向消息的通讯是有消息保护边界的;

    - 两种情况下会产生粘包:

      - 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包);

      - 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包);

    - 粘包示例

      - Server端

 1 #!/usr/bin/python3
 2 # -*- coding:utf-8 -*-
 3
 4 import time
 5 import socket
 6 import subprocess
 7 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 8 # Address already in use
 9 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
10 phone.bind((‘127.0.0.1‘,8090))
11 phone.listen(10)  # tcp 半连接池
12
13 while True:
14     print(‘Starting...‘)
15     conn,addr = phone.accept()
16     print(‘conn is %s‘ % conn)
17     print(‘addr is:‘, addr)
18
19     print(‘Ready to reveice msg...‘)
20     while True:
21         try:
22             cmd = conn.recv(1024)  # 写死每次取1024容易造成粘包现象
23             if not cmd: # for linux platform
24                 break
25             t = time.strftime(‘%Y-%m-%d %X‘)
26             print(‘Time: %s\tClient CMD: %s‘ %(t, cmd.decode(‘utf-8‘)))
27             res = subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,
28                                    stdout=subprocess.PIPE,
29                                    stderr=subprocess.PIPE)
30             err = res.stderr.read()
31             if err:
32                 cmd_res = err                  # 如果执行出错,则返回错误信息
33             else:
34                 cmd_res = res.stdout.read()    # 否则返回执行的结果信息
35             conn.send(cmd_res)
36         except Exception as e:
37             print(‘Exception: %s‘ % e)
38             break
39     conn.close()
40 phone.close()

      - Client端

 1 #!/usr/bin/python3
 2 # -*- coding:utf-8 -*-
 3
 4 import socket
 5
 6 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 7
 8 phone.connect((‘127.0.0.1‘,8090))
 9 while True:
10     cmd = input(‘>>>:\t‘).strip()
11     if not cmd:
12         print(‘不允许空消息!‘)
13         continue
14     phone.send(cmd.encode(‘utf-8‘))
15     cmd_res = phone.recv(1024)           # 写死每次取1024容易造成粘包现象
16     print(‘%s Result:\n%s‘ % (cmd,cmd_res.decode(‘gbk‘)))
17
18 phone.close()

    - 粘包解决办法

      - Server端

 1 #!/usr/bin/python3
 2 # -*- coding:utf-8 -*-
 3
 4 import time
 5 import socket
 6 import subprocess
 7 import json
 8 import struct
 9
10 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
11 # Address already in use
12 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
13 phone.bind((‘10.0.0.9‘,8080))
14 phone.listen(5)  # tcp 半连接池
15
16 while True:
17     print(‘Starting...‘)
18     conn,addr = phone.accept()
19     print(‘conn is %s‘ % conn)
20     print(‘addr is:‘, addr)
21     print(‘Ready to reveice msg...‘)
22     while True:
23         try:
24             cmd = conn.recv(1024)
25             if not cmd: # for linux platform
26                 break
27             t = time.strftime(‘%Y-%m-%d %X‘)
28             print(‘Time: %s\tClient CMD: %s‘ % (t, cmd.decode(‘utf-8‘)))
29             res = subprocess.Popen(cmd.decode(‘utf-8‘),shell=True,
30                                    stdout=subprocess.PIPE,
31                                    stderr=subprocess.PIPE)
32             cmd_res = res.stderr.read()
33             if not cmd_res:
34                 cmd_res = res.stdout.read()
35             # 自定义数据报头
36             head_dic = {‘filename‘: None, ‘hash‘: None, ‘total_size‘: len(cmd_res)}
37             head_json = json.dumps(head_dic)
38             head_byte = head_json.encode(‘utf-8‘)
39             # 先发送数据报头的大小
40             conn.send(struct.pack(‘i‘,len(head_byte)))
41             # 再发送数据报头
42             conn.send(head_byte)
43             # 最后发送真实的数据体
44             conn.send(cmd_res)
45         except Exception as e:
46             print(‘Exception: %s‘ % e)
47             break
48     conn.close()
49 phone.close()

      - Client端

 1 #!/usr/bin/python3
 2 # -*- coding:utf-8 -*-
 3
 4 import socket
 5 import struct
 6 import json
 7
 8 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 9 phone.connect((‘10.0.0.9‘,8080))
10
11 while True:
12     cmd = input(‘>>>:\t‘).strip()
13     if not cmd:
14         print(‘不允许空消息!‘)
15         continue
16     phone.send(cmd.encode(‘utf-8‘))
17     # cmd_res = phone.recv(1024)  # 写死1024会造成粘包现象
18     # 先收报头大小
19     head_struct = phone.recv(4)
20     head_len = struct.unpack(‘i‘,head_struct)[0]
21     # 再接收数据报头
22     head_byte = phone.recv(head_len)
23     head_json = head_byte.decode(‘utf-8‘)
24     head_dict = json.loads(head_json)
25     # 从head_dict中获取真实的数据的大小
26     total_size = head_dict[‘total_size‘]
27     # 不能一次都接收完,要每次接收一部分,分多次接收
28     reve_size = 0
29     data = b‘‘
30     while reve_size < total_size:
31         tmp = phone.recv(1024)
32         data += tmp
33         reve_size += len(tmp)
34     print(‘%s Result:\n%s‘ % (cmd,data.decode(‘gbk‘)))
35 phone.close()

      - 不足:

经过在CentOS-6.6上测试存在几个问题:

1.在客户端执行查看大文件的时候会卡死(客户端+服务端),例如执行: cat /etc/services

2.不支持并发接收用户请求并处理

      - 需要注意的几点:

1.需要关闭CentOS上的 getenforce

2.需要关闭iptables,或者插入一条过滤规则:
    iptables -I INPUT -s 10.0.0.0/8 -p tcp --dport 8080 -j ACCEPT

3.如果出现这个错误:OSError: [Errno 98] Address already in use
需要在 bind() 操作前加入一行代码:
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

4.在Linux平台上,针对用户的输入要进行非空判断(在客户端/服务端判断):
if not cmd: # for linux platform
    break
如果不判断会造成卡死状态

5.subprocess执行命令相关细节很重要!

6.struct.pack() 和 struct.unpack() 的使用很重要;

7.报文头字典的定义要合理

      - ftp上传下载示例:

  - 9.认证客户端的链接合法性

  - 10.socketserver实现并发

三、day08课后作业

题目要求:

代码实现:

时间: 2024-09-29 21:54:52

Python面向对象进阶和socket网络编程-day08的相关文章

Python全栈【Socket网络编程】

Python全栈[socket网络编程] 本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" 用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是"一切皆文件",对于文件用[打开][读写][关闭]模式

python进阶之Socket 网络编程

 一:网络编程介绍   自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了. 计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信.网络编程就是如何在程序中实现两台计算机的通信. 举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上. 由于你的电脑上可能不止浏览器,还有QQ.Skype.Dropbox.邮件客户端等,不同的程序连接的别的计算机也会不同,所以,更

python进阶九_网络编程(一)

Python网络编程一 一.一些基本概念 在Python网络编程这一节中会涉及到很多网络相关的术语,对于一些最基本的概念,如TCP/IP,Socket等等不再赘述,不明白的可以自己去查一查,对于一些常见但是可能不是很熟悉的术语这里做了一个简要的介绍,后续遇到不会因此止步. SNTP:简单网络时间协议(Simple Network Time  Protocol)简单来说就是用来同步因特网中的计算机时钟,需要注意的是这里的SNTP是用户数据报(UDP)协议. MIME:Multipurpose In

从零开始学Python第八周:网络编程基础(socket)

Socket网络编程 一,Socket编程 (1)Socket方法介绍 Socket是网络编程的一个抽象概念.通常我们用一个Socket表示"打开了一个网络链接",而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 套接字是一个双向的通信信道的端点.套接字可能在沟通过程,进程之间在同一台机器上,或在不同的计算机之间的进程 要创建一个套接字,必须使用Socket模块的socket.socket()方法 在socket模块中的一般语法: s = socket.

Python Socket 网络编程

Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ 聊天.收发 email 等等.要解决网络上两台主机之间的进程通信问题,首先要唯一标识该进程,在 TCP/IP 网络协议中,就是通过 (IP地址,协议,端口号) 三元组来标识进程的,解决了进程标识问题,就有了通信的基础了. 本文主要介绍使用 Python 进行 TCP Socket 网络编程,假设你已

Python之路【第九篇】:Python基础(25)——socket网络编程

socket网络编程 socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用打开.读写.关闭模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭) socket和file的区别: file模块是针对某个指定

python------面向对象进阶 Socket网络编程

一.Socket网络编程 1.七层模型,亦称OSI(Open System Interconnection)参考模型,是参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系. 建立链接:ip+端口(端口有65535) 2.Socket Families(地址簇)  socket.AF_UNIX   unix本机进程通信 socket.AF_INET     IPV4 socket.AF_INET6     IPV6 3. Socket Types socket.SOC

Socket网络编程进阶与实战

第1章 课程导学(Java语言教学)[说明:课程案例部分以Java语言实现]本章节首先会对课程进行导学讲解,包括为什么应该学习本课程,课程目标与收获,课程内容安排,适合人群和学习建议等,接着会讲解代码规范与开发注意事项,目的全在于希望极大的方便同学进行本课程的学习.... 第2章 Socket网络编程快速入门本章首先整体介绍什么是Socket网络编程:让大家对Socket有个大概的概念与方向.之后通过几个小Case引出课程的主角,轻松愉快的让你体验到Socket编程的快感.本章节主要收获:1.

[8421论坛]2019 Socket网络编程入门到进阶与实战完整视频教程

第一章 课程介绍及java语言简介第二章 快速入门Socket网络编程第三章 快速入门Socket UDP第四章 快速入门Socket TCP第五章 UDP辅助TCP实现点对点传输案例第六章 简易聊天室案例第七章 服务器传输优化-NIO第八章 数据传输稳定性优化第九章 局域网文件快传技术实战第十章 聊天室升级版实战第十一章 语音数据即时通信实战第十二章 整体代码结构梳理与升华资料 下载地址? 原文地址:https://www.cnblogs.com/yellowvase/p/10720644.h