面试题整理(python基础)

python基础

1.1 三程

进程

1.定义

一个运行起来的程序就是一个进程。
进程是资源分配的最小单位( 内存、cpu、网络、io)。
进程具有独立的内存空间,所以没有办法相互通信。

什么是程序(程序是我们存储在硬盘里的代码)
硬盘(256G)、内存条(8G)
当我们双击图标,打开程序的时候,实际上就是通过I/O操作(读写)内存条里面
内存条就是我们所指的资源

  • CPU分时

CPU比你的手速快多了,分时处理每个线程,但是由于太快然你觉得每个线程都是独占cpu
cpu是计算,只有时间片到了,获取cpu,线程真正执行
当你想使用 网络、磁盘等资源的时候,需要cpu的调度

2.进程通信

  • 进程间如何通信

进程queue(父子进程通信)
pipe(同一程序下两个进程通信)
managers(同一程序下多个进程通信)
RabbitMQ、redis等(不同程序间通信)

进程彼此之间相互隔离,要实现进程间通信,multprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。

队列:
	不同于线程queue,进程queue的生成是用multiprocessing模块生成的。
	在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间。
	Multiprocess.Queue是跨进程通信队列。

常用方法:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样



管道:
	管道就是管道,就像生活中的管道,两头都能进能出。
	默认管道是全双工的,如果创建管道的时候映射成False,左边只能用于接收,右边只能用于发送,类似于单行道。

import multiprocessing

def foo(sk):
    sk.send('hello world')
    print(sk.recv())

if __name__ == '__main__':
    conn1,conn2=multiprocessing.Pipe()    #开辟两个口,都是能进能出,括号中如果False即单向通信
    p=multiprocessing.Process(target=foo,args=(conn1,))  #子进程使用sock口,调用foo函数
    p.start()
    print(conn2.recv())  #主进程使用conn口接收
    conn2.send('hi son') #主进程使用conn口发送


常用方法:
conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
注意:send()和recv()方法使用pickle模块对对象进行序列化

3.进程池

  • 为什么需要进程池

一次性开启指定数量的进程
如果有十个进程,有一百个任务,一次可以处理多少个(一次性只能处理十个)
防止进程开启数量过多导致服务器压力过大

开多进程是为了并发,通常有几个cpu核心就开几个进程,但是进程开多了会影响效率,主要体现在切换的开销,所以引入进程池限制进程的数量。
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止

线程

1.定义

线程是操作系统调度的最小单位
线程是进程正真的执行者,是一些指令的集合(进程资源的拥有者)
同一个进程下的读多个线程共享内存空间,数据直接访问(数据共享)
为了保证数据安全,必须使用线程锁

  • GIL全局解释器锁

在python全局解释器下,保证同一时间只有一个线程运行
防止多个线程都修改数据
GIL锁只能保证同一时间只能有一个线程对某个资源操作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以操作了

  • 线程锁(互斥锁)

线程锁本质把线程中的数据加了一把互斥锁
加上线程锁之后所有其他线程,读都不能读这个数据

mysql中共享锁 & 互斥锁
mysql共享锁:共享锁,所有线程都能读,而不能写
mysql排它锁:排它,任何线程读取这个这个数据的权利都没有
  • 有了GIL全局解释器锁为什么还需要线程锁

因为cpu是分时使用的

  • 死锁定义

两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

线程是系统调度的最小单位。
同进程下线程资源共享。
进程无法自己执行,只有通过线程操作CPU,内存。
一个进程下可以运行多个线程,这些线程之间共享主进程内申请的操作系统资源。
在一个进程中启动多个线程的时候,每个线程按照顺序执行。现在的操作系统中,也支持线程抢占,也就是说其它等待运行的线程,可以通过优先级,信号等方式,将运行的线程挂起,自己先运行。

2.多线程

1、join ()方法:主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,那么在调用这个线程时可以使用被调用线程的join方法。
原型:join([timeout]),里面的参数时可选的,代表线程运行的最大时间,即如果超过这个时间,不管这个此线程有没有执行完毕都会被回收,然后主线程或函数都会接着执行的。

2、setDaemon()方法。主线程A中,创建了子线程B,并且在主线程A中调用了B.setDaemon(),这个的意思是,把主线程A设置为守护线程,这时候,要是主线程A执行结束了,就不管子线程B是否完成,一并和主线程A退出.这就是setDaemon方法的含义,这基本和join是相反的。此外,还有个要特别注意的:必须在start() 方法调用之前设置。

3.线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

协程

1.定义

  • 什么是协程

协程微线程,纤程,本质是一个单线程
协程能在单线程处理高并发

线程遇到I/O操作会等待、阻塞,协程遇到I/O会自动切换(剩下的只有CPU操作)
线程的状态保存在CPU的寄存器和栈里而协程拥有自己的空间,所以无需上下文切换的开销,所以快

  • 为什么协程能够遇到I/O自动切换

协程有一个gevent模块(封装了greenlet模块),遇到I/O自动切换

  • 协程缺点

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上

线程阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

  • 协程最大的优点

不仅是处理高并发(单线程下处理高并发)
特别节省资源(500日活,用php写需要两百多态机器,但是golang只需要二十多太机器)

200多台机器一年
二十多天机器一年

2.协程处理并发

