2021-05-19

Python基础编

1、列表list的底层实现
列表实现是基于数组或基于链表结构的
python采用动态顺序表结构,每次储存元素的空间使用完之后,将动态分配原有储存空间两倍的大小的新内存作为存储空间。
list实际申请内存空间的大小与实际存储元素所占空间的大小要大,为了避免每次有新元素加入list时都要调用realloc进行内存分配。
list增长的模型是:0, 4, 8, 16, 25, 35, 46, 58, 72,.....会申请多余的空间以避免调用多次list_resize()函数
append和insert的时候在实际申请内存空间未占满时不会申请新的内存空间;在实际申请内存空间占满时会根据list增长模型申请新的内存空间。
pop和remove的时候弹出元素,也会把内存空间释放。
insert的时间复杂度是O(n)
Remove的时间复杂度为O(n)
append的时间复杂度为O(1)
2、字典底层的实现:
字典是通过散列表或说哈希表实现的;散列表是一个稀疏数组(总是有空白元素的数组),数组的每个单元叫做 bucket。
每个 bucket 有两部分:一个是键对象的引用,一个是值对象的引用。
1. 键必须可散列(及键必须是不可变类型),键唯一是通过支持hash()函数和支持通过__eq__()方法检测相等性。
2. 字典在内存中开销巨大,典型的空间换时间。
3. 键查询速度很快
4. 往字典里面添加新建可能导致扩容,导致散列表中键的次序变化。因此,不要在遍历字典的同时进行字典的修改。
3、Python的反射机制:
反射的定义:检测和修改它本身状态或行为的一种能力(自省)。
核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!
譬如导入模块, 调用函数,都是一种反射机制,也是设计模式,编程方法的体现,高内聚,松耦合的思想。
通过反射,Python可以通过字符串的映射或修改程序运行的状态和方法。
反射的四个方法:
	hasattr:判断一个方法是否存在于这个类中;
	getattr:根据字符串去获取obj对象里的对应的方法的内存地址,加"()"括号即可执行;
	setattr:通过setattr将外部的一个函数绑定到实例中;
	delattr:删除一个实例或者类中的方法。
好处:实现可插拔机制;动态导入模块(基于反射当前模块成员)
4、python的魔法方法:
定义:魔法方法是python内置方法,不需要主动调用,存在的目的是为了给python的解释器进行调用,几乎每个魔法方法都有一个对应的内置函数,或者运算符,当我们对这个对象使用这些函数或者运算符时就会调用类中的对应魔法方法。
__new__: 是一个实例化方法,在实例化对象时触发,创建对象,至少一个cls参数接收当前类,必须返回一个对象实例,先触发__new__才会触发__init__。
__init__: 初始化类的时候被调用,没有返回值。
__call__: 让类的实例具有类似于函数的行为, 对象后面加括号,触发执行。即:对象() 或者 类()();
__del__:销毁的方法,触发时机为当一个对象在内存中被销毁的时候自动调用,至少一个self参数,接收对象;
__str__: 使用print(对象)或者str(对象)的时候触发,一个self参数接收对象,返回字符串类型的数据,
__hash__: Hash算法,是将一个不定长的输入,通过哈希函数变换成一个定长的输出,即哈希值。哈希变换是一种单向运算,具有不可逆性,常见的hash算法有:SM3、MD5、SHA-1等。
		  在python中有内置的哈希函数hash(),返回一个对象(数字、字符串,不能直接用于 list、set、dict)的哈希值,在python中set集合要求数据类型是可哈希的,
		  因为set集合会默认调用对象的__hash__函数进行快速查询,如果找到了则调用对象的__eq__判断两个是是否相同,如果相同则不添加。保证数据的唯一性(自动去重功能)。Hash值是对象id值1/16
