为单用户服务

  • server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 绑定IP及端口号
s.bind(ip_port)

# step 3: 监听绑定的端口
s.listen(5)

# 等待客户端的连接(阻塞函数)
# step 4: 接受客户端的连接
conn, addr = s.accept()
# conn对象里封装了连接过来的这个客户端的通信线路信息
# 后期跟这个客户端的通信与交互都需要在conn这条通信线路上进行

while True:

# step 5: 接收消息(在conn通道没有被关闭的情况下是阻塞的函数,一旦conn被客户端关闭,该函数将不会阻塞)
recv_data = conn.recv(1024)

# 如果conn通道被客户端主动关闭,recv函数将不再阻塞,recv_data将接收到空字符串
# 通过判断recv_data为空字符串来退出服务端的连接
if len(recv_data) == 0: break

# step 6: 发送消息
send_data = recv_data.upper()
conn.send(send_data)

# step 7: 断开连接
conn.close()

这个服务端在与客户端交互(收发消息)的部分使用了循环,没次接收消息,处理消息,发送消息之后就进入到下一次循环等待接收消息。

这里需要注意的一点就是conn,服务端的conn对象对应了客户端的s对象,都代表了两端之间建立的连接通道,一旦客户端主动关闭连接对象s,在服务端对应的conn对象将立即失效,随即退出循关闭服务端持有的连接对象。

此时conn.recv()函数不再是一个阻塞的状态,它将返回空值。我们需要对接收这个空值的对象(变量)做出相应的处理,关闭掉服务端持有的连接对象

  • client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 连接服务端
s.connect(ip_port)

while True:

# step 3: 发送消息
send_data = input("> ").strip()

# 如果客户端输入了exit,退出循环,主动close掉与服务端的连接
if send_data == "exit": break

# 如果什么也没有输入,重新循环接收输入
if len(send_data) == 0: continue

# 这里注意,和服务端不同的是,服务端找到对端是通过conn对象,而客户端是s对象
# 在Python3.x中,socket对象发送对象必须是字节类型(2.7中可以是字符串)
s.send(bytes(send_data, encoding='utf-8'))

# step 4: 收消息
recv_data = s.recv(1024)
print(recv_data, "--->", type(recv_data), str(recv_data, encoding='utf-8'))

# step 5: 断开连接
s.close()

这个版本实现了服务端为单个用户提供服务的场景。

从上面的说明中也可以看出,服务端在接收到客户端主动关闭的操作后(特征为:conn.recv返回值为空),相继关闭服务端持有的连接对象,接着代码就停止了。

这里需要特别注意的就是处理客户端输入为空的情况,因为服务端判断客户端是否主动断开,主要依据的就是conn.recv方法的返回值是否为空,所以我们要屏蔽掉用户输入为空的情况

为多用户服务(排队)

  • server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 绑定IP及端口号
s.bind(ip_port)

# step 3: 监听绑定的端口
s.listen(5)

# 把接收客户端连接请求的操作循环起来就可以接受多个用户的请求啦
# 注意:同一时间只能处理一个客户端的请求,其他连接上的用户会排队等待
# 最后支持多少个用户排队等待,是由listen中的参数决定的(连接池)
while True:

# 等待客户端的连接(阻塞函数)
# step 4: 接受客户端的连接
conn, addr = s.accept()
# conn对象里封装了连接过来的这个客户端的通信线路信息
# 后期跟这个客户端的通信与交互都需要在conn这条通信线路上进行

while True:

# step 5: 接收消息(在conn通道没有被关闭的情况下是阻塞的函数,一旦conn被客户端关闭,该函数将不会阻塞)
recv_data = conn.recv(1024)

# 如果conn通道被客户端主动关闭,recv函数将不再阻塞,recv_data将接收到空字符串
# 通过判断recv_data为空字符串来退出服务端的连接
if len(recv_data) == 0: break

# step 6: 发送消息
send_data = recv_data.upper()
conn.send(send_data)

# step 7: 断开连接
conn.close()

服务端的代码只增加了一个while循环,上一个代码版本中,服务端唯一的循环放在了处理某一客户端的请求上。此次加上的循环,放在了接收用户连接请求上,可以在关闭上一个连接之后,循环的处理下一个连接请求

  • client.py 客户端的代码没有任何修改

此时,连续的运行两个客户端,发现两个客户端都可以连接到服务端,但是一个可断端发出请求后,在第一个客户端不关闭连接的情况下,第二个客户端发出的请求一直卡在终端中。只有第一个客户端关闭连接,触发服务端关闭对第一个客户端的连接之后,才会去接收第二个客户端发来的信息