gevent模块:遇到IO自动切换
greenlet可以实现协程,不过每一次都要人为的去指向下一个该执行的协程,显得太过麻烦。python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent
gevent每次遇到io操作,需要耗时等待时,会自动跳到下一个协程继续执行。


import gevent

def A():
    while 1:
        print('-------A-------')
        gevent.sleep(1) #用来模拟一个耗时操作,注意不是time模块中的sleep

def B():
    while 1:
        print('-------B-------')
        gevent.sleep(0.5)  #每当碰到耗时操作,会自动跳转至其他协程

g1 = gevent.spawn(A) # 创建一个协程
g2 = gevent.spawn(B)
g1.join()  #等待协程执行结束
g2.join()
greenlet模块:遇到IO手动切换

from greenlet import greenlet
import time

def A():
    while 1:
        print('-------A-------')
        time.sleep(0.5)
        g2.switch()

def B():
    while 1:
        print('-------B-------')
        time.sleep(0.5)
        g1.switch()

g1 = greenlet(A)  #创建协程g1
g2 = greenlet(B)

g1.switch()  #跳转至协程g1

3.select、poll、epoll

  • I/O的实质是什么?

I/O的实质是将硬盘中的数据,或收到的数据实现从内核态 copy到 用户态的过程

本文讨论的背景是Linux环境下的network IO。
比如微信读取本地硬盘的过程
微信进程会发送一个读取硬盘的请求----》操作系统
只有内核才能够读取硬盘中的数据—》数据返回给微信程序(看上去就好像是微信直接读取)

  • 用户态 & 内核态

系统空间分为两个部分,一部分是内核态,一部分是用户态的部分
内核态:内核态的空间资源只有操作系统能够访问
用户态:我们写的普通程序使用的空间

在这里插入图片描述

  • select 在一个进程内可以维持最多 1024 个连接,poll 在此基础上做了加强,可以维持任意数量的连接。
  • epoll 是 select 和 poll 的增强版,epoll 同 poll 一样,文件描述符数量无限制。
  • select

只能处理1024个连接(每一个请求都可以理解为一个连接)
不能告诉用户程序,哪一个连接是活跃的

  • pool

只是取消了最大1024个活跃的限制
不能告诉用户程序,哪一个连接是活跃的

  • epool

不仅取消了1024这个最大连接限制
而且能告诉用户程序哪一个是活跃的

select()
拷贝所有的文件描述符给协程,不论这些任务的是否就绪,都会被返回,那么协程就只能for循环去查找自己的文件描述符,也就是任务列表,select的兼容性非常好,支持linux和windows
但是他有一个支持的最大的任务数,就是1024,但是可以通过修改linux的内核参数可以变大


poll()
3年后,poll诞生了,但是和select一样,几乎没有大的升级,唯一的升级就是没有最大文件描述符的限制


epoll()
到了linux的内核2.6版本才支持epoll,直接由内核之间支持,继承了select和poll的全部优点,被认为是linux下性能最好的多路io就绪通知方法,
epoll和select和poll一样,也只会返回已经就绪的文件描述符或者任务列表,但是当我们调用epoll_wait()获取文件描述符的时候,返回的不是描述符本身,而是一个代表就绪文件描述符的数量的值,也就是
一个索引,你只需要到epoll指定的一个列表或者数组中取得相应的就绪的文件描述符或者就绪的任务列表即可 ,只拷贝就绪的文件描述符,使用内存映射技术(nmap),用户态的进程的可以直接内核空间的内
存,但是仅仅只能访问固定的一块,所有效率会提高,不用在反复拷贝文件描述符


epoll有4个动作:创建,注册,等待,取消注册,很显然我们用不着


epoll和select,poll还有一个本质的区别的就是
select和poll只有在下次在循环回来,再去操作系统获取文件描述符
epoll会直接告诉程序,我们这里已经就绪了,你可以接受数据了,等下一次协程去调用epoll_wait的时候就可以直接拿到就绪的文件描述符

4.猴子补丁

在运行期间动态修改一个类或模块。

class A:
    def func(self):
        print("Hi")
def monkey(self):
    print("Hi, monkey")
m.A.func = monkey
a = m.A()
a.func()

1.2 三器

装饰器

  • 什么是装饰器(what)

装饰器本质是函数,用来给其他函数添加新的功能
特点:不修改调用方式、不修改源代码

  • 装饰器应用场景(where)

用户认证,判断用户是否登录
计算函数运行时间(算是一个功能、在项目里用的不多)
插入日志的时候
redis缓存

  • 为什么要使用装饰器(why)

结合应用场景说需求
比如提高代码复用性,节省内存空间

  • 二级装饰器和三级装饰器的区别以及三级装饰器带参数时加括号和不加括号的区别???

二级和三级装饰器的区别就是三级装饰器在二级基础上再次嵌套了一层函数

三级装饰器加括号就是调用函数,返回函数执行结果,不加括号返回是内存地址

1.手写三级装饰器

#装饰器带参数
def timer(parameter):
    def out_wapper(func):
        def wapper(*wargs,**kwargs):
            if parameter == "task1":
                start = time.time()
                func(*wargs,**kwargs)
                stop = time.time()
                print ("the task1 is run:",stop-start)
            elif parameter == "task2":
                func(*wargs, **kwargs)
                print ("the task2 is run:")
        return wapper
    return out_wapper

