Python基础-第七天-面向对象编程进阶和Socket编程简介

时间:2023-02-15 13:36:54

本篇内容:

1.面向对象编程进阶-静态方法

2.面向对象编程进阶-类方法

3.面向对象编程进阶-属性方法

4.面向对象编程进阶-特殊成员(内置方法)

5.面向对象编程进阶-反射

6.异常处理、断言

7.Socket编程简介



一、面向对象编程进阶-静态方法

1.静态方法的实现

通过@staticmethod装饰器可以把其装饰的方法变为一个静态方法;

变成静态方法后,形参中可以不用写self了。如果写了self,默认是不会把对象本身传递给self,需要手动传递;

class Dog(object):
    @staticmethod  # 将eat方法函数变成了静态方法
    def eat():
        print("正在吃%s" % (self.name, "包子"))


静态方法允许在不创建对象的情况下直接被调用,意思就是不需要实例化后通过对象来调用,可以直接通过类来调用;

Dog.eat()  # 通过类直接调用正在吃包子



二、面向对象编程进阶-类方法

1.类方法的实现

通过@classmethod装饰器可以把其装饰的方法变为一个类方法;

类方法至少需要一个cls参数,在调用类方法时,自动将调用该方法的类传递给cls参数;

class Dog(object):    name = "二哈"    food = "包子"    @classmethod  # 将eat方法函数变成了类方法    def eat(cls):        print("%s正在吃%s" % (cls.name, cls.food))  # 可以访问类变量


类方法允许在不创建对象的情况下直接被调用,意思就是不需要实例化后通过对象来调用,可以直接通过类来调用;

Dog.eat()  # 通过类直接调用,将类本身传递给cls二哈正在吃包子



三、面向对象编程进阶-属性方法

Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富,所以通过装饰器定义属性要区分新式类和经典类;

当使用静态字段的方式创建属性时,经典类和新式类无区别;

不管是新式类还是经典类,都只能通过对象调用,不能通过类调用。


1.通过装饰器定义属性

①新式类

具有三种装饰器

class Dog(object):    def __init__(self, name):        """        构造方法        :param name: 狗的名字        """""        self.name = name        self.__food = None    @property  # 将eat方法函数变成了静态属性    def eat(self):        print("%s正在吃%s" % (self.name, self.__food))    @eat.setter    def eat(self, food):        self.__food = food        print("赋值完成为:", self.__food)    @eat.deleter    def eat(self):        del self.__food        print("删除成功")                d = Dog("二哈")                d.eat  # 执行@property装饰的eat方法函数二哈正在吃Noned.eat = "包子"  # 执行@eat.setter装饰的eat方法函数,并将包子赋值给eat方法的food参数赋值完成为: 包子d.eat二哈正在吃包子del d.eat  # 执行@eat.deleter装饰的eat方法函数删除成功

注意这三个装饰器的书写顺序,@property要写在最上面



②经典类

只具有一种装饰器

class Dog:    def __init__(self, name):        """        构造方法        :param name: 狗的名字        """""        self.name = name        self.__food = None    @property  # 将eat方法函数变成了静态属性    def eat(self):        print("%s正在吃%s" % (self.name, self.__food))                d = Dog("二哈")d.eat二哈正在吃None


2.通过静态属性方式,定义值为property对象的静态属性


定义值为property对象的静态属性要写在方法函数的下面

class Foo:    def get_bar(self):        return 'zhong'    # *必须两个参数    def set_bar(self, value):         return return 'set value' + value    def del_bar(self):        return 'zhong'    BAR = property(get_bar, set_bar, del_bar, 'description...')obj = Foo()obj.BAR  # 自动调用第一个参数中定义的方法:get_barobj.BAR = "alex"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入del Foo.BAR  # 自动调用第三个参数中定义的方法:del_bar方法obj.BAE.__doc__  # 自动获取第四个参数中设置的值:description...

property的构造方法中有个四个参数:

  ●第一个参数是方法名,调用 对象.属性 时自动触发执行方法;

  ●第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法;

  ●第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法;

  ●第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息;



四、面向对象编程进阶-特殊成员(内置方法)

Python的类成员存在着一些具有特殊含义的成员,例如:__init__、__call__、__dict__等。

下面介绍几个常用的特殊成员;


1.__doc__:输出类的描述信息

class Dog(object):    """描述狗的类"""    def eat(self):        passprint(Dog.__doc__)描述狗的类


2.__call__:在对象后面加上括号后,就触发执行了

构造方法的执行是由创建对象触发的,即:对象 = 类名();而对于__call__方法的执行是由对象后加括号触发的,即: 对象() 或 类()()