这里特别提示一下,当两个客户端相继连接上服务端后,看似服务端对两个连接都接受了请求,但是,只有第一个连接过来的客户端才进入到了服务端代码中的内层while循环体,此时即使第一个客户端没有发送信息,第二个客户端先给服务端发送信息,终端也是会阻塞住的

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)

  • 参数一:地址簇

    • socket.AF_INET IPv4(默认)
    • socket.AF_INET6 IPv6
    • socket.AF_UNIX 只能够用于单一的Unix系统进程间通信
  • 参数二:类型

    • 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头。

    • socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。

    • socket.SOCK_SEQPACKET 可靠的连续数据包服务

  • 参数三:协议

    • 0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# UDP实例
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
data = sk.recv(1024)
print data


import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
inp = raw_input('数据:').strip()
if inp == 'exit':
break
sk.sendto(inp,ip_port)

sk.close()

sk.bind(address)

将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5

这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

关闭套接字

sk.recv(bufsize[,flag])

接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(byte(string)[,flag])

将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

注意,在Python2.x中可以发送string,但是在3.x中,发送的内容必须是字节类型,所以需要使用byte()内置函数来转换一下

sk.sendall(byte(string)[,flag])

将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常

内部通过递归调用send,将所有内容发送出去

sk.sendto(byte(string)[,flag],address)

将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议

sk.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

主要用于客户端

sk.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)

主要用于客户端,获取服务器的IP地址及端口号

sk.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

主要用于服务端,获取自己监听IP和端口的信息

sk.fileno()

套接字的文件描述符

在IO多路复用的时候会用到

TCP/IP协议是一门非常古老的协议,我们在网络编程的时候,不可能花几个月时候去研究TCP/IP协议,socket就是网络编程的救星,由socket来提供一系列符合TCP/IP协议的接口,我们的应用程序只需要与socket交互,即可实现通过TCP/IP协议在网络中收发数据。需要注意的一点是,socket抽象层本身不负责收发数据,数据收发仍然是依赖四层模型中的传输层、网络层和链路层

Python Version: 3.5+

socket简介

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

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

socket和file的区别:

  • file模块是针对某个指定文件进行【打开】【读写】【关闭】
  • socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】

socket网络编程中的四层协议

下面是socket编程中的一个简单实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# server.py
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 绑定IP及端口号
s.bind(ip_port)

# step 3: 监听绑定的端口
s.listen(5)

# 等待客户端的连接

# step 4: 接受客户端的连接
conn, addr = s.accept()
# conn对象里封装了连接过来的这个客户端的通信线路信息
# 后期跟这个客户端的通信与交互都需要在conn这条通信线路上进行

# step 5: 接收消息
recv_data = conn.recv(1024)

# step 6: 发送消息
send_data = recv_data.upper()
conn.send(send_data)

# step 7: 断开连接
conn.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# client.py
import socket

ip_port = ('127.0.0.1', 5555)

# step 1: 创建socket套接字, 里面封装了通信协议
s = socket.socket()

# step 2: 连接服务端
s.connect(ip_port)

# step 3: 发送消息
send_data = "hello"
# 这里注意,和服务端不同的是,服务端找到对端是通过conn对象,而客户端是s对象
# 在Python3.x中,socket对象发送对象必须是字节类型(2.7中可以是字符串)
s.send(bytes(send_data, encoding='utf8'))

# step 4: 收消息
recv_data = s.recv(1024)
print(recv_data, "--->", type(recv_data), str(recv_data))

# step 5: 断开连接
s.close()

先启动server.py

在启动client.py

1
2
# 客户端终端回显
b'HELLO' ---> <class 'bytes'> b'HELLO'

在Linux中使用 su - testuser && ls 这样方式来指定命令的执行身份时会出问题,切换用户后,后面的命令将被忽略掉,还是使用su命令,加一些参数即可实现在Linux命令行中实现切换用户后立即执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@iZ25olg4lg9Z ~]# su -h

Usage:
su [options] [-] [USER [arg]...]

Change the effective user id and group id to that of USER.
A mere - implies -l. If USER not given, assume root.

Options:
-m, -p, --preserve-environment do not reset environment variables
-g, --group <group> specify the primary group
-G, --supp-group <group> specify a supplemental group