@timer(parameter = "task1")
def task1():
    time.sleep(2)
    print "in the task1"

@timer(parameter = "task2")
def task2():
    time.sleep(2)
    print "in the task2"

task1()
task2()

2.闭包

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

 1 #闭包函数的实例
 2 # outer是外部函数 a和b都是外函数的临时变量
 3 def outer( a ):
 4     b = 10
 5     # inner是内函数
 6     def inner():
 7         #在内函数中 用到了外函数的临时变量
 8         print(a+b)
 9     # 外函数的返回值是内函数的引用
10     return inner
11 
12 if __name__ == '__main__':
13     # 在这里我们调用外函数传入参数5
14     #此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
15     # 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
16     demo = outer(5)
17     # 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
18     # demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
19     demo() # 15
20 
21     demo2 = outer(7)
22     demo2()#17

3.装饰器在项目中的应用

https://www.cnblogs.com/superhin/p/11454823.html
附加功能
数据的清理或添加:
	函数参数类型验证 @require_ints 类似请求前拦截
	数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改
	为函数提供额外的数据 mock.patch
函数注册
	在任务中心注册一个任务
	注册一个带信号处理器的函数

迭代器

1.定义

迭代器与python2.2版本后添加,他为类序列对象提供了一个可以使其进化为迭代器的接口iter
	
迭代器对象内部会维持一个状态,用于记录当前迭代的状态,以方便下次迭代时提取正确的数据元素
	
可迭代对象内置__iter__函数,该函数将对象处理为可迭代对象
	
任何实现__iter__和__next__的对象都可看做迭代器
	
__iter__返回迭代器自身、__next__返回迭代的下个值
	
迭代器没有返回的元素,抛出Stopiteration

可迭代对象执行iter方法,得到的结果就是迭代器,迭代器对象有next方法

2.可迭代对象

内置iter方法的,都是可迭代的对象。 list是可迭代对象,dict是可迭代对象,set也是可迭代对象。

3.next()和iter()

lst=[1,2,30]
for i in lst:
    print(i)
next(lst)

lst=[1,2,30]
for i in iter(lst):
    print(i)
next(iter(lst))

运行结果:
1
2
30
1
  • 一些可以直接利用的迭代器
无线迭代器生产:
	无线迭代器------> from itertools import count(接受俩个参数起点和步长)
    每次调用count都是21递增,将所有的基数打印出来,如果说你的内存足够大的话,可以无限迭代下去(可以使用for循环或者next测试),这里推荐使用next,因为它比较直观。
  • 手机号(count)
    在这里插入图片描述
  • cycle(塞口),每次调用都会字符串下一个值创建出来,而创建完最后一个,他会返回到开头,像一个圈状的数据类型不停去创建一个新的值
    在这里插入图片描述
  • 有限迭代器

iter()
from itertools import islice
islice(iterable,stop) 接受参数(可迭代对象,终点值)
islice(iterable,start,stop[,step]) 接受参数(可迭代对象,索引,也可以加步长)

  • 基于可迭代对象创建 iter()
    在这里插入图片描述
  • 使用islice方法
    在这里插入图片描述
  • 自定义迭代器(斐波那契数列)
    在这里插入图片描述

生成器

  • 什么是生成器(what)?

生成器就是一个特殊的迭代器
一个有yield关键字的函数就是一个生成器

  • 特点

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。
对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

  • 生成器应用场景(where)

生成器是一个概念,我们平常写代码可能用的并不多,但是python源码大量使用
比如我们tornado框架就是基于 生成器+协程
在我们代码中使用举例
比如我们要生成一百万个数据,如果用生成器非常节省空间,用列表浪费大量空间

import time
t1 = time.time()
g = (i for i in range(100000000))
t2 = time.time()
lst = [i for i in range(100000000)]
t3 = time.time()
print('生成器时间:',t2 - t1)  # 生成器时间: 0.0
print('列表时间:',t3 - t2)    # 列表时间: 5.821957349777222
  • 为什么要使用生成器

节省空间
高效

python读取超大文件

  • 普通读文件方法弊端分析

1.with 上下文管理器会自动关闭打开的文件描述符,在迭代文件对象时,内容是一行一行返回的,不会占用太多内存
2. 如果python读取文件如果被读取的文件里,根本就没有任何换行符,将会变成一个非常巨大的字符串对象,占用大量内存。

#! /usr/bin/env python
# -*- coding: utf-8 -*-
def read_file(fname):
    with open(fname) as file:
        for line in file:
            print(line.strip('\n'),)
path = r'C:\aaa\luting\edc-backend\aaa.py'
read_file(path)
  • 读取大文件正确方式

1 我们使用了一个 while 循环来读取文件内容,每次最多读取 8kb 大小
2. 这样可以避免之前需要拼接一个巨大字符串的过程,把内存占用降低非常多。

#!/usr/bin/python
# -*- coding: utf-8 -*-
def read_big_file_v(fname):
    block_size = 1024 * 8
    with open(fname,encoding="utf8") as fp:
        while True:
            chunk = fp.read(block_size)
            # 当文件没有更多内容时,read 调用将会返回空字符串 ''
            if not chunk:
                break
            print(chunk)
path = r'C:\aaa\luting\edc-backend\tttt.py'
read_big_file_v(path)

1.工作原理