__eq__: 默认比较的是内存地址。
__repr__: 改变对象的字符串显示,算是__str__的备胎吧。但如果是使用%r作为输出,那么调用的就是__repr__方法;
__slots__: 限制实例的属性,定义的属性仅对当前类实例起作用,对继承的子类是不起作用的;
__iter__: 将一个对象变为可迭代对象;
__next__将一个对象变为迭代器。
__sizeof__: 查看内存空间。
5、类对象,实例对象,类变量,实例变量,类方法,实例方法,静态方法,元类
class Foo:
		class_obj = 'leiduixiang' # 表示类变量,对用于全局,声明在类内部,在函数体外,类内部任意位置。只会保存在类的命名空间中。

		def __init__(self, name):
			self.name = name  # 表示实例变量,声明在实例方法内部,用self关键字修饰,实例变量保存在实例各自的命名空间中
		#类变量和实例变量都可以在类或者实例在对象声明前后绑定

		def run(self):
			print('---我是实例方法----第一个参数必须默认传实例对象,只能被实例对象调用--', self.name)

		@classmethod
		def class_func(cls, var):
			cls.var = var
			print('----类方法----,我可以被类对象和实例对象调用----', cls.var)
			print("注意:传入的第一个参数为cls,指代的是类本身,既能通过“类名.类变量名”的方式访问类变量,也能通过“cls.类变量名”的方式访问类变量")
			return cls.var

		@staticmethod
		def static_func():
			print('----静态方法----,我可以被类对象和实例对象调用----')
			print("注意:在静态方法内部,只能通过“类名.类变量名”的方式访问类变量")
			print("ps:静态方法不能直接访问实例变量")

	foo_obj = Foo("foo") # Foo表示类对象
	foo_obj.run() # foo_obj 表示实例对象

	Foo.age = 18 # 在类对象声明后绑定一个类变量
	print(Foo.age) # 可通过类对象调用
	print(foo_obj.age) # 也可通过实例对象调用,不推荐,会提示警告

	foo_obj.sex = '男' # 在实例对象声明后绑定一个实例变量
	print(foo_obj.sex) # 只可通过实例对象调用

	Foo.static_func()
	foo_obj.static_func()

	Foo.class_func('var')
	foo_obj.class_func('var')
	print(foo_obj.var)
	实例对象和类对象之间的对应关系,创建了一个实例对象之后,就在内存中开辟了一个实例对象的内存空间块,里面存着的是实例对象的一些基本属性,
	元类:__metaclass__修饰,为了拦截类的创建、修改类、返回修改之后的类。
6、单例模式
它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的共有函数用于创建或获取它本身的静态私有对象。
一些资源管理器常常设计成单例模式。
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时,如:日志文件,公共配置文件
优点:
	对唯一实例的受控访问
	单例相当于全局变量,但防止了命名空间被污染
优点:
	1、实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
	2、灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
缺点:
	1、开销:虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。	
class Singleton(object):  # __new__模式
		def __new__(cls, *args, **kwargs):
			if not hasattr(cls,'_instance'):
				orig=super(Singleton,cls)
				cls._instance=orig.__new__(cls,*args,**kwargs)
			return cls._instance

class MyClass(Singleton):
	a=1
obj = MyClass()
	
class Singleton(type):  # 元类的方式
	def __init__(self, name, bases, dict):
		super(Singleton,self).__init__(name,bases, dict)
		self._instance = None

	def __call__(self, *args, **kwargs):
		if self._instance is None:
			self._instance = super(Singleton,self).__call__(*args, **kwargs)
		return self._instance

class MyClass(object, metaclass=Singleton):
	a = 1

obj = MyClass()
	
def singleton(cls, *args, **kwargs): # 装饰器方式
	instances = {}		
	def _singleton():
		if cls not in instances:
			instances[cls] = cls(*args, **kwargs)
		return instances[cls]
	return _singleton

@singleton
class MyClass3(object):
	a = 1

obj = MyClass3()
7、Python中的作用域及命名空间
命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。
Python 的作用域一共有4种,分别是:
有四种作用域:
	L(Local):最内层,包含局部变量,比如一个函数/方法内部。
	E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
	G(Global):当前脚本的最外层,比如当前模块的全局变量。
	B(Built-in): 包含了内建的变量/关键字等,最后被搜索
8、read,readline和readlines及大数据读取处理
read: 读取整个文件,将文件内容放到一个字符串变量中。
	  劣势是:如果文件非常大,尤其是大于内存时,无法使用read()方法。
readline: 每次读取一行;读取时占用内存小,比较适合大文件.返回的是一个字符串对象,保持当前行的内存;
readlines: 读取整个文件所有行,保存在一个列表(list)变量中,每行作为一个元素,但读取大文件会比较占内存.
9、拷贝
直接赋值:其实就是对象的引用(别名)。
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
	a = {1: [1,2,3]}
	b = a.copy()
	print(a, b) # {1: [1, 2, 3]} {1: [1, 2, 3]}
	a[1].append(4)
	print(a, b, 2222) # {1: [1, 2, 3, 4]} {1: [1, 2, 3, 4]}
	import copy
	c = copy.deepcopy(a)
	a[1].append(5)
	print(a, c, 3333) # {1: [1, 2, 3, 4, 5]} {1: [1, 2, 3, 4]}
10、pickle模块

可以使对象序列化和反序列化,使一个对象持久化。

pickle.dump()  # 序列化 
pickle.load()  # 反序列化
11、Python参数的传递
Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传引用的一种综合。
如果函数收到的是一个可变对象(比如字典 或者列表)的引用,就能修改对象的原始值——相当于通过“传引用”来传递对象。
如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象—相当于通过“传值”来传递对象。
Python参数的传递取决于:(对可变对象和不可变对象的)赋值过程中是如何分配内存地址的。
12、位运算:
	& : 按位与操作, 只有 1&11, 其它情况为 0. 可用于进位运算

    |  :  按位或操作 , 只有 0|00 ,其他情况为1
   
    ~ : 逐位取反
   
    ^ :  异或, 相同为 0 ,相异为 1 . 可用于加操作 (不包括进位项 )
   
    << : 左移操作, 2的幂有关
   
    >> : 右移操作, 2的幂有关
 def calSum(data_list, result):
	for i in range(1 << len(data_list)): # << : 左移操作, 2的幂有关.表示存在列表长度2的幂等组合
		sum = 0
		tmp = str()
		for j in range(len(data_list)):
			if i & 1 << j:
				sum = sum + data_list[j]
				tmp = str(data_list[j]) if tmp == '' else tmp + ' + ' + str(data_list[j])

		if sum == result:
			print(i, bin(i))
		print(tmp + ' = 35')
# calSum(data_list, 35)
13、协程:
协程是一个微线程,自带CPU的上下文,看上去也是个子程序,但是在执行过程中,在子程序内部可中断(中断不是函数的调用,有点类似CPU),
然后转而执行别的子程序,在适当的时候再返回接着执行。的中断),然后转而执行别的子程序,在适当的时候再返回接着执行。
协程优势:
	1、协程有极高的执行效率,因为子程序切换不是线程切换,而是程序自身控制,所有没有线程切换开销;
	2、不需要多线程的锁机制(GIL),因为只有一个线程,不存在写变量冲突,控制共享资源不加锁,只需判断状态即可;
工作中运用多核CPU:多进程+协程
例子:
	传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
	改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
一句话总结:子程序就是协程的一种特例。
gevent是第三方库,通过greenlet实现协程,其基本思想是:
	当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。
	由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。
补充:monkey patch (猴子补丁):用来在运行时动态修改已有的代码,而不需要修改原始代码。
asyncio:
	1、通过async定义一个协程,该协程是一个对象,然后需要把协程对象加入到事件循环,由loop在适当的时候调用协程。
	2、在python3.7以前通过get_event_loop()函数获取事件循环对象,然后通过不同策略调用loop.run_forever()执行异步任务;在3.7后执行asyncio.run()即可;
	3、创建task对象,保存协程运行后的状态,用于未来获取协程结果;
	4、使用await可以正对耗时操作进行挂起,就像生成器里的yield一样,函数让出控制权。协程遇到await,事件循环就会挂起这个协程,执行别协程,直到其他协程也挂起或执行完毕,在进行下一个协程的执行;
	5、await 等待一个协程,也可以启动一个协程。asyncio.wait_for()超时等待;asyncio.wait()简单等待,阻塞;asyncio.gather()并发运行任务;
	6、asyncio.create_task() 函数用来并发运行作为 asyncio 任务的多个协程。