-, -l, --login make the shell a login shell
-c, --command <command> pass a single command to the shell with -c
--session-command <command> pass a single command to the shell with -c
and do not create a new session
-f, --fast pass -f to the shell (for csh or tcsh)
-s, --shell <shell> run shell if /etc/shells allows it

-h, --help display this help and exit
-V, --version output version information and exit

For more details see su(1).
  • 在Linux命令行切换用户并执行命令
1
su - testuser -c whoami
  • 在Linux命令行切换用户并执行脚本
1
su - testuser -s /bin/bash shell.sh

在程序中使用异常处理来提高程序的健壮性,隐藏程序的内部实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while True:
n1 = input("> ")
n2 = input("> ")

try:
n1 = int(n1)
n2 = int(n2)
result = n1 + n2
except Exception as ex: # ex是Exception类的一个对象
print(ex) # 执行了ex对象的__str__方法
# ex中封装了所有的错误信息

------------
> 1
> str
invalid literal for int() with base 10: 'str'
>

异常的捕获属于程序正常运行的功能,和报错退出有本质的区别,上面的程序中,虽然有错误,但是程序并没有被终止

常见的异常种类

1
2
3
4
5
6
7
8
9
10
11
12
13
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的

更多的异常种类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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

Exception

在捕获上面指定异常的时候,我们常常需要指定多个异常区捕获,但有时还是无法全部全部预料到会出现哪种类型的异常,这个时候,Python提供给我们一个万能的异常捕获Exception

那单独捕获异常存在意义是什么呢?在很多时候,我们需要捕获特定的异常来记录日志,或是对某些指定的异常做特殊的处理,这个时候指定捕获某类异常就非常有用啦,为了同时兼顾程序的健壮性,可以像if elif else一样来指定捕获的异常同时兼顾捕获所有的异常,类似于下面的写法

1
2
3
4
5
6
7
8
try:
# ...
except ValueError as ex:
print(ex)
except IndexError as ex:
print(ex)
except Exception as ex:
print(ex)

这样把指定的异常区分开来

注意:Exception一定要放在最后!

异常处理的完整代码块

1
2
3
4
5
6
7
8
9
10
11
12
try:
# 主代码块
pass
except KeyError,e:
# 异常时,执行该块
pass
else:
# 主代码块执行完,执行该块
pass
finally:
# 无论异常与否,最终执行该块
pass
  • 当 try 中的主代码块执行正确时, 将执行 else 里面的代码, 最后执行 finally 里面的代码
  • 当 try 中的主代码块执行出现异常时, 将执行 except 里面的代码, 最后执行 finally 里面的代码

主动触发异常

1
2
3
4
try:
raise Exception("主动抛出异常") # 创建了一个Exception对象,self.message = "主动抛出异常"
except Exception as ex:
print(ex) # __str__ return self.message
1
2
3
4
try:
raise ValueError("主动抛出值异常") # 创建了一个ValueError对象,self.message = "主动抛出异常"
except ValueError as ex:
print(ex) # __str__ return self.message

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
class PsException(Exception):

def __init__(self, msg):
self.message = msg

def __str__(self):
return self.message

try:
raise PsException('Ps的异常')
except PsException as ex:
print(ex)

断言

语法:assert 条件表达式

条件表达式为假则触发AssertionError异常

1
2
3
assert 1 == 1

assert 1 == 2

assert的应用场景:在执行某些操作之前,如果不符合我指定的某些规则,抛出异常

1
2
3
4
5
6
# multiprocessing源码截取
from multiprocessing import pool

p = pool.Pool()

p.join()
1
2
3
4
5
6
7
8
def join(self):
util.debug('joining pool')
assert self._state in (CLOSE, TERMINATE)
self._worker_handler.join()
self._task_handler.join()
self._result_handler.join()
for p in self._pool:
p.join()

*断言与 raise 的区别: 可以简单理解为 断言=if...else... + raise *

单例模式:一个实例,一个对象。用来创建单个实例(对象)

拿数据库的连接池来举例,每个用户的数据库访问请求,都应该只去一个连接池中拿资源。一个连接池就是一个实例。单例模式就可以应用在这里场景下

实现单例模式的思路:

  • 在类的内部自定义一个方法去创建实例,因为使用默认的init构造方法创建实例是不可控的
  • 创建一个旗标变量,该变量保存一个对象实例,创建对象的方法如果检测到该变量已经有值了,就直接将该对象返回,如果没有值,则执行首次创建,并为旗标变量赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A:

instance = None

def __init__(self, name):
self.name = name