class Dog(object):    """描述狗的类"""    def __call__(self, *args, **kwargs):        print("__call__方法函数运行了", args, kwargs)d = Dog()d(1, 2, 3, name="二哈")__call__方法函数运行了 (1, 2, 3) {'name': '二哈'}Dog()(4, 5, 6, name="金毛")__call__方法函数运行了 (4, 5, 6) {'name': '金毛'}


3.__dict__:查看类或对象中的所有成员 

通过类调用,输出的是字典,字典的key包括类变量、私有类变量、普通方法、类的特殊成员,字典的value是各成员所对应的值;

通过实例调用,输出的是字典,字典的key包括实例变量名、私有实例变量名,字典的value是各成员所对应的值;

class Dog(object):    """描述狗的类"""    age = 2    __sex = "公"    def __init__(self, name):        self.name = name        self.__food = "包子"    def eat(self):        print("%s在吃%s" % (self.name, self.__food))d = Dog("二哈")print(d.__dict__)  # 通过对象调用{'_Dog__food': '包子', 'name': '二哈'}for item in Dog.__dict__:  # 通过类调用    print("类成员", item)类成员 __weakref__类成员 __doc__类成员 __dict__类成员 age类成员 __module__类成员 eat类成员 _Dog__sex类成员 __init__


4.__str__:如果类中定义了__str__方法,当打印对象时,默认输出__str__方法的返回值

class Dog(object):    """描述狗的类"""    def __init__(self, name):        self.name = name        self.__food = "包子"    def __str__(self):        return "<对象的实例属性姓名的值: %s>" % self.named = Dog("二哈")print(d)<对象的实例属性姓名的值: 二哈>



五、面向对象编程进阶-反射

1.为什么需要用到反射

有时需要根据用户的选择去调用类中的方法,而不能通过 对象名.用户输入的内容 这样调用方法,因为用户输入的内容的数据类型是字符串,也不能 if 用户输入的内容 in 对象名 这样判断对象中是否存在用户所输入的方法。

这时就可以用到反射了,反射的作用是将字符串反射成内存中的对象地址;


2.反射的使用

反射具有四个方法:hasattr、getattr、setattr、delattr


通过hasattr判断对象中是否存在对应的成员,存在返回True,不存在返回False

class Foo(object):    def __init__(self):        self.name = 'zhong'    def eat(self):        print("%s正在吃东西" % self.name)obj = Foo()print(hasattr(obj, "name"))  # 判断属性Trueprint(hasattr(obj, "eat"))  # 判断方法Trueprint(hasattr(obj, "function"))  # 不存在的成员False


通过getattr获取对象中对应的成员内存对象地址

class Foo(object):    def __init__(self):        self.name = 'zhong'    def eat(self, food):        print("%s正在吃%s" % (self.name, food))obj = Foo()print(getattr(obj, "eat"))  # 获取方法的内存对象地址<bound method Foo.eat of <__main__.Foo object at 0x0000000000B215F8>>getattr(obj, "eat")("包子")  # 加上括号就是调用该方法了zhong正在吃包子func = getattr(obj, "eat")func("拉面")zhong正在吃拉面print(getattr(obj, "name"))  # 获取属性zhongvalue = getattr(obj, "name")print(value)zhong


通过setattr给对象添加成员

def bulk(self):  # 添加的方法必须要存在    print("%s在大叫" % self.name)class Foo(object):    def __init__(self):        self.name = 'zhong'    def eat(self, food):        print("%s正在吃%s" % (self.name, food))obj = Foo()setattr(obj, "talk", bulk)  # 添加方法,字符串类型的名称才是方法名obj.talk(obj)  # 调用方法,需要手动将对象传递给方法的self参数zhong在大叫setattr(obj, 'show', lambda num: num + 1)  # 添加方法,字符串类型的名称才是方法名print(obj.show(5))6setattr(obj, "age", 35)  # 添加属性,字符串类型的名称才是属性名print(obj.age)35


通过delattr删除对象中对应的成员

class Foo(object):    def __init__(self):        self.name = 'wupeiqi'    def eat(self, food):        print("%s正在吃%s" % (self.name, food))obj = Foo()delattr(obj, "name")  # 删除属性delattr(obj, "eat")  # 删除方法



六、异常处理、断言

1.异常处理

①常见异常

AttributeError:试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x;

IOError:输入/输出异常。基本上是无法打开文件;

FileNotFoundError:指定的文件不存在;

ImportError:无法引入模块或包。基本上是路径问题或名称错误;

IndentationError:语法错误。代码没有正确对齐,缩进错误。这个错误捕获不到;

IndexError:下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5];

KeyError:试图访问字典里不存在的键;

KeyboardInterrupt:Ctrl键 + C键被按下;

NameError:使用一个还未被赋予对象的变量;

SyntaxError:Python代码非法,代码不能编译(个人认为这是语法错误,写错了)。这个错误捕获不到;