# 只要 Python 函数中包含关键字 yield,该函数就是生成器函数。
def gen_123():
    # 生成器函数的定义体中通常都有循环,不过这不是必要条件;这里我重复使用 yield。
    yield 1
    yield 2
    yield 3
    yield 4


if __name__ == '__main__':
    # 仔细看,gen_123 是函数对象。
    print(gen_123)
    # 但是调用时,gen_123() 返回一个生成器对象。
    print(gen_123())
    # 生成器是迭代器,会生成传给 yield 关键字的表达式的值。
    for i in gen_123():
        print(i)

    # 为了仔细检查,我们把生成器对象赋值给 g。
    g = gen_123()
    # 因为 g 是迭代器,所以调用 next(g) 会获取 yield 生成的下一个元素。
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    # 生成器函数的定义体执行完毕后,生成器对象会抛出 StopIteration 异常。
    print(next(g))

2.yield运行机制

当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把 yield 的参数给你,之后生成器就不会往下继续运行。 当你问他要下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给你,之后停下。如此反复直至退出函数。

1.3 面向对象

  • 什么是面向对象

使用模板的思想,将世界完事万物使用对象来表示一个类型

方法

1.静态方法

特点:名义上归类管理,实际上不能访问类或者变量中的任意属性或者方法

作用:让我们代码清晰,更好管理

调用方式: 既可以被类直接调用,也可以通过实例调用

代码块是一个整体   分类整理  是代码看起来清晰明了。

复用性   减少代码量   (比如父类A下面有100个子类,100个子类都可以调用父类A中的这个方法,还可以调用自己本类中的方法)class A:
    @staticmethod
    def func():
        print(666)
A.func()

2.类方法

作用:无需实例化直接被类调用

特性:类方法只能访问类变量,不能访问实例变量

类方法使用场景:当我们还未创建实例,但是需要调用类中的方法

调用方式:既可以被类直接调用,也可以通过实例调用

通过类名调用的方法,类方法中第一个参数约定俗成cls,python自动将类名(类空间)传给cls

class A:
    def func(self):
        print(self)        普通方法  : 由对象调用,至少一个self参数,执行普通方法的时候,自动将调用该方法的对象赋值给self
    @classmethod
    def func(cls):          类方法   :  由类调用,至少一个cls参数,执行类方法的时候,自动将调用该方法的类赋值给cls
        print(cls) 
a1 = A()
a1.func()     对象调用方法,cls最终的到的是该类本身

3.属性方法

将一个方法,伪装成一个属性,在代码的级别上没有本质的提升,但是可以让其看起来更合理.

class Person:
    def __init__(self,name,weight,height):
        self.name = name
        self.weight = weight
        self.height = height                 
    def bmi(self):                                 此时bmi按常理会认为他是一个名词.然而在这里确实当做动词再用,所以不太合理
        return round(self.weight / self.height ** 2,2)
p1 = Person("刘某某",60,1.7)
print(p1.bmi())

4.魔法方法

常用的有new、init、del

  • new:产生一个实例
  • init:产生一个对象
  • del:析构方法,删除无用的内存对象(当程序结束会自动执行析构方法)

简单来说就是这个析构方法会在代码执行完毕触发:

  • 当我们实例化对象,然后再进行引用,这时我们使用del删除实例对象的变量
    在这里插入图片描述
    在这里插入图片描述
1、__doc__    查看类的描述信息
class Foo(object):
    """这是一段关于该类的介绍和描述"""
    pass

print(Foo.__doc__)  #这是一段关于该类的介绍和描述
2、__module__ 和 __class__

class Bar(object):
    pass

b1 = Bar()
print(Bar.__module__)  # __main__   ----》当前文件所在模块
print(Bar.__class__)  # <class 'type'>
print(b1.__class__)  # <class '__main__.Bar'>

# 注:从上述结果可以看出,类也是对象,该Bar类对象其是由type类实例化得到的,而对象b1是由当前模块的Bar类实例化得到的
3、__init__  构造方法
当通过类实例化成对象时,自动触发执行__init__方法。该方法下定义的属性即为实例属性,即会自动开辟一个内存空间,用来存放该实例对象的属性和类方法的指针。

class Foo(object):
    # 构造方法
    def __init__(self,name):
        self.name =name
        print("in the init")

foo = Foo("alex")   # in the init
4、__del__ 方法
当由该类创建的实例对象,被删除或者说在内存中被释放时,将会自动触发执行。

class Foo(object):
    # 析构方法
    def __del__(self):
        print("in the __del__")

foo = Foo()
del foo  # in the __del__
5、__call__   对象后面加括号,触发执行。

class Bar(object):
    pass

bar = Bar()
# bar()    # TypeError: 'Bar' object is not callable

# 当一个类中有__call__,其实例化得到的对象便是可调用的即callable
class Foo(object):

    def __call__(self):
        print("in the call")

foo = Foo()
foo()  # in the call
6、__dict__   类或者实例的属性字典
类或这实例的属性字典,用来存放类或者实例的属性和方法;

class Bar(object):
    gender = "male"
    def __init__(self,name):
        self.name = name
    def tt(self):
        pass

bar = Bar("alex")
print(Bar.__dict__)
# 输出为:{'__module__': '__main__', 'gender': 'male', '__init__': <function Bar.__init__ at 0x0000020F94F10B70>, 'tt': <function Bar.tt at 0x0000020F94F10BF8>, '__dict__': 
# <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None}