@classmethod
def get_instance(cls, name): # 类方法, 第一个参数默认是类这个对象
if cls.instance: # 判断如果这个静态字段为真(已经存在实例)
return cls.instance # 直接返回这个实例
else: # 如果实例不存在
obj = cls(name) # 在类的内部创建一个实例,内部调用了init构造方法
cls.instance = obj # 将该实例赋值给类的静态字段,防止下次重复创建
return obj # 将新创建的对象返回

# 经过上面的修改, 如果想实现单例模式, 就不能再使用init构造方法来创建对象了
# a1 = A("ps")
# a2 = A("lr")

# 相当于把创建对象的任务交给了get_instance方法
a1 = A.get_instance("PolarSnow") # 第一次会创建对象
a2 = A.get_instance("PolarSnow") # 第二次会使用第一次创建的对象

print(a1)
print(a2)

------------
<__main__.A object at 0x101377b38>
<__main__.A object at 0x101377b38>

1
2
3
4
5
6
7
8
9
10
class MyDict(dict):
def __init__(self):
super(MyDict, self).__init__()
self.li = []

md = MyDict()
md["k1"] = 123
md["k2"] = 456

print(md)

在不破坏字典对象构造方法的情况下,自定义创建一个列表,用来存放key

1
2
3
4
5
6
7
8
class MyDict(dict):
def __init__(self):
super(MyDict, self).__init__()
self.li = []

def __setitem__(self, key, value):
self.li.append(key)
super(MyDict, self).__setitem__(key, value)

将新的键值对保存到字典的时候,保存它的key到我们自定义创建的列表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyDict(dict):
def __init__(self):
super(MyDict, self).__init__()
self.li = []

def __setitem__(self, key, value):
self.li.append(key)
super(MyDict, self).__setitem__(key, value)

def __str__(self):
tmp_list = []
for key in self.li:
value = self.get(key) # 使用了父类的get方法
tmp_list.append("'%s': %s" % (key, value))
tmp_str = "{" + ", ".join(tmp_list) + "}"
return tmp_str

md = MyDict()
md["k1"] = 123
md["k2"] = 456

print(md)

------------

{'k1': 123, 'k2': 456}

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def f1(self):
print("A.f1")

class B(A):
def f1(self):
print("B.f1")

b = B()
b.f1()

------------
B.f1

上面的实例中,B类继承了A类,并重写了A类的f1方法,现在我想在B类中主动去调用父类中的方法,可以通过super关键字实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def f1(self):
print("A.f1")

class B(A):
def f1(self):
# 主动执行父类的f1方法
super(B, self).f1()
print("B.f1")

b = B()
b.f1()

------------
A.f1
B.f1

应用场景:在源码中的某些功能不够完善,我们可以继承他的类,并在调用父类方法的情况下,在自己的类中添加一些功能。相当于在类中模拟了装饰器的功能

1
2
3
4
5
6
7
8
9
10
class A:
def f1(self):
print("A.f1")

class B(A):
def f1(self):
print("装饰开始")
ret = super(B, self).f1() # 接收父类f1方法执行结果的返回值
print("装饰结束")
return ret # 返回了父类f1执行结果的返回值(你可以自定义返回任何东西)

非主流的方式去调用父类方法

1
2
3
4
5
6
7
class A:
def f1(self):
print("A.f1")

class B(A):
def f1(self):
A.f1(self) # 别人这么写你需要看懂,但是自己不要这么写

Python的自省,是自我反省,自己检查自己的机制。在Python中,有很多方式可以做到让Python自省,这篇文章主要介绍两个isinstanceissubclass

isinstance

isinstance(obj, cls) 用来检查obj对象是否是cls类的对象

1
2
3
4
5
6
7
8
9
10
11
12
class A:
pass

a = A()
print(isinstance(a, A))

i = 10
print(isinstance(i, int))

------------
True
True
1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
pass

class B(A):
pass

b = B()
print(isinstance(b, B))
print(isinstance(b, A))

------------
True
True

补充:也可以是cls的父类

issubclass

issubclass(sub, super) 用来检查sub类是否是 super 类的派生类

1
2
3
4
5
6
7
8
class A:
pass

class B(A):
pass

print(issubclass(B, A)) # B 继承了 A, B是A的子类
print(issubclass(A, B))

Python Version: 3.5+

__init__

构造方法,每个对象被实例化出来的时候都将首先去执行__init__方法

1
2
3
class A:
def __init__(self):
print("在创建对象的时候会首先自动执行__init__")