TypeError:传入对象类型与要求的不符合;

UnboundLocalError:试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它。

ValueError:传入一个调用者不期望的值,即使值的类型是正确的;


②捕获异常的语法

try:    主代码块except 异常种类1 as e:  # e是错误的详细信息。只能抓到异常种类1    异常1时执行的代码except 异常种类2 as e:  # 只能抓到异常种类2    异常2时执行的代码...except Exception as e:  # Exception是万能异常,可以捕获任意异常。    其它异常时执行的代码else:    主代码块执行完后,执行该块代码finally:    无论异常与否,都会执行该块代码


③自定义异常

定义语法:

class 自定义的错误类的类名(Exception):    def __init__(self, msg):        self.message = msg    def __str__(self):        return self.messagetry:    raise 自定义的错误类的类名("错误信息")except 自定义的错误类的类名 as e:    print(e)

Exception是一个异常的基类;

raise是触发的意思,这里代表主动(即手动)触发自定义的错误;

打印e就代表打印实例,会触发自定义的错误类中的__str__方法函数;


2.断言

assert语句用来声明某个条件是真的。当assert语句失败的时候,会抛出一个AssertionError异常;

如果检验某个条件时,在它非真的时候引发一个错误,那么assert语句是应用在这种情形下的理想语句。



①断言的定义

语法:

assert expression, arguments

python的assert断言是声明表达式布尔值为真,如果发生异常就说明表达式为假。可以理解为当assert断言语句为raise-if-not,用来测试表达式,其返回值为假时,就会触发异常。所以assert expression, arguments的等价语句为:

if not expression:    raise AssertionError


②例子

# assert语句声明的条件为真assert 2 + 2 == 2 * 2, "2加2不等于2乘2"没有输出# assert语句声明的条件为假assert 2 == 1, '2不等于1'AssertionError: 2不等于1



七、Socket编程简介

1.socket简介

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,对于文件用打开、读写、关闭模式来操作。socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)


socket和file的区别:

  ●file模块是针对某个指定文件进行【打开】【读写】【关闭】

  ●socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】


建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。


socket流程图:

Python基础-第七天-面向对象编程进阶和Socket编程简介


2.socket使用

①服务端,要先开启服务端。下面的语法是按照先后顺序来写的

server = socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0):声明socket类型,同时生成socket连接对象

参数详解:

  ●family即地址簇,是位于网络层的

    socket.AF_INET:IPv4(默认);
    socket.AF_INET6:IPv6;

    socket.AF_UNIX:只能够用于单一的Unix系统进程间通信。因为进程之间是不能直接通信的,不能直接在内存中通信。而采用其它办法通信,比如把数据序列化到硬盘中再由其它进程反序列化得到数据这种方式通信,会受限于硬盘的读写速度。在没有网络的情况下,本机进程之间想快速的通信可以采用AF_UNIX;

  ●type即类型,是位于传输层的

    socket.SOCK_STREAM:流式socket,for TCP(默认);
    socket.SOCK_DGRAM:数据报式socket,for UDP;

    socket.SOCK_RAW:原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。利用SOCK_RAW可以伪造ip地址实现洪水攻击;
    socket.SOCK_RDM:是一种可靠的UDP形式,即保证交付数据报但不保证顺序,发送数据的顺序是ABC,接收到的数据顺序可能就是CAB,保证数据都被发送和接收了,但顺序错乱了。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
    socket.SOCK_SEQPACKET:可靠的连续数据包服务,已废弃;

  ●proto即协议

    0:与特定的地址家族相关的协议,如果是0(即零),则系统就会根据地址格式和套接类别,自动选择一个合适的协议。(默认)


  server.bind(address):将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以一个具有两个元素的元组(host, port)的形式表示地址。host是客户端所访问的ip地址(服务端上的ip地址),注意ip地址要用引号引起来。port是客户端访问服务端上的端口号。

 

  server.listen(backlog):开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。注意,backlog不包括正在通信的连接,是除正在通信的连接外,还能够挂起的连接数量。并且,只有当服务端和连接通信交互后,listen设置的backlog才会生效;

 

  conn, address = server.accept():接收连接并返回一个具有两个元素的元组(conn, address),connect是客户端连接上服务端后,在服务端上为其生成的连接对象,address是一个具有两个元素的小元组(host, port),客户端的ip地址与客户端使用的端口号。接收TCP 客户的连接(阻塞式)等待连接的到来;

 

  data = conn.recv(bufsize, flags=None):接受套接字的数据。注意将对象改成conn(即客户端连接上服务端后,在服务端为其生成的连接对象)后,就支持多个客户端同时和一个服务端交互数据了。Python 2.X中数据以字符串类型返回,Python 3.X中数据以bytes类型返回。bufsize指定最多可以接收的数量,单位为字节。flags提供有关消息的其他信息,通常可以忽略。

 

  conn.send(data, flags=None):将data中的数据发送到连接的套接字。注意将对象改成conn(即客户端连接上服务端后,在服务端为其生成的连接对象)后,就支持多个客户端同时和一个服务端交互数据了。Python 2.X中数据以字符串类型发送,Python 3.X中数据以bytes类型发送。返回发送的字节数量,该数量可能小于data的字节大小,即:可能未将指定内容全部发送。

  注意,用send发送空内容会卡住;

  因为对端会设置最大能接收的数据量,当本端发送的数据量比对端设置接收的数据量要大时,本端就会出现在一个发送动作中无法将全部数据发送出去,剩下部分的数据会存在网络发送接口缓存区中,等到下次再向对端发送数据时,优先发送被存起来的数据;

  当对端把接收数据量设置很大后,这时发送数据量的瓶颈是send的缓存区,不同系统的缓存区大小不一样,Linux是32768字节,即在linux系统上通过send每次最多只能发送32768字节的数据量。


  server.close():关闭套接字;