print(bar.__dict__)
#{'name': 'alex'}

7、__str__方法

class Bar(object):

    def __str__(self):
        return "<bar object <Bar> at 000000xxxx>"

# 如果一个类中定义了__str__方法,那么在打印对象 时,默认输出该方法的返回值。
bar = Bar()
print(bar)  # <bar object <Bar> at 000000xxxx>


# 其实在其他的对象中也是这样实现,当打印该对象给用户一些提示信息
import threading
def test():
    pass
t = threading.Thread(target=test)
print(t)  #<Thread(Thread-1, initial)>

特性

1.封装

对类中属性和方法进行一种封装,隐藏了实现细节

1.封装是面向对象编程的一大特点
2.面向对象编程的第一步 将属性和方法封装到一个抽象的类中(为什么说是抽象的,因为类不能直接使用)
3.外界使用类创建对象,然后让对象调用方法
4.对象方法的细节都被封装在类的内部

2.继承

子类继承父类后,就具有了父类的所有属性和方法,先继承,后重写

实现代码的重用,相同的代码不需要重复的写,分为单继承和多继承

单继承:子类拥有父类的所有属性和方法
多继承:多继承可以让子类对象,同时具有多个父类的属性和方法

3.多态

一种接口,多种表现形式

中国人和美国人都能讲话,调用中国人的类讲中文,调用美国人将英文

以封装和继承为前提,不同的子类对象调用相同的方法,产生不同的执行结果

新式类&经典类

  • python3无论新式类还是经典类都是用 广度优先
  • python2中,新式类:广度优先,经典类:深度优先

在这里插入图片描述

class D:
    def talk(self):
        print('D')

class B(D):
    pass
    # def talk(self):
    #     print('B')

class C(D):
    pass
    def talk(self):
        print('C')

class A(B,C):
    pass
    # def talk(self):
    #     print('A')

a = A()
a.talk()

属性

公有属性:name
私有属性:_ _name


class Human:
    name='ren'          #公有属性 name、gender、age
    gender = 'male'
    age = '25'
    __money = 8000      #私有属性__money
 
zhangsan = Human()
zhangsan.name = 'zhangsan'
print(zhangsan.name)

公有方法: def  _ _funName(self):
私有方法: def funName(self):

def say(self):      #公有方法
        print("my name is %s ,i have %d "%(self.name,self.__money))
       
    def __lie(self):    #私有方法
        print("my name is %s ,i have %d "%(self.name,self.__money))




对象的属性(attribute)也叫做数据成员(data member)。

如果想指向某个对象的属性,可以使用格式:

  object.attribute

属性又分为:私有属性和公有属性。

私有属性是以两个下划线开头(__),私有成员在类的外部不能直接访问。

Python提供了一种特殊方式来访问私有成员:

  对象名._类名__私有属性名 (对象名是类实例后的对象)

公有属性既可以在类的内部进行访问,也可以在外部程序中使用。

Python还有一类比较特殊的内置属性,如:__doc__、__module__、__base__。

反射

  • 常用的就是hasattr、getattr这两个
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
# hasattr(ogj,name_str) 判断一个对象里是否有对应的字符串方法

class Dog(object):
    def eat(self,food):
        print("eat method!!!")
d = Dog()

#hasattr判断对象d是否有eat方法,有返回True,没有返回False
print(hasattr(d,'eat'))     #True
print(hasattr(d,'cat'))     #False
#getattr(obj,name_str) 根据字符串去获取obj对象里的对应的方法对应的内存地址

class Dog(object):
    def eat(self):
        print("eat method!!!")
d = Dog()

if hasattr(d,'eat'):          # hasattr判断实例是否有eat方法
    func = getattr(d, 'eat')  # getattr获取实例d的eat方法内存地址
    func()                    # 执行实例d的eat方法
#运行结果:  eat method!!!
#使用stattr给类实例对象动态添加一个新的方法

class Dog(object):
    def eat(self,food):
        print("eat method!!!")
d = Dog()

def bulk(self):               #给Dog类添加一个方法必须先写这个方法
    print('bulk method add to Dog obj')

d = Dog()
setattr(d,"bulk",bulk)        #将bulk方法添加到实例d中,命名为talk方法
d.bulk(d)                     #实例d调用刚刚添加的talk方法时必须将实例d自身当变量传入,因为他不会自己传入self



#使用stattr给类实例对象动态添加一个新的属性
class Dog(object):
    def __init__(self,name):
        self.name = name
    def eat(self,food):
        print("eat method!!!")
d = Dog('Fly')

#1 实例d中没有sex这个属性,就会动态添加一个属性   sex = Male
setattr(d,"sex",'Male')   #给实例d添加一个属性:sex=Male
print("将实例d添加一个新属性sex=Male:\t",d.sex)

#2 如果实例d中本身存在这个属性那么 新的值就会覆盖这个属性
setattr(d,'name','name change to jack')
print("原本名字是Fly改变后的名字是:\t",d.name)

# 运行结果:
# 将实例d添加一个新属性sex=Male:     Male
# 原本名字是Fly改变后的名字是:      name change to jack
#delattr删除实例属性