__del__

析构方法,每个对象在被垃圾回收机制回收之前执行的方法

1
2
3
class A:
def __del__(self):
print("在对象销毁之前会执行__del__")

__doc__

类的描述信息

1
2
3
class A:
"""我是A类的描述信息"""
pass

__module__

表示当前操作的对象在哪个模块

1
2
3
class A:
"""我是A类的描述信息"""
pass
1
2
3
from lib import A
a = A()
print(a.__module__)

__class__

表示当前操作的对象的类是什么

1
2
3
4
5
class A:
pass

a = A()
print(a.__class__)

__call__

类名后面加括号表示创建一个对象;如果在对象后面加括号,就需要使用__call__方法了,如果不定义这个方法,在执行对象()的时候就会报错

1
2
3
4
5
6
7
8
9
10
class A:

def __call__(self, *args, **kwargs):
print("call")

a = A()
a()

------------
call

创建对象的时候首先执行__init__,在对象被调用的时候执行__call__

也可以在一行执行

1
a = A()()

__str__

print对象的时候显示的内容

1
2
3
4
5
6
7
8
class A:
pass

a = A()
print(a)

------------
<__main__.A object at 0x101b77128>

在没有定义__str__的情况下,输出的是a对象的内存地址信息

1
2
3
4
5
6
7
8
9
class A:
def __str__(self):
return "A~"

a = A()
print(a)

------------
A~

str的应用实例

1
2
3
4
5
6
7
8
9
10
11
12
class B:
def __init__(self, name):
self.name = name

def __str__(self):
return self.name

b = B("ps")
print(b)

------------
ps

str类型转换

1
2
3
4
5
6
7
8
9
10
class B:
def __init__(self, name):
self.name = name

def __str__(self):
return self.name

b = B("ps")
ret = str(b)
print(ret)

str(b)print()都会自动去调用b对象中的__str__方法

__dict__

对象的dict

在对象中默认已经有dict,不需要自定义。该方法用来获取对象中所有封装的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

b = B("ps", 26)
print(b.__dict__)

------------
{'age': 26, 'name': 'ps'}

类的dict

列出类中所有可以调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

b = B("ps", 26)
print(B.__dict__)