②客户端,下面的语法是按照先后顺序来写的

  client = socket.socket():声明socket类型,同时生成socket连接对象。由于family默认是AF_INET,type默认是SOCK_STREAM,所以可以不用再声明。

 

  client.connect(address):连接到address处的套接字。address为一个具有两个元素的元组元组(host, port),host是客户端所访问的ip地址(服务端上的ip地址),注意ip地址要用引号引起来。port是客户端访问服务端上的端口号。如果连接出错,返回socket.error错误。

 

  client.send(data, flags=None):将data中的数据发送到连接的套接字。Python 2.X中数据以字符串类型发送,Python 3.X中数据以bytes类型发送。返回发送的字节数量,该数量可能小于data的字节大小,即:可能未将指定内容全部发送。注意,用send发送空内容会卡住;

  因为对端会设置最大能接收的数据量,当本端发送的数据量比对端设置接收的数据量要大时,本端就会出现在一个发送动作中无法将全部数据发送出去,剩下部分的数据会存在网络发送接口缓存区中,等到下次再向对端发送数据时,优先发送被存起来的数据;

  当对端把接收数据量设置很大后,这时发送数据量的瓶颈是send的缓存区,不同系统的缓存区大小不一样,Linux是32768字节,即在linux系统上通过send每次最多只能发送32768字节的数据量。

 

  data = client.recv(bufsize, flags=None):接受套接字的数据。Python 2.X中数据以字符串类型返回,Python 3.X中数据以bytes类型返回。bufsize指定最多可以接收的数据大小,单位为字节。flags提供有关消息的其他信息,通常可以忽略。

 

  client.close():关闭套接字;


3.实例

server:

import socketserver = socket.socket()  # 声明socket类型,同时生成socket连接对象server.bind(("localhost", 55555))  # 绑定客户端访问的ip地址和客户端要访问的端口server.listen(5)  # 监听,最多允许挂起5个连接print("开始等待客户端访问的连接了")while True:  # 能接收多个客户端的连接    # 接收客户端访问的连接    # connect是客户端连接上服务器端后,在服务端为其生成的连接对象    # address是一个元组,客户端ip地址与客户端使用的端口号    connect, address = server.accept()    print("接收到客户端访问的连接了")    while True:  # 能和一个连接通信多次        data = connect.recv(1024)  # 接收客户端发送的数据,一次最多可以接收1024字节的数据        # 当接收的内容为空就退出,在Unix/Linux/OSX系统上当连接断开后,服务端会一直接收空内容,在Windows上会报错        if not data:              print("客户端发送的数据为空")            break        data = data.decode(encoding="utf-8")  # 从bytes类型转换成str类型        print("客户端发送的数据为: %s" % data)        new_data = "成功接收消息:" + data        connect.send(new_data.encode(encoding="utf-8"))  # 响应客户端server.close()


client:

import socketclient = socket.socket()  # 声明socket类型,同时生成socket连接对象client.connect(("localhost", 55555))  # 连接服务端的ip地址和要访问服务端的服务端口while True:  # 能向服务端发送多次消息    message = input("输入你想要发送的消息\n>>>").strip()    if len(message) == 0:  # 防止输入的内容为空,因为send发送空内容会卡住        continue    # client.send(b"abc")  # 只能发送bytes类型。bytes方法只能转换ascii里面的数据    client.send(message.encode(encoding="utf-8"))    data = client.recv(1024)  # 接收服务器端响应的数据,一次最多可以接收1024字节的数据    print("服务端响应的数据为: %s" % data.decode(encoding="utf-8"))client.close()


本文出自 “12031302” 博客,谢绝转载!