class Dog(object):
    def __init__(self,name):
        self.name = name
    def eat(self,food):
        print("%s is eating....."%self.name)
d = Dog("NiuHanYang")
choice = input(">>:").strip()
if hasattr(d,choice):
    delattr(d,choice)  #使用delattr(d,choice)删除实例的属性那么所以下面打印就会报错
print(d.name)

# 运行结果:
# >>:name   #输入的值是name
# 下面是报错信息
# Traceback (most recent call last):
#   File "C:/Users/admin/PycharmProjects/s14/Day7/test1.py", line 10, in <module>
  • 单例模式

1、单例模式:永远用一个对象得实例,避免新建太多实例浪费资源
2、实质:使用__new__方法新建类对象时先判断是否已经建立过,如果建过就使用已有的对象
3、使用场景:如果每个对象内部封装的值都相同就可以用单例模式

class Foo(object):
   instance = None
   def __init__(self):
      self.name = 'alex'

   def __new__(cls, *args, **kwargs):
      if Foo.instance:
         return Foo.instance
      else:
         Foo.instance = object.__new__(cls,*args,**kwargs)
         return Foo.instance

obj1 = Foo()       # obj1和obj2获取的就是__new__方法返回的内容
obj2 = Foo()
print(obj1,obj2)   # 运行结果: <__main__.Foo object at 0x00D3B450>    <__main__.Foo object at 0x00D3B450>

# 运行结果说明:
# 这可以看到我们新建的两个Foo()对象内存地址相同,说明使用的•同一个类,没有重复建立类

1.4 常识概念

重点

1.深浅拷贝

浅拷贝:不管多么复杂的数据结构,浅拷贝只会拷贝第一层

深拷贝:深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们对这两个变量中任意一个修改都不会影响其他变量

在这里插入图片描述

深拷贝就是将一个对象拷贝到另一个对象中,这意味着如果你对一个对象的拷贝做出改变时,不会影响原对象。在Python中,我们使用函数deepcopy()执行深拷贝

import copy
b=copy.deepcopy(a)
浅拷贝则是将一个对象的引用拷贝到另一个对象上,所以如果我们在拷贝中改动,会影响到原对象。我们使用函数function()执行浅拷贝

b=copy.copy(a)
与可变不可变数据的区别

可变(mutable)类型,比如列表、字典、集合,修改其中一个,另一个必定改变
不可变类型(immutable),比如数字、字符串、元组,修改了其中一个,另一个并不会变

2.垃圾回收机制

  • 引用计数

原理:

  • 当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.
  • 当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。

优点:

  • 引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。

缺点:

  • 引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的,
  • 显然比其它那些垃圾收集技术所带来的额外操作只是与待回收的内存数量有关的效率要低。
  • 同时,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉。

标记清除

  • 它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
  • 对象之间通过引用(指针)连在一起,构成一个有向图
  • 从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。
  • 根对象就是全局变量、调用栈、寄存器。
    在这里插入图片描述
  • 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5
  • 第一步将标记块1,并记住块2和3以供稍后处理。
  • 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。
  • 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。

分代回收

  • 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
  • Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
  • 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小
  • 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
  • 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
  • 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