------------
{'__weakref__': <attribute '__weakref__' of 'B' objects>, '__module__': '__main__', '__str__': <function B.__str__ at 0x10137b730>, '__init__': <function B.__init__ at 0x10137b6a8>, '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>}

__add__

当执行一个对象 + 一个对象的时候,就会自动去执行这个方法

注意,执行的是第一个对象的add方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def __init__(self, num):
self.num = num

def __add__(self, other):
return self.num + other.num

class B:
def __init__(self, num):
self.num = num


a = A(5)
b = B(9)
c = a + b
print(c)

__getitem__ __setitem__ __delitem__

用于索引操作,如字典。以上分别表示获取、设置、删除数据

1
2
3
4
d = {"k": "v"}
print(d["k"])
d["k"] = "vv"
del d["k"]

上面的代码展示了一个字典对象的取值、赋值和删除的操作。在自定义的类中,也可以实现类似于字典这样的操作

对象后面加小括号是执行__call__方法,那么对象后面加中括号又是怎样处理的呢?

使用key进行的操作

取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
print("执行了getitem方法", item)

b = B("ps", 26)
b["name"]

------------
执行了getitem方法 name

赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
print("执行了getitem方法", item)

def __setitem__(self, key, value):
print("你要为%s重新赋值为%s" % (key, value))

b = B("ps", 26)
print(b.name)
b["name"] = "lr"

------------
ps
你要为name重新赋值为lr

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
print("执行了getitem方法", item)

def __setitem__(self, key, value):
print("你要为%s重新赋值为%s" % (key, value))

def __delitem__(self, key):
print("你要删除%s" % key)

b = B("ps", 26)
del b["age"]

------------
你要删除age

在web开发中,自定义session框架的时候会用到

使用下标进行的操作

使用下标和使用key的形式类似,使用key, item接收的是一个字符串,使用下标, item接收的是一个int类型的数字,可以在方法体内通过判断传递过来数据的数据类型来进行对应的操作

使用切片的操作

1
2
l = [1,2,3,4,5,6,7,8,9]
l[1:5:2]

在Python2.x中使用__getslice__ __setslice__ __delslice__来实现切片的操作,但是Python3.x中被遗弃,所有切片的功能都集中在了__getitem__ __setitem__ __delitem__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
# print("执行了getitem方法", item)
print(type(item))

def __setitem__(self, key, value):
print("你要为%s重新赋值为%s" % (key, value))

def __delitem__(self, key):
print("你要删除%s" % key)

b = B("ps", 26)
b["name"]
b[1]
b[1:5:2]

------------
<class 'str'>
<class 'int'>
<class 'slice'>

itemslice时表示调用了切片的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
print("起点索引", item.start)
print("终点索引", item.stop)
print("步长", item.step)
return "haha"

def __setitem__(self, key, value):
print("你要为%s重新赋值为%s" % (key, value))

def __delitem__(self, key):
print("你要删除%s" % key)

b = B("ps", 26)
ret = b[1:5:2]
print(ret)

------------
起点索引 1
终点索引 5
步长 2
haha

相对应的,取值可以通过判断item的类型做相应的操作,赋值和删除也可以通过判断key的类型来进行想对应的切片操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class B:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return self.name

def __getitem__(self, item):
print("起点索引", item.start)
print("终点索引", item.stop)
print("步长", item.step)
return "haha"

def __setitem__(self, key, value):
print("起点索引", key.start)
print("终点索引", key.stop)
print("步长", key.step)
print("新值为", value)

def __delitem__(self, key):
print("起点索引", key.start)
print("终点索引", key.stop)
print("步长", key.step)

b = B("ps", 26)
print("切片取值")
ret = b[1:5:2]

print("切片赋值")
b[1:5:2] = "hehe"

print("切片删除")
print(ret)
del b[1:5:2]

------------
切片取值
起点索引 1
终点索引 5
步长 2
切片赋值
起点索引 1
终点索引 5
步长 2
新值为 hehe
切片删除
haha
起点索引 1
终点索引 5
步长 2

__iter__

一个自定义类实例化的对象,默认是不可迭代的,在类中使用__iter__方法后,对象就变成了可迭代对象。当对象被迭代时,会自动调用iter方法

1
2
3
4
5
6
7
8
9
10
11
12
class A:
pass

a = A()
for i in a:
print(i)

------------
Traceback (most recent call last):
File "/Users/lvrui/PycharmProjects/untitled/8/c8.py", line 5, in <module>
for i in a:
TypeError: 'A' object is not iterable
1
2
3
4
5
6
7
8
9
10
11
class A:
def __iter__(self):
return iter([1, 2]) # return了一个可迭代对象

a = A()
for i in a:
print(i)

------------
1
2
1
2
3
4
5
6
7
8
9
10
11
12
class A:
def __iter__(self): # 返回了一个生成器
yield 1
yield 2

a = A()
for i in a:
print(i)

------------
1
2

先去a对象中找到iter方法执行,并拿到返回值进行迭代

__new__ __metaclass__

1
2
3
4
5
6
class A(object):

def __init__(self):
pass

a = A() # a是通过A类实例化的对象

上述代码中,a 是通过 A 类实例化的对象,其实,不仅 a 是一个对象,A类本身也是一个对象,因为在Python中一切事物都是对象。

如果按照一切事物都是对象的理论:a对象是通过执行A类的构造方法创建,那么A类对象应该也是通过执行某个类的构造方法创建。

1
2
print type(a) # 输出:<class '__main__.A'>     表示,a对象由A类创建
print type(A) # 输出:<type 'type'> 表示,A类对象由type类创建

所以,a对象是A类的一个实例,A类对象是type类的一个实例,即:A类对象是通过type类的构造方法创建

那么,创建类就可以有两种方式:

  • 普通方式
1
2
3
4
class A(object):

def func(self):
print("ps")
  • 特殊方式(type类的构造函数)
1
2
3
4
5
6
7
def func(self):
print("ps")

A = type('A',(object,), {'func': func})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员

–> 类是由type类实例化产生

那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?

答:类中有一个属性__metaclass__ 其用来表示该类由来实例化创建,所以,我们可以为__metaclass__设置一个type类的派生类,从而查看创建的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyType(type):

def __init__(self, what, bases=None, dict=None):
super(MyType, self).__init__(what, bases, dict)

def __call__(self, *args, **kwargs):
obj = self.__new__(self, *args, **kwargs)

self.__init__(obj)

class A(object):

__metaclass__ = MyType

def __init__(self, name):
self.name = name

def __new__(cls, *args, **kwargs):
return object.__new__(cls, *args, **kwargs)

# 第一阶段:解释器从上到下执行代码创建A类
# 第二阶段:通过A类创建a对象
a = A()