14、isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。

数据库(MySQL)

1、事务
事务是数据库并发控制的基本单位;
事务可以看作是一系列SQL语句的集合;
事务必须要么全部执行成功,要么全部执行失败(回滚)。
2、事物的ACID特性
原子性:一个事务中所有操作要么全部成功或失败;
一致性:事务开始和结束之后数据完整性没有被破坏;
隔离性:允许多个事务同时对数据库修改和读写;
持久性:事务结束之后,修改的永久不会丢失。----(其实是持久化到磁盘上)
3、事务的并发控制产生的四种异常问题
幻读:一个事物第二次查出现第一次没有的结果;
非重复读:一个事物重复读两次得到不同结果;
脏读:一个事务读取到另一个事物没有提交的修改;
丢失修改:并发写入造成其中一些修改丢失。
4、解决并发控制问题,定义四种事务隔离级别
读未提交(read uncommitted):别的事物可以读取到未提交的更改;
读已提交(read committed):只能读取已提交的数据;
可重复读(repeatable read):同一事物先后查询结果一样;-----MySQL引擎innoDb默认级别
串行化(Serializable):事务完全串行化的执行,隔离级别最高,但执行效率最低;
5、解决高并发场景下的插入重复问题
使用数据库的唯一索引;----分库分表时有局限
使用队列异步写入;
使用redis实现分布式锁。
6、悲观锁与乐观锁
悲观锁:先获取锁再进行操作,一锁二查三更新(select for update);
乐观锁:先修改,更新的时候发现数据已变更就回滚(check and set); ----乐观锁一般通过版本号或时间戳实现
选择:根据响应速度、冲突频率、重试代价来判断使用哪一种。
7、InnoDB与MyISAM
MyISAM不支持事务,InnoDB支持事务;
MyISAM不支持外键,InnoDB支持外键;
MyISAM只支持表锁,InnoDB支持表锁与行锁。
8、索引
MySQL实际使用的是B+Tree作为索引的数据结构;
B+Tree:只在叶子节点带有指向记录的指针,可以增加数的度;叶子节点通过指针相连,实现范围查询。
索引类型:普通索引、唯一索引、多列索引、主键索引、全文索引(innodb不支持)
索引创建时机:
	经常用作查询条件的字段(where条件);
	经常用作表连接的字段;
	经常出现在order by、group by之后的字段。
创建索引的注意事项:
	非空字段NOT NULL,MySQL很难对控制做查询优化;
	区分度高、离散度大。作为索引的字段值尽量不要有大量相同值;
	索引的长度不要太长,不然比较耗时。
索引失效:
	模糊匹配:以%开头的like语句,模糊搜索;
    类型隐转:出现隐式类型转换;
    最左匹配:没有遵循最左匹配前缀原则。(广度优先)
聚集索引与非聚集索引:
	聚集还是非聚集指的是B+Tree叶节点存的是指针还是数据记录;
	MyISAM索引与数据分离,使用的是非聚集索引;
	InnoDB数据文件就是索引文件,主键索引就是聚集索引。
9、慢查询
原因:通常是缺少索引,索引不合理或业务代码实现导致
方案:
	slow_query_log_file 开启并查询慢查询日志;
	通过explain排查索引问题;
	调整数据修改索引;优化业务代码逻辑。

Redis

1、缓存使用原因
缓解关系型数据库的并发访问压力;存储热点数据;
减少响应时间;内存IO速度比磁盘快;
提升吞吐量;redis等内存数据库可以支撑很大的并发;
2、Redis数据类型
string:底层实现为整数或sds
list:ziplist或double linked list
	 ziplist:通过一个连续的内存块实现list结构,其中的每个entry节点头部保存前后节点长度信息,实现双向链表功能。
hash:ziplist或hashtable
set:intset或hashtable
zset:skiplist(跳跃表)
	跳表:允许快速查询,插入和删除一个有序连续元素的数据链表。
		 跳跃列表的平均查找和插入时间复杂度都是O(logn)。
		 快速查询是通过维护一个多层次的链表,且每一层链表中的元素是前一层链表元素的子集。
		 原理:一开始时,算法在最稀疏的层次进行搜索,直至需要查找的元素在该层两个相邻的元素中间。这时,算法将跳转到下一个层次,重复刚才的搜索,直到找到需要查找的元素为止。
3、Redis持久化
RDB:把数据快照放在磁盘二进制文件中,dump.rdb
AOF:每一个写命令追加到appendonly.aof中
4、Redis事务
将多个请求打包,一次性、按序执行多个命令的机制;
Redis通过MULTI\EXEC\WATCH等命令实现事务功能;
5、Redis实现分布式锁
使用setnx实现枷锁,可以同时通过expire添加超时时间;
锁的value值可以使用一个随机的uuid或者特定的命名;
释放锁的时候,通过uuid判断是否是该锁,是则执行delete释放锁。
import uuid
import math
import time
from redis import WatchError


class RedisLock:
    def __init__(self, lock_name, acquire_timeout=3, lock_timeout=2):
        self.conn = redis.conn()
        self.lock_name = lock_name # 锁的名称
        self.acquire_timeout = acquire_timeout # 获取锁的超时时间,默认 3 秒
        self.lock_timeout = lock_timeout # 锁的超时时间,默认 2 秒
        self.identifier = str(uuid.uuid4()) # 锁的标识

    def acquire_lock_with_timeout(self):
        """
        基于 Redis 实现的分布式锁   
        """       
        lockname = f'lock:{self.lock_name}'
        lock_timeout = int(math.ceil(self.lock_timeout))

        end = time.time() + self.acquire_timeout

        while time.time() < end:
            # 如果不存在这个锁则加锁并设置过期时间,避免死锁
            if self.conn.set(lockname, self.identifier, ex=lock_timeout, nx=True):
                return identifier
            time.sleep(0.001)

        return False

    def release_lock(self):
        """
        释放锁
        """
        # python中redis事务是通过pipeline的封装实现的
        with self.conn.pipeline() as pipe:
            lockname = 'lock:' + self.lock_name
            while True:
                try:
                    # watch 锁, multi 后如果该 key 被其他客户端改变, 事务操作会抛出 WatchError 异常
                    pipe.watch(lockname)
                    iden = pipe.get(lockname)
                    if iden and iden.decode('utf-8') == self.identifier:
                        # 事务开始
                        pipe.multi()
                        pipe.delete(lockname)
                        pipe.execute()
                        return True

                    pipe.unwatch()
                    break
                except WatchError:
                    pass
            return False
6、主从及哨兵机制
7、缓存使用模式
Cache Aside:同时更新缓存和数据库(常用);
Read/Write Through:先更新缓存,缓存负责同步更新数据库;
Write Behind Caching:先更新缓存,缓存定期异步更新数据库;
8、缓存穿透
原因:由于大量缓存查不到就去数据库	取,数据库也没有要查的数据
解决:
	对于没查到返回为None的数据也做缓存;
	插入数据的时候删除相应缓存,或者设置较短的超时时间。
9、缓存击穿
原因:热点数据key失效导致大量请求打到数据库增加数据库压力
解决:
	分布式锁:获取锁的线程从数据库拉数据更新缓存,其它线程等待;
	异步后台更新:后台任务针对过期的key自动刷新。
10、缓存雪崩
原因:缓存不可用或大量缓存key同时失效,大量请求直接请求到数据库
解决:
	多级缓存:不同级别的key设置不同的超时时间;
	随机超时:key的超时时间随机设置,防止同时超时;
	架构层:提升系统的可用性。监控/报警系统的完善。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值