引用计数:
记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数减1,一旦对象的引用计数为0,该对象立即被回收(Python语言默认采用的垃圾收集机制是 .引用计数法
标记清除:
第一段给所有活动对象标记,第二段清除非活动对象
分代回收:
python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,比如有年轻代、中年代、老年代,年轻代最先被回收

3.TCP/UDP

TCP协议:
1.可靠、慢、全双工通信
2.建立连接的时候 : 三次握手
3.断开连接的时候 : 四次挥手
4.在建立起连接之后
	发送的每一条信息都有回执.
	为了保证数据的完整性,还有重传机制.
5.长连接 :会一直占用双方的端口
6.IO(input,output)操作,输入和输出是相对内存来说的
	write / send -----> 输出 output
	read / recv ------> 输入 input
7.能够传递的数据长度几乎没有限制
UDP协议:
1.无连接的 速度快
2.可能会丢消息
3.能够传递的数据的长度是有限的,是根据数据传递设备的设置有关系
应用场景:
TCP 文件的上传下载(发送邮件、网盘、缓存电影)
UDP 即时通信类的(qq、微信、飞秋)
socket(套接字)

socket 是一个工作在应用层和传输层之间的抽象层。
	帮助我们完成了所有信息的组织和拼接.
	sokcet对于程序员来说 已经是网络操作的底层了
socket历史:
	(初期)基于文件通信 -------- 完成同一台机器上的两个服务之间的通信的
	(现在)基于网路通信 -------- 完成了多台机器之间的多个服务通信
socket用于TCP协议

# server.py 服务端
import socket

sk = socket.socket()  
# socket()参数中:family(基于……通信)=AF_INET(网络通信), type(协议)=SOCK_STREAM(TCP协议),TCP协议默认不用写,如果想要写协议必须是:type=socket.SOCK_STREAM
sk.bind(('192.168.12.25',9000))
sk.listen()
while True:
    conn,addr = sk.accept()
    while True:
        msg = conn.recv(1024)
        if msg.decode('utf-8').upper() == 'Q':
            break
        print(msg.decode('utf-8'))
        cont = input('内容(输入Q断开):')
        conn.send(cont.encode('utf-8'))
        if cont.upper() == 'Q':
            break
    conn.close()
sk.close()

# client.py 客户端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
    msg = sk.recv(1024)
    if msg.decode('utf-8').upper() == 'Q':
        break
    print(msg.decode('utf-8'))
    cont = input('内容(输入Q断开):')
    sk.send(cont.encode('utf-8'))
    if cont.upper() == 'Q':
        break
sk.close()

sk.accept() 与 sk.connect() 是建立三次握手的过程
socket用于UDP协议

# server.py 服务端
import socket

sk = socket.socket(type = socket.SOCK_DGRAM) # UDP协议必须加上 type=socket.SOCK_DGRAM
sk.bind(('127.0.0.1',9000))
while True:
    msg,client_addr = sk.recvfrom(1024) # recvfrom用于不知道对方ip时,获取到ip给client_addr
    msg = msg.decode('utf-8')
    print(msg)
    msg1 = input('>>>').encode('utf-8')
    sk.sendto(msg1,client_addr)
sk.close()

# client.py 客户端
import socket

sk = socket.socket(type = socket.SOCK_DGRAM)
while True:
    inp = input('>>>').encode('utf-8')
    sk.sendto(inp,('127.0.0.1',9000))
    msg = sk.recv(1024).decode('utf-8') # 这里不需要使用recvfrom,因为知道对方ip地址
    print(msg)

sk.close()

次重点

1.高阶函数

map()函数

格式:map(func, iter)

说明:
	接收两个参数:一个函数和一个可迭代对象
	返回值:返回一个生成器
	生成器内容是将func依次作用域iter每个元素的处理结果

def func(x):
    return x * x
print map(func,[1,2,3,4,5]) 

注解:
1.list里的每个元素都会走一遍f(x)方法
2.输出结果是[1,4,9,16,25]
filter( )函数

格式:filter(func, iter)

说明:
	参数是一个函数和一个可迭代对象
	返回一个生成器
	将func依次作用于iter中的元素,返回值为真的将会保留,为假的将会过滤掉

lt = [1, 2, 3, 4, 5]

# 提取偶数
f = filter(lambda x: x%2==0, lt)
print(list(f))

注解:lt中数据会走一遍匿名函数就是除以2余数为零,然后filter过滤符合要求(偶数)的打印出来,不符合要求的过滤掉 

输出结果为 2,4
reduce()函数

格式:reduce(func, iter)

说明:
	接收两个参数,一个函数和一个可迭代对象
	首先取前两个元素作为func的参数,计算完的结果与第三个元素继续使用func处理,直至结束
	返回处理的最后结果

from functools import reduce

lt = [1, 2, 3, 4, 5]

# 求和
# s = reduce(lambda x,y: x+y, lt)
# 转换为12345
s = reduce(lambda x,y: x*10+y, lt)
print(s)

输出结果为12345
sorted()函数

sorted() 函数对所有可迭代的对象进行排序操作。
sort 与 sorted 区别:
	sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
	list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

sorted 语法:
	sorted(iterable[, cmp[, key[, reverse]]])

参数说明:
	iterable -- 可迭代对象。
	cmp -- 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0。
	key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
	reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。


print(sorted([1,5983]))
输出结果为 [1,3,5,8,9]

2.读写文件

python文件对象提供了三个"读"方法: read()、readline() 和 readlines()。每种方法可以接受一个变量以限制每次读取的数据量。

read() 每次读取整个文件,它通常用于将文件内容放到一个字符串变量中。如果文件大于可用内存,为了保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。

readlines() 之间的差异是后者一次读取整个文件,象 .read() 一样。.readlines() 自动将文件内容分析成一个行的列表,该列表可以由 Python 的 for ... in ... 结构进行处理。

readline() 每次只读取一行,通常比readlines() 慢得多。仅当没有足够内存可以一次读取整个文件时,才应该使用 readline()。

注意:这三种方法是把每行末尾的'\n'也读进来了,它并不会默认的把'\n'去掉,需要我们手动去掉。

f = open('test.txt', 'r')
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:
f = open('test.txt', 'w') # 若是'wb'就表示写二进制文件
f.write('Hello, world!')
 f.close()

python文件对象提供了两个"写"方法: write() 和 writelines()。

write()方法和read()、readline()方法对应,是将字符串写入到文件中。
writelines()方法和readlines()方法对应,也是针对列表的操作。它接收一个字符串列表作为参数,将他们写入到文件中,换行符不会自动的加入,因此,需要显式的加入换行符。
关于open()的mode参数:

'r':读

'w':写

'a':追加

'r+' == r+w(可读可写,文件若不存在就报错(IOError)'w+' == w+r(可读可写,文件若不存在就创建)

'a+' ==a+r(可追加可写,文件若不存在就创建)

对应的,如果是二进制文件,就都加一个b就好啦:

'rb'  'wb'  'ab'  'rb+'  'wb+'  'ab+'

3.常用模块

subprocess模块

提供了一种一致的方法来创建和处理附加进程,与标准库中的其它模块相比,提供了一个更高级的接口。
常用方法:
	run: 返回一个表示执行结果的对象
	call: 返回执行的状态码

总结: subprocess的好处是可以获取指令的执行结果,且执行指令时可以在子进程中,避免造成主进程卡死

import subprocess
 
# run 执行指令并返回,PIPE:通道
res = subprocess.run('tasklist',shell=True,stdout=subprocess.PIPE)
print(res)
print(res.stderr)
print(res.stdout.decode('gbk'))
 
call 父进程等待子进程执行命令,返回子进程执行命令的状态码,如果出现错误,不进行报错
res = subprocess.call('tasklist',shell=True)
print(res)
 
'''
注意:
    shell默认为False,在Linux下,shell=False时, Popen调用os.execvp()执行args指定的程序;
    shell=True时,如果args是字符串,Popen直接调用系统的Shell来执行args指定的程序,
    如果args是一个序列,则args的第一项是定义程序命令字符串,其它项是调用系统Shell时的附加参数。
  
    在Windows下,不论shell的值如何,Popen调用CreateProcess()执行args指定的外部程序。
    如果args是一个序列,则先用list2cmdline()转化为字符串。
    但需要注意的是,并不是MS Windows下所有的程序都可以用list2cmdline来转化为命令行字符串。
    在windows下,调用脚本时要写上shell=True。
Popen :subprocess模块中只定义了一个类: Popen
      Popen对象创建后,主程序不会自动等待子进程完成。
      我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block)
'''
 
stdout=subprocess.PIPE,stderr=subprocess.PIPE 使用管道将stdout和stderr的输出为打印输出
res1 = subprocess.Popen('tasklist', stdout=subprocess.PIPE, shell=True, stderr=subprocess.PIPE)
print(res1)
print(res1.stdout.read().decode('gbk'))
print(res1.stderr.read().decode('gbk')) # 错误信息返回
 
# 将上述的输出结果作为输入给res2进行处理
res2 = subprocess.Popen('findstr',stdout=subprocess.PIPE,shell=True,stderr=subprocess.PIPE,stdin=res1)
print(res2)
print(res2.stdout.read().decode('gbk'))
print(res2.stderr.read().decode('gbk'))
re模块


findall: findall(pattern,string),查找所有满足条件的字符

search: search(pattern,string[,flags]),在字符串中查找,返回第一个匹配的字符串 分装返回对象为,span = (0,5) (匹配的位置)左闭右开

match : match(pattern,string[,flags]) 在字符串开头查找,与search返回值相同

split:split(pattern,string[,maxsplit=0])根据模式切割字符串

compilecompile(pattern,[,flags]) 根据包含正则表达式的字符串创建模式对象

sub : sub(pat,repl,string[,count=0]) 将字符串中模式pat匹配的子串都替换为repl

paramiko模块

paramiko包含两个核心组件:SSHClient和SFTPClient。
SSHClient的作用类似于Linux的ssh命令,是对SSH会话的封装,该类封装了传输(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用于执行远程命令。

SFTPClient的作用类似与Linux的sftp命令,是对SFTP客户端的封装,用以实现远程文件操作,如文件上传、下载、修改文件权限等操作。

4.python2和python3的区别

python解释器默认编码(python2与python3的区别一)
	python2 解释器默认编码:ascii
	python3 解释器默认编码:utf-8
输入(python2与python3的区别二)
	python2:name=raw_input('请输入姓名')
	python3:name=input('请输入你的姓名')
输出(python2与python3的区别三)
	python2:print "你好"
	python3:print("你好")
数字表示(python2与python3的区别四)
	python2
		64位机器,范围-2^63~2^63-1	
		超出上述范围,python自动转化为long(长整型)	
		注:long(长整型)数字末尾有一个L
	python3
		所有整型都是int,没有long(长整型)
整型除法(python2与python3的区别五)
	python2:只能保留整数位
	python3:可以保留所有内容
range / xrange(python2与python3的区别六)
	python2:
		xrange:不会在内存中立即创建,而是在循环时,边循环边创建
		range:在内存立即把所有的值创建
	python3:
		只有range,相当于python2中的xrange
		range:不会在内存中立即创建,而是在循环时,边循环边创建
包的定义(python2与python3的区别七)
	python2:文件夹中必须有_ _ init _ _.py文件
	python3:不需要有_ _ init _ _.py文件
字典的keys / values / items方法(python2与python3的区别八)
	python2:返回列表,通过索引可以取值
	python3:返回迭代器,只能通过循环取值,不能通过索引取值
map / filter(python2与python3的区别九)
	python2:返回列表,直接创建值,可以通过索引取值
	python3:返回迭代器,不直接创建值,通过循环,边循环边创建
str(字符串类型)的区别(python2与python3的区别十)(最大区别,优先写这个)
	python2:
		str类型,相当于python3中的字节类型,utf-8/gbk等其他编码
		unicode类型,相当于python3中的字符串类型,unicode编码
		python2中没有字节类型
	python3:
		str类型,字符串类型,unicode编码
		python3中没有unicode类型

5.with(上下文管理)

1.with是一种上下文管理协议,目的在于从流程图中把 try,exceptfinally 关键字和资源分配释放相关代码统统去掉,
2.使用with处理的对象必须有enter()和exit()这两个方法,
3.with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

作用:用于资源的获取和释放

6.is和==比较

is

比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址。
==

比较的是两个对象的内容是否相等,默认会调用对象的__eq__()方法。
发布了84 篇原创文章 · 获赞 1 · 访问量 2089

猜你喜欢

转载自blog.csdn.net/lxp_mocheng/article/details/103821153
今日推荐