基础部分
1.可变与不可变类型;
strings, tuples, 和 numbers 是不可更改的对象,list,dict,set 等则是可以修改的对象
2.单下划线和双下划线的区别
“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;
“双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
以双下划线开头和结尾的(__foo__)代表python里特殊方法专用的标识,如__init__()代表类的构造函数。
3.__new__和__init__的区别
__new__()用于创建实例,所以该方法是在实例创建之前被调用,它是类级别的方法,是个静态方法;
__init__()用于初始化实例,所以该方法是在实例对象创建后被调用,设置对象属性的一些初始值,通常用在初始化一个类实例的时候, 是一个实例方法;
__new__先被调用,__init__后被调用,__new__的返回值(实例)将传递给__init__方法的第一个参数,然后__init__给这个实例设置一些参数。
Python 的 call 方法可以让类的实例具有类似于函数的行为,通常用于定义和初始化一些带有上下文的函数。
区别
__new__()至少有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别;
__init__()至少有一个参数self,就是这个__new__()返回的实例__init__()在__new__()的基础上完成一些初始化的操作。
__new__()必须有返回值,返回实例对象;
__init__()不需要返回值。
浅拷贝和深拷贝
copy浅拷贝:没有拷贝子对象,所以原始数据改变,子对象改变
deepcopy深拷贝:包含对象里面的子对象的拷贝,所以原始对象的改变不会造成深拷贝里的任何子元素的改变
设计模式
单例模式
目的是保证某一个实例对象只会存在一个,减少资源的消耗实现方式有很多种,
第一种,重写__new__方法实现
定义一个实例变量,在 __new__ 方法中保证这个变量仅仅初始化一次
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
第二种,闭包定义装饰器
def singleton(cls):
"""
定义单例的装饰器(闭包)
:param cls:
:return:
"""
_instance = {}
def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return _singleton
工厂模式
第一种,简单工厂模式
简单工厂是最常见的工厂模式,适用于简单的业务场景。首先,定义一个工厂类,创建一个静态方法,根据输入的类型,返回不同的对象
class FactorySimple(object):
"""简单工厂模式"""
@staticmethod
def get_fruit(fruit_name):
if 'a' == fruit_name:
return Apple()
elif 'b' == fruit_name:
return Banana()
elif 'o' == fruit_name:
return Orange()
else:
return '没有这种水果'
第二种,工厂方法模式
工厂方法将创建对象的工作让相应的工厂子类去实现,保证在新增工厂类时,不用修改原有代码。首先,创建一个抽象公共工厂类,并定义一个生产对象的方法
import abc
from factory.fruit import *
class AbstractFactory(object):
"""抽象工厂"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def get_fruit(self):
pass
接着,创建抽象工厂类的 3 个子类,并重写方法,创建一个实例对象并返回
class AppleFactory(AbstractFactory):
"""生产苹果"""
def get_fruit(self):
return Apple()
class BananaFactory(AbstractFactory):
"""生产香蕉"""
def get_fruit(self):
return Banana()
class OrangeFactory(AbstractFactory):
"""生产橘子"""
def get_fruit(self):
return Orange()
第三种,抽象工厂模式
如果一个工厂要生产多个产品,使用工厂方法的话,就需要编写很多工厂类,不太实用,使用抽象工厂就可以很好的解决这个问题。
以川菜馆和湘菜馆炒两个菜,毛血旺和小炒肉为例
首先,创建川菜毛血旺、川菜小炒肉、湘菜毛血旺、湘菜小炒肉 4 个类
class MaoXW_CC(object):
"""川菜-毛血旺"""
def __str__(self):
return "川菜-毛血旺"
class XiaoCR_CC(object):
"""川菜-小炒肉"""
def __str__(self):
return "川菜-小炒肉"
class MaoXW_XC(object):
"""湘菜-毛血旺"""
def __str__(self):
return "湘菜-毛血旺"
class XiaoCR_XC(object):
"""湘菜-小炒肉"""
def __str__(self):
return "湘菜-小炒肉"
然后,定义一个抽象工厂类,内部定义两个方法,可以生成毛血旺和小炒肉
class AbstractFactory(object):
"""
抽象工厂
既可以生产毛血旺,也可以生成小炒肉
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def product_maoxw(self):
pass
@abc.abstractmethod
def product_xiaocr(self):
pass
接着,创建抽象工厂类的两个子类,川菜工厂和湘菜工厂,重写方法,然后创建对应的实例对象返回
class CCFactory(AbstractFactory):
"""川菜馆"""
def product_maoxw(self):
return MaoXW_CC()
def product_xiaocr(self):
return XiaoCR_CC()
class XCFactory(AbstractFactory):
"""湘菜馆"""
def product_maoxw(self):
return MaoXW_XC()
def product_xiaocr(self):
return XiaoCR_XC()
编码与解码
从内存(文本信息字符串)存到磁盘(二进制字节数据)的过程,称为编码。
从磁盘(二进制字节数据)到内存(文本信息字符串)的过程的过程,称为解码。
在计算机内存中统一使用Unicode编码,当保存到硬盘或者需要传输时,就转换到UTF-8编码。
列表生成式和生成器的优劣
列表推导式是将所有的值一次性加载到内存中
生成器是将列表推导式的[]改成(),不会将所有的值一次性加载到内存中,延迟计算,一次返回一个结果,它不会一次生成所有的结果,这对大数据量处理,非常有用
# 生成器函数: 一个函数中包含了yield关键,那么这个函数就不是普通的函数,是一个生成器函数
# 调用生成器函数,不会立马执行该函数里面的代码, 而是会返回一个 生成器
列表推导式可以遍历任意次
生成器只能遍历一次
迭代器、生成器、可迭代对象
可迭代对象与迭代器
1)可迭代对象包含迭代器。
2)如果一个对象拥有__iter__方法,其是可迭代对象;如果一个对象拥有next方法,其是迭代器。
3)定义可迭代对象,必须实现__iter__方法;定义迭代器,必须实现__iter__和next方法。
“一同两不同”:相同的是都可惰性迭代,不同的是可迭代对象不支持自遍历(即next()方法),而迭代器本身不支持切片(即__getitem__() 方法)。
iter()方法和next()方法
1)__iter__()
该方法返回的是当前对象的迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。
写法一:用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
写法二:用于迭代器类的写法,直接返回self(即自己本身),表示自身即是自己的迭代器。
2)next()
返回迭代的每一步,实现该方法时注意要最后超出边界要抛出StopIteration异常。
#!/usr/bin/env python
# coding=utf-8
class MyList(object): # 定义可迭代对象类
def __init__(self, num):
self.data = num # 上边界
def __iter__(self):
return MyListIterator(self.data) # 返回该可迭代对象的迭代器类的实例
class MyListIterator(object): # 定义迭代器类,其是MyList可迭代对象的迭代器类
def __init__(self, data):
self.data = data # 上边界
self.now = 0 # 当前迭代值,初始为0
def __iter__(self):
return self # 返回该对象的迭代器类的实例;因为自己就是迭代器,所以返回self
def next(self): # 迭代器类必须实现的方法
while self.now < self.data:
self.now += 1
return self.now - 1 # 返回当前迭代值
raise StopIteration # 超出上边界,抛出异常
my_list = MyList(5) # 得到一个可迭代对象
print type(my_list) # 返回该对象的类型
my_list_iter = iter(my_list) # 得到该对象的迭代器实例,iter函数在下面会详细解释
print type(my_list_iter)
for i in my_list: # 迭代
print i
问题:上面的例子中出现了iter函数,这是什么东西?和__iter__方法有关系吗?
其实该函数与迭代是息息相关的,通过在Python命令行中打印“help(iter)”得知其有以下两种用法。
用法一:iter(callable, sentinel)
不停的调用callable,直至其的返回值等于sentinel。其中的callable可以是函数,方法或实现了__call__方法的实例。
用法二:iter(collection)
1)用于返回collection对象的迭代器实例,这里的collection我认为表示的是可迭代对象,即该对象必须实现__iter__方法; 事实上iter函数与__iter__方法联系非常紧密,iter()是直接调用该对象的__iter__(),并把__iter__()的返回结果作为自己的返回值,故该用法常被称为“创建迭代器”。
2)iter函数可以显示调用,或当执行“for i in obj:”,Python解释器会在第一次迭代时自动调用iter(obj),之后的迭代会调用迭代器的next方法,for语句会自动处理最后抛出的StopIteration异常。
生成器
生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法。
生成器在迭代的过程中可以改变当前迭代值,而修改普通迭代器的当前迭代值往往会发生异常,影响程序的执行。
#!/usr/bin/env python
# coding=utf-8
def myList(num): # 定义生成器
now = 0 # 当前迭代值,初始为0
while now < num:
val = (yield now) # 返回当前迭代值,并接受可能的send发送值;yield在下面会解释
now = now + 1 if val is None else val # val为None,迭代值自增1,否则重新设定当前迭代值为val
my_list = myList(5) # 得到一个生成器对象
print my_list.next() # 返回当前迭代值
print my_list.next()
my_list.send(3) # 重新设定当前的迭代值
print my_list.next()
print dir(my_list) # 返回该对象所拥有的方法名,可以看到__iter__与next在其中
具有yield关键字的函数都是生成器,yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会。在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直至遇到下一个yield。
range()函数
1)它表示的是左闭右开区间;
2)它接收的参数必须是整数,可以是负数,但不能是浮点数等其他类型;
3)它是不可变的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素;
4)它是可迭代对象,却不是迭代器。
内置函数实现迭代器
map() 根据提供的函数对指定序列做映射
filter()用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表
zip()用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
enumerate()用于将一个可遍历的对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中
reduce()对参数序列中元素进行累积
>>>def square(x):
>>> return x ** 2
>>>list(map(square,[1,2,3,4,5]))
[1, 4, 9, 16, 25]
#可以结合lambda函数使用
>>>list(map(lambda x,y :x + y, [1,3,5,7,9],[2,4,6,8,10])) #对相同位置的列表进行相加
[3, 7, 11, 15, 19]
>>>def is_odd(n):
>>> return n % 2 == 1
>>>newlist = filter(is_odd,[1,2,3,4,5,6,7,8,9,10])
>>>print(list(newlist))
[1, 3, 5, 7, 9]
>>>a = [1,2,3]
>>>b = [4,5,6]
>>>list(zip(a,b))
[(1, 4), (2, 5), (3, 6)]
>>>a = [1,2,3]
>>>b = [4,5,6]
>>>zipped = zip(a,b)
>>>print(list(zip(*zipped)))
[(1, 2, 3), (4, 5, 6)]
#与zip相反,*zippped可理解为解压,返回二维矩阵式
>>>seasons = ['spring','summer','fall','winter']
>>>print(list(enumerate(seasons)))
[(0, 'spring'), (1, 'summer'), (2, 'fall'), (3, 'winter')]
>>>print(list(enumerate(seasons,start = 1))) #下标从1开始
[(1, 'spring'), (2, 'summer'), (3, 'fall'), (4, 'winter')]
>>>seq = ['Tom','Jerry','Bob']
>>>for i ,element in enumerate(seq):
>>> print(i,element)
0 Tom
1 Jerry
2 Bob
>>>from functools import reduce
>>>def add(x,y):
>>> return x + y
>>>reduce(add,[1,2,3,4,5])
15
#等同于
>>>from functools import reduce
>>>reduce(lambda x,y :x + y,[1,2,3,4,5])
15
什么是装饰器;如果想在函数之后进行装饰,应该怎么做;
装饰器是一个函数,这个函数的主要作用是包装另一个函数或类包装的目的是在不改变原函数名的情况下改变被包装对象的行为。可以在让其它函数不做任何变动的情况下提供额外的功能
把装饰器函数(被装饰函数)赋值给被装饰函数。
引用计数: python里一切皆为对象, 比方说赋值操作x=1, y=1, x和y的地址是相同的, 都指向1这个对象, 这时1的引用计数是2, 再执行赋值语句x=2, y=2, 那么x和y都指向2这个整型对象, 1的引用计数变为0, 于是1被回收机制回收, 从内存删除。
介绍下垃圾回收:引用计数/分代回收/孤立引用环;
何时进行垃圾回收:频繁的垃圾回收会影响工作效率, 于是在内存中分配对象和取消分配对象的差值达到一定阙值的时候, 才会进行垃圾回收, gc模块的get_threshold函数可以查看该阙值, 调用返回(700, 10, 10), 700就是我们之前说的阙值, 后面两个10与后面的分代回收有关。
分代回收: 这一策略的基本假设是存活越久的对象, 越不可能在后面的程序中变成垃圾, 我们将所有的对象分为0, 1, 2三代, 垃圾扫描时, 每次都扫面0代对象, 达到一定次数时转为扫描0, 1代对象, 再达到一定次数时, 扫描0,1,2代对象, 上面所说的两个次数就是get_threshold返回的元组的后两个值。
我们可以用set_threshold来改变阙值, 增加1代或2代对象的扫描频率。
孤立引用环:
a = []
b = [a]
a.append(b)
此时a所引用的兑现指向b所引用的对象, b所引用的对象指向a所引用的对象, 即使删除了a,b两个引用, 两个对象的引用计数也不为0, 不会被回收, 但程序已经无法利用这两个对象了。
如何回收引用环?
为了回收这样的引用环, python复制每个对象的引用计数, 记为gc_ref, 对象i的引用计数记为gc_ref_i, python会遍历所有的对象, 对象i引用的对象j的引用计数gc_ref_j会减1;
在结束遍历后, gc_ref不为0的对象, 和该对象引用的对象, 以及下游被引用的对象都会保留, 其它的对象会被回收。
多进程与多线程的区别;CPU密集型适合用什么;
多线程:
在单个程序中同时运行多个线程完成不同的工作,称为多线程。线程共享内存空间;进程的内存是独立的,
同一个进程的线程之间可以直接交流;两个进程想通信,必须通过一个中间代理来实现,
一个线程可以控制和操作同一进程里的其他线程;但是进程只能操作子进程
优缺点:
1.多进程的优点是稳定性好,一个子进程崩溃了,不会影响主进程以及其余进程。
但是缺点是创建进程的代价非常大,因为操作系统要给每个进程分配固定的资源。
2.多线程优点是效率较高一些,但是致命的缺点是任何一个线程崩溃都可能
造成整个进程的崩溃,因为它们共享了进程的内存资源池。
*CPU密集型适合用多进程开发
1、CPU密集型代码(各种循环处理、计算等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。
python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。
- GIL在python中的版本差异:
1、在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100时进行释放。(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
2、在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。
Python常用模块
requests、re、json、random、os、datetime
Linxu常用命令
ls,cd,cat,clear,mkdir,pwd,rm,ps -ef|grep,find,mv,su
ps -ef|grep uwsgi
telnet 127.0.0.1 8000
netstat -nutlp |grep 80
介绍下协程,为何比线程还快?
协程类似于线程, 基于线程, 但是协程允许一个执行过程A中断, 转而执行过程B, 然后再执行过程A;
优势在于:
协程理论上数量有无限个, 没有线程之间的切换动作,所以比较快;
没有锁机制, 因为所有协程都在一个线程中;
请求频率限制
最近做了一个项目,通过调用短信网关实现手机号和验证码登录。但是如果有人恶意调用,会发出很多无谓的短信,而且会增加短信服务的费用。于是对接口做了请求频率限制 Rate limiting。例如限制一个用户1分钟内最多可以范围100次。这里简要的复现一下实现思路。
思路:以类名+调用方法名+ip作为key
当用户调用接口的时候,先查询redis中是否有存在该key,获取该key所对应的value,比较value和frequency,如果小于frequency,则在原来的基础上value++;如果大于则返回访问频率过于频繁。
如果不存在,则将该key存入redis,value为1,设置过期时间。