python基础内容
- 一、多线程与多进程
- 二、迭代器和生成器
- 三、闭包
- 四、装饰器和面向切面编程AOP
- 五、有和无序、可变变量和不可变变量
- 六、形参和实参
- 七、dict和set
- 八、for 和 while
- 九、赋值、浅拷贝和深拷贝
- 十、队列和列表
- 十、补丁
- 十一、描述器
- 十二、继承
- 十三、多态性
- 十四、try、raise、except、else和finally
- 十五、sort()和sorted()
- 十六、堆(heapq)
- 十七、静态函数, 类函数, 成员函数、属性函数的区别
- 十八、推导式
- 十九、map、reduce、filter、zip
- 二十、*args和**kargs
- 二十一、Local、global、nonlocal、Enclosing和Built-in
- 二十二、is和==
- 二十三、自省机制
- 二十四、lambda
- 二十五、单下划线和双下划线
- 二十六、__new__和__init__的区别
- 二十七、新式类和旧式类
- 二十八、read, readline和readlines
- 二十九、with
- 三十、range和xrange的区别
- 三十一、位运算
- 三十二、any和all
- 三十二、正数与负数的求模、整除运算
一、多线程与多进程
进程(Process):进程是正在执行的计算机程序的实例。每个进程都有自己的存储空间,用于存储正在运行的指令,以及用于需要存储和访问任何数据。
线程(Thread):cpu调度执行的最小单元,不能独立存在,依赖进程存在。线程是进程的组成部分,可以并行运行。一个进程中可以有多个线程,它们共享相同的内存空间,即父进程的内存空间。这意味着要执行的代码以及程序中声明的所有变量将由所有线程共享。
GIL线程全局锁:python为了保证线程的安全而采取的独立线程运行的限制, 也就是说 一个核只能在同一时间运行一个线程,使得多线程并不能实现真正的并发工作(伪并发,快速切换),每一次只能一个线程执行一个任务。只有多进程才能实现多核并发。
多核多线程适合单核I/O密集型任务:
IO传输过程分为:发送消息,等待返回消息。在等候返回消息时线程是不工作的(阻塞状态),此时可以切换到其它线程里,即python此时释放GIL, 其他线程得到GIL发送消息,…,这样保证了IO传输过程大量减少阻塞时间,提高io传输效率。
多核多进程适合CPU密集型任务:
程序使用CPU进行大量数据运算和处理的时候,单核CPU的处理能力有限,多核CPU同时开启,分开处理才能加快处理速度。而只有多进程才能真正实现多核并发。使用多线程的话,多线程有可能因为争夺资源而变慢。
二、迭代器和生成器
迭代器是一种可迭代的对象。普通的迭代器是将全部对象存储到内存里,然后可以通过迭代(next())逐个读取。
生成器是一种只能迭代一次的迭代器,不会将对象存储到内存里,是一种即生成即输出的迭代器。这种方法的优点是节约内存,在训练模型的时候遇到数据过多,显存不足的时候经常使用这种方法。
yield类似return,它能将一个函数返回成生成器,当迭代对象耗尽才会结束。
yeild的原理:python 的 generator 只保留栈帧上下文(yield暂停的方法),不保留调用栈,然后运行栈帧上下文的字节码,而且 generator 函数不允许 return。只有调用next()函数的时候才会执行函数语句,在for循环中会自动调用next()方法。函数执行过程中遇到一个yield会中断一次,返回一个迭代值,函数保存自己的变量和状态,下次迭代时从yield下一条语句继续执行(这个性质很重要),函数恢复之前状态,直到遇到下一个yield返回迭代值,这样循环。
a = (x*x for x in range(3)) 这样也是个生成器#小括号
(1)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。
(2)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。
(3)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。
(4)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。
(5)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)
三、闭包
原理:两个函数嵌套,外部函数返回内部函数的引用,外部函数一定会传入参数,外部函数起的是交换引用的作用
def f1(a):
def f2(b):
....
return f2(a)
作用:闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。
闭包函数需要满足两点:函数内部定义的函数;引用了外部变量但非全局变量。
四、装饰器和面向切面编程AOP
python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。装饰器函数的外部函数传入要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数
AOP:在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想
def f1(f): #f1为装饰器的名,f为装饰器传入的参数
def f2():
print(1)
f() #调用f
return f2
@f1
def a():
print(2)
a()
#output: 1
2
#通过将a()装饰成f1()的输入参数,然后调用a()的时候变成直接运行f1(),此时f1()就具有a()的功能
#假如再装饰个b(),那么调用b()时,f1()就会具有b()的功能
@f1
def b():
print(3)
b()
#output: 1
3
装饰器的优点是避免大量雷同代码,像上面的都要print(1),就可以把这部分代码写到装饰器里,然后将a()和b()装饰,之后调用a()和b()就都有这个功能了。
其实用@f1进行装饰就是替换x=f1(a)和x=f1(b),让代码更简洁
五、有和无序、可变变量和不可变变量
有/无序是指是否可索引
有序对象:list、tuple、str
无序对象:dict、set
可/不可变指对象存放在地址的值是否可以被改变
可变:list、set、dict
不可变:int、float、boole、str、tuple
提示:
- 不可变类型的数据作为实参传递, 修改形参不影响实参
- 可变类型的数据作为实参传递, 修改形参会导致实参跟着变化。但是形参赋值不会改变实参[可变传的是索引,所以修改形参,实参也会变,形参赋值指的是x=y+a(y是函数的形参,a为同类型的参数)这种方式,不是x=y这种简单的引用方式]。
六、形参和实参
形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
- 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
- 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
- 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。
七、dict和set
异:
set()只有key,dict()以key->value进行映射存储,dict的value可以是任何类型
同:
无序,都是key的集合,key不允许重复,并且不能为list,可以是tuple(只能用不可变类型作为key);
都是hash结构,查找的时间复杂度为O(1);
功能函数:
set():
add(key),添加key,key重复忽略
remove(key),删除key,key不存在会报错
dict():
.clear()清空
del xxx直接删除整个字典
.pop(key,x)/del xxx[key] 删除,key不存在会报错,其中pop具有第二个参数,key不存在默认返回第二个参数,要是第二个参数不存在也会报错
update()/xxx[key]=value 更新,key存在则更新value,不存在会创建一组key->value的映射
get(key,x)/xxx[key] 获取value,key不存在会报错,其中get具有第二个参数,key不存在默认返回第二个参数,要是第二个参数不存在也会报错
setdefault(key, default=None)查找key对应的value,要是key不存在会创建key,然后初始值为default
copy()浅拷贝
fromkeys(seq,x) 以seq中的元素作为key创建字典,x为初始值,没x初始值为None
has_key(key)查找key存不存在,存在返回True,不存在False
items()以列表返回可遍历的(key, value) 元组数组
keys()以列表返回一个字典所有的key
values()以列表返回字典中的所有value
popitem() 删除字典中的最后一对key和value,并将其返回
八、for 和 while
for的对象在循环的时候是不可变的(无论是dict、list、set),while可变
九、赋值、浅拷贝和深拷贝
对象的赋值就是简单的对象引用,会重新创建一个指针指向该对象的地址。
浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。其实就是一个指针指向地址。
深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此它的时间和空间开销要高。申请新地址,然后赋值。
十、队列和列表
Queue: 先进先出队列
LifoQueue: 后进先出队列
PriorityQueue: 优先级队列
deque: 双边队列
队列其实就是个list,只是不同的队列具有不同的进出队规则,只有deque具有所有的list功能,并且它还具有左右边添加和删除元素的功能。
list: 有序,元素可重复;remove(x) 没有匹配项则报错;x.extend(y)可以将x扩展开兼并y内的元素;append()只能在末尾处添加;在其它位置添加用insert(index,x);pop(i)指定索引越界则报错,默认删除最后一个;list.clear() 等价于del a[:],清空;index(x) 返回第一个匹配值,或没有匹配值报错;count(x) 不存在的值报错;.reverse()翻转;copy(),只有一层时深拷贝,嵌套为浅拷贝,嵌套时内层list保存的是地址,copy的时候是将地址copy了;浅拷贝的方法:列表生成式、for循环遍历(只对第一层深拷贝)、切片;深拷贝:deepcopy()(y=copy.deepcopy(x))
十、补丁
补丁是指在运行时通过类外部的函数对类或模块进行动态修改
功能:在不改变系统原来的代码或内置函数的情况下,来替成我们的代码
第一种:
mod_1.py
def mod1_function():
print("mod1_function")
mod_2.py
def mod2_function():
print("mod2_function")
def monkey_patch_module_2():
import sys
import mod_1
#mod_1.mod1_function() #print mod1_function
sys.modules['mod_1'] = __import__("mod_2")
import mod_1
#使用mod_2 code替换mod_1,此时再import mod_1时则是import mod_2
mod_1.mod2_function() #print mod2_function
monkey_patch_module_2()
output: mod2_function
第二种:
import mod_1
import mod_2
def mokey_patch_m_2():
mod_1.__name__ = "mod_2" #此可有可无,但是防止乱,还是加上好。
mod_1.mod1_function = mod_2.mod2_function #将mod_1的mod1_function的功能替换成mod_2的mod2_function
mokey_patch_m_2()
mod_1.mod1_function()
output: mod2_function
十一、描述器
作用: 可以控制我们访问属性、方法的行为
- 一个类实现了
__get__
(getattr,获取属性)、__set__
(setattr,设置属性)、__delete__
(删除属性)三个任意一个方法都称为描述器 - 如果一个类的类属性设置为描述器,那么它被称为此描述器的owner属主
描述器的定义划分:
- 如果一个类仅仅实现了
__get__()
方法,称为非数据描述器non-data descriptor - 如果一个类实现了
__get__()
, ___set__()
方法,称为数据描述器data descriptor
非数据描述器
参考链接:https://zhuanlan.zhihu.com/p/121459013
# 示例
class Student1:
def __init__(self):
self.course = 'Python'
print('Student1.__init__')
class Student2:
stu1 = Student1() # Student1()返回的是Student1类的实例
def __init__(self):
print('Student2.__init__')
print(Student2.stu1.course)
# 创建Student2的实例对象
stu2 = Student2()
print(stu2.stu1.course)
# 示例:引入描述器
class Stduent1:
def __init__(self):
self.course = 'Python'
print('Stduent1.__init__')
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
class Stduent2:
stu1 = Stduent1()
def __init__(self):
print('Stduent2.__init__')
print(Stduent2.stu1.course)
# Stduent2.stu1会访问Stduent1的实例,默认会调用__get__方法,但是__get__方法没有将实例返回,因此,Stduent2.stu1.course会报错
stu2 = Stduent2()
print(stu2.stu1.course) # 一样的报错
# 示例 引入描述器
class Stduent1:
def __init__(self):
self.course = 'Python'
print('Stduent1.__init__')
def __get__(self, instance, owner):
# 这里的self为Stduent1的实例. instance为实例, 如果是类访问,那么instance为None. owner是调用者的类
print('self={} instance={} owner={}'.format(self, instance, owner))
return self # 返回Student1的实例self
class Stduent2:
stu1 = Stduent1()
def __init__(self):
print('Stduent2.__init__')
print(Stduent2.stu1.course)
stu2 = Stduent2()
print(stu2.stu1.course)
- 函数包含一个
__get__()
方法以便在属性访问时绑定方法,这就是说所有的函数都是非数据描述器,它们返回绑定还是非绑定的方法取决于他们是被实例调用还是被类调用
数据描述器
# 示例1:
#非数据描述器
class Student1:
def __init__(self):
self.course = 'Python'
print('Student1.__init__')
def __get__(self, instance, owner):
# 这里的self为Student1的实例. instance为实例, 如果是类访问,那么instance为None. owner是调用者的类
print('self={} instance={} owner={}'.format(self, instance, owner))
return self # 返回Student1的实例self
class Student2:
stu1 = Student1()#类变量
def __init__(self):
print('Student2.__init__')
self.y = Student1() #实例变量 # 没有调用__get__方法
print(Student2.stu1.course)
stu2 = Student2()
print(stu2.y)
# 示例,数据描述器
class Student1:
def __init__(self):
self.course = 'Python'
print('Student1.__init__')
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
return self
def __set__(self, instance, value):
print('self={} instance={} value={}'.format(self, instance, value))
self.course = value
class Student2:
stu1 = Student1() #类属性
def __init__(self):#实例
print('Student2.__init__')
self.y = Student1() #实例属性 # 调用了__get__方法
print(Student2.stu1.course)
stu2 = Student2()
print(stu2.stu1)
(类的函数为实例,里面的变量为实例的变量,不是函数的属性为类的属性,类的属性的变量为类变量)
- 当非数据描述器是实例的变量时,实例访问非数据描述器不会调用
__get__
方法,只是访问了描述器类的实例; - 当数据描述器是实例的变量时,实例访问数据描述器会调用描述器的
__get__
方法;
非数据描述器和数据描述器的访问顺序
- 当存在描述器的时候,一个类实例的查找属性顺序为:先查找类或父类中是否有数据描述器属性,如果有那么,先访问数据描述器,如果没有数据描述器 --> 那么就会查找自己实例的dict属性,如果dict属性里面也没有找到 --> 然后会在类或父类的非数据描述器进行查找
# 示例1:非数据描述器
class Student1:
def __init__(self):
self.course = 'Python'
print('Student1.__init__')
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
return self
class Student2:
stu1 = Student1()
def __init__(self):
print('Student2.__init__')
self.x = 'Student1'
print(Student2.stu1.course)
stu2 = Student2()
print(stu2.stu1)
print(stu2.__dict__) # 实例的__dict__属性中有 {'stu1': 'Student1'}
# 示例:数据描述器
class Student1:
def __init__(self):
self.course = 'Python'
print('XKD1.__init__')
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
return self
def __set__(self, instance, value):
print('self={} instance={} value={}'.format(self, instance, value))
self.course = value
class Student2:
stu1 = Student1()
def __init__(self):
print('Student2.__init__')
self.x = 'Student1'
print(Student2.stu1.course)
stu2 = Student2()
print(stu2.stu1)
print(stu2.__dict__) # 实例的__dict__为空
- 事实上,实例属性的查找顺序并没有改变,依然是实例的dict中的属性优先被访问;
- 只是如果实例有属性是数据描述器的话,属性会被dict字典移除,因此就会访问类的属性,造成了数据描述器优先访问的假象;
哪些案例是描述器实现的
- 属性装饰器@property(),是通过数据描述器实现;
- 类方法装饰器@classmethod和静态方法装饰器@staticmethod都是通过非数据描述器实现;
@staticmethod的实现
class StaticMethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
return self.fn
class Stduent:
@StaticMethod
def show(): # show = StaticMethod(show) , 此时的show方法,已经是StaticMethod类的实例
print('静态方法实现')
Stduent.show()
Stduent().show()
- 静态方法装饰器@staticmethod装饰的方法被装饰成了StaticMethod类的实例,可以看做一个类变量,因此类和类的实例都可以访问这个类变量,那么就会调用__get__方法,返回self.fn,也就是原生的方法对象,最后调用这个方法对象;
@ClassMethod的实现
from functools import partial
class ClassMethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
print('self={} instance={} owner={}'.format(self, instance, owner))
return partial(self.fn, owner)
class Stduent:
@ClassMethod
def show(cls): # show = ClassMethod(show)
print('类方法实现')
Stduent.show()
Stduent().show()
- 类方法的实现与静态方法类似,只是需要调用partial偏函数,将类提前作为参数传递;
属性装饰器property实现原理分析
class Property:
def __init__(self, fget, fset=None):
self.fget = fget
self.fset = fset
def __get__(self, instance, owner):
if instance is not None:
return self.fget(instance)
return self
def __set__(self, instance, value):
if callable(self.fset):
self.fset(instance, value)
else:
raise AttributeError('{} is not callable'.format(self.fset.__name__))
def setter(self, fn):
self.fset = fn
return self
class Stduent:
def __init__(self, data):
self.__data = data
@Property # data = Property(data)
def data(self):
return self.__data
@data.setter # data = data.setter(data)
def data(self, value):
self.__data = value
stu= Stduent('Python')
print(stu.data)
stu.data = 'JAVA'
print(stu.data)
- Python内置的@property装饰器,把一个方法变成可以像属性那样,做取值用;
- @data.setter装饰器,把一个方法变成可以像属性那样,作赋值用;
- @property和data.setter()同时使用,表示可读可写;
十二、继承
继承允许一个类获得另一个类的所有元素(比如属性和方法)。继承提供代码可重用性,使创建和维护应用程序更容易。我们继承的类称为超/基类,继承的类称为派生/子类。
以下是Python支持的不同类型的继承:
- 单一继承 - 一个类只继承于一个类。
- 多级继承 - 一个子类还可以作为另一个类的父类而派生出另一个子类。
- 分层继承 - 从基类继承多个类,即多个子类继承同一个超类。
- 多重继承 - 一个类继承了多个类。
十三、多态性
多态性表示能够采取多种形式。因此如果父类具有名为ABC的方法,则子类也可以具有相同名称ABC的方法,该方法具有其自己的参数和变量。Python允许多态。
十四、try、raise、except、else和finally
try: 尝试抛出异常;
raise: 引发异常;
except: 处理异常;
finally: 是否发生异常都需要做的事情;
try…except形式: 指定一个或多个异常处理器(异常子句)
当在try子句中没有异常发生时,异常处理器将不被执行。
当在try子句中有异常发生时,首先会执行except搜索异常处理器,它会按顺序搜索直到第一个匹配的处理器找到为止.。
如果在except后面找到对应的异常声明,就会处理。
如果没有找到,异常就会被临时保存起来,然后去执行finally语句。
注意:一般我们会将一个没有指定异常的except语句, 它必须放在最后, 它会匹配任何异常并处理。
try…finally形式:指定一个清除处理器。在执行try语句块没有异常发生时, finally子句被执行。在异常引发时, 该异常就被临时保存起来, finally也被执行, 然后暂存的异常被重新引发。
如果执行finally子句时引发了另一个异常或执行了return或break语句, 就会抛弃保存的异常,在finally子句中的continue语句是非法的(这么做的原因是当前实现的原因 ---- 这个限制可能也会保留下去)在执行finally子句时异常信息是无效的。
try…except…else形式:try语句块正常执行没有发生异常,则将执行else语句后的内容。
raise:抛出异常,最好指出异常的具体类型
a = 0
if a == 0:
raise Exception("a must not be zero")
“----”
a = 0
if a == 0:
raise ZeroDivisionError("a must not be zero")
异常类型可查异常列表
简单总结:
1.当执行try…except之间的语句序列没有发生异常时,则忽略异常处理部分(except)的语句,然后有else则执行else。
2.Except括起来的语句,则只有在产生异常的情况下会被执行,其他情况一概不执行的。
3.Finally括起来的语句是铁定会被执行的,无论是否有异常产生。
十五、sort()和sorted()
sort()是list的内置函数,只有list可用。
sort()不会返回一个新对象,sorted()会返回一个新的对象,并且sorted()可对所有可迭代的序列排序生成新的序列。
key是关键字函数,排序的时候根据key函数返回的值进行排序。
reverse:True降序,False升序。
在python3中没有cmp,如果要自定义cmp函数需要使用 from functools import cmp_to_key
from functools import cmp_to_key
def cmp_new(x,y):
if (x+y)>(y+x):
return 1
elif (x+y)<(y+x):
return -1
else :
return 0
n=input()
s=input().split()
s.sort(key=cmp_to_key(cmp_new),reverse=True)
print(''.join(s).lstrip("0"))
#或者如下
s_new = sorted(s,cmp_to_key(cmp_new),reserve=True)
print(''.join(s_new).lstrip("0"))
十六、堆(heapq)
python3中的堆为小根堆,使用大根堆需要对值取负.
使用heapq
模块;heapq.heapify(list)
将列表转换为堆;heapq.heappop(list)
删除最小值;heapq.heappush(list,e)
增加元素;heapq.heapreplace(list,e)
删除最小元素值,添加新的元素值;heapq.heappushpop(list,e)
首先判断添加元素值与堆的第一个元素值对比,如果大,则删除第一个(最小的)元素,然后添加新的元素值,否则不更改堆;heapq.merge(list1,list2)
合并堆;heapq.nlargest(n,list)
查询堆中最大的前n个元素;heapq.nsmallest(n,list)
查询堆中最小的前n个元素
十七、静态函数, 类函数, 成员函数、属性函数的区别
静态函数(@staticmethod) : 即静态方法,主要处理与这个类的逻辑关联,它是不可以访问实例变量或类变量的。
类函数(@classmethod): 即类方法, 只能访问类变量,不能访问实例变量,类方法通过@classmethod装饰器实现。
成员函数 : 实例的方法,只能通过实例进行调用,若需通过类名来调用,则应申明为类方法。
属性函数:通过@property把一个方法变成一个静态属性。
具体应用:
比如日期的方法,可以通过实例化(init)进行数据输出,传入参数self;
可以通过类的方法 (@classmethod) 进行数据转换, 传入参数cls;
可以通过静态方法(@staticmethod)进行数据验证;
十八、推导式
1.列表推导式:
# 列表推导式
list_b = [b for b in range(5)]
print(list_b)
等价于
# for循环
list_a = list()
for a in range(5):
list_a.append(a)
print(list_a)
复杂一点的:
# 带if条件语句的列表推导式
list_d = [d for d in range(6) if d % 2 != 0]
print(list_d)
# 多个for循环
list_e = [(e, f * f) for e in range(3) for f in range(5, 15, 5)]
print(list_e)
# 嵌套列表推导式,多个并列条件
list_g = [[x for x in range(g - 3, g)] for g in range(22) if g % 3 == 0 and g != 0]
print(list_g)
优点:
- 使代码更简洁,并且在相互嵌套的for循环里可以提高代码可读性。
- 在Python3中列表推导式有自己的局部作用域,就像函数似的。表达式内部的变量和赋值只在局部起作用,表达式的上下文里的同名变量还可以被正常引用,局部变量并不会影响到它们。(不会有变量泄漏的问题,在列表推导中的赋值操作不可能会影响到列表推导上下文中的同名变量)
当列表推导式过长时还是改成多层for循环的可读性较高
2.字典推导式
推导式可以应用到字典上
# 因为key是唯一的,所以最后value都是1
dict_a = {key: value for key in 'python' for value in range(2)}
print(dict_a)
# 可以根据键来构造值
dict_b = {key: key * key for key in range(6)}
print(dict_b)
# 遍历一个有键值关系的可迭代对象
list_phone = [('HUAWEI', '华为'), ('MI', '小米'), ('OPPO', 'OPPO'), ('VIVO', 'VIVO')]
dict_c = {key: value for key, value in list_phone}
print(dict_c)
3.集合推导式
同样可以用到集合上
# 遍历一个可迭代对象生成集合
set_a = {value for value in '1234'}
print(set_a)
十九、map、reduce、filter、zip
1.map(): 会根据提供的函数对指定序列做映射,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。有多个可迭代对象时,以最短的为主。
map(function, iterable1, iterable2,…)
function – 处理函数
iterablex – 可迭代对象
iterablex 中同一个索引的元素会一起被输入function里进行运算或操作
list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))
list(map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))
2.reduce: 将一个序列根据处理逻辑从左到右缩成一个值,即对参数元素累积
reduce(function, sequence[, initial])
function:处理逻辑
sequence:待处理序列
initial:初始值,默认None
reduce(f,[x1,x2,x3,x4])=>f(f(f(x1,x2),x3),x4)
reduce(lambda x,y:x+y,[1,2,3,4,5])=(((1+2)+3)+4)+5)=15
3.过滤器(filter)的作用是过滤序列中不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。filter其实和map很相似,但它的可迭代对象只有一个,map可以有多个。
filter(function, iterable)
function – 判断函数
iterable – 可迭代对象
filter_10 = filter(lambda x: x>10, [0, 12, 14, 2, 5, 17])
list(filter_10)
out:
[12, 14, 17]
4.zip: 将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。可以使用 list() 转换来输出列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表,也可以将以List存储的二维矩阵转置。
zip(iterable1,iterable2,…)
iterablex – 可迭代对象
a = [1,2,3]
b=[1,2,3,4]
zip(a,b) #<zip object at 0x7fbed3571440>
list(zipped) # [(1, 1), (2, 2), (3, 3)]
a1, a2 = zip(*zip(a,b)) #只有2个迭代对象的话,解压后为2个list,3个迭代对象解压后为3个list
list(a1) #[1, 2, 3]
list(a2) #[1, 2, 3]
c=[[1,2,3],[3,2,1],[4,5,6]]
list(zip(*c))#[[1,3,4],[2,2,5],[3,1,6]]
二十、*args和**kargs
python中规定参数前带 * 的,称为可变位置参数
*args:将函数传入的参数,存储在元组类型的变量args当中
def f(*args):
print(args)
f(1,2,3) #(1, 2, 3)
def f(x,*args):
print(args)
f(0,1,2,3) #(1, 2, 3)
f(0,[1,2,3]) #([1, 2, 3],)
**kargs: 将函数的参数和值,存储在字典类型的kargs变量中
def f(**kargs):
print(kargs) #{'var1': 'a', 'var2': 2, 'var3': 3}
f(var1='a',var2=2,var3=3)#vari是key也是变量名,定义方法和定义变量一样
def f(x,**kargs):
print(kargs)#{'a': 'a', 'var2': 2, 'var3': 3}
f(1,a='a',var2=2,var3=3)
#由于是转换成字典存储,所以只能上面这样传入,不能像*args可以使用list等传入
def f(x,*args,**kargs):
print(args)#([1, 2, 3],)
print(kargs) #{'a': 'a', 'var2': 2, 'var3': 3}
f(1,[1,2,3],a='a',var2=2,var3=3)
二十一、Local、global、nonlocal、Enclosing和Built-in
1.Local:局部变量包含在def定义的函数体内。在函数体内声明的变量,默认都是局部变量,除非有特别说明,如全局变量的声明要用关键字global
def f():
x = 123 # x即为局部变量
print(x) #x是在f里定义的,没有global,所以在这里会报错
2.global:全局作用域的变量
x = 123
def f():
print(x)
x = 100 #报错
f()
按照python引用变量的顺序,当print(x)时,就会先在函数体内查找x,能找到,但在他的下一行,故为在声明前被引用是不对的。
x = 123
def f():
global x
print(x) #123
x = 100
f()
print(x) #100
此时先进行对x进行全局作用域的声明后就可以print(x)再重新声明
3.nonlocal:这个一般是用在闭包函数里。但是一定要认识到, nonlocal声明的变量在上级局部作用域内,而不是全局定义, 如果在它声明的变量在上级局部中不存在,则会报错。
x = 123
def outer():
x = 100
def inter():
nonlocal x
x = x+1
inter()
print(x)#101
outer()
x = 123
def outer():
x = 100
def inter(k):
nonlocal x
x = x+1
if k==1:
return
inter(1)
inter(0)
print(x) #102
outer()
3.Enclosing:嵌套作用域。变量的访问规则:L(Local) –> E(Enclosing) –> G(Global) –>B(Built-in)
x = 123
def outer():
x = 100
def inter():
print(x) #100
inter()
outer()
4.Built-in:内置作用域。在 Python 中,有一个内建模块,该模块中有一些常用函数;而该模块在 Python 启动后、且没有执行程序员所写的任何代码前,Python会 首先加载该内建函数到内存。另外,该内建模块中的功能可以直接使用,不用在其前添加内建模块前缀,其原因是对函数、变量、类等标识符的查找是按LE(N)GB法 则,其中B即代表内建模块。
二十二、is和==
is 判断左右两端的数据是否是同一个对象
== 判断左右两端的数据是否一样
二十三、自省机制
对某一对象进行检查,知道它是什么能做什么
1.dir()
dir() 返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。
import keyword
print(dir(keyword)) #['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'iskeyword', 'kwlist']
2.type()
type() 确定对象的类型。
print(type([1,2]))#<class 'list'>
3.hasattr()
检索对象是否具有该属性。
print(hasattr(id,'__doc__')) #True
4.getattr()
返回一个对象属性值
class A(object):
bar = 1
a = A()
print(getattr(a, 'bar')) #1
print(getattr(a, 'bar1')) #不存在bar1,报错
5.isinstance()
测试对象,以确定它是否是某个特定类型或定制类的实例
print(isinstance('123',str))#True
print(isinstance('123',int))#False
6.help()
查看函数或模块用途的详细说明
7.doc
查看对象的注释
8. callable()
检查一个对象是否是可调用的。如果返回 True,object 仍然可能调用失败;但如果返回 False,调用对象 object 绝对不会成功。
9.issubclass()
判断参数 class 是否是类型参数 classinfo 的子类。语法issubclass(class, classinfo)
10.id()
返回对象的唯一标识符,标识符是一个整数。is的对比的内容就是id的结果是否一样
CPython 中 id() 函数用于获取对象的内存地址。
二十四、lambda
匿名函数lambda:是指一类无需定义标识符(函数名)的函数或子程序。
lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。
a=lambda x:x*x
print(a) #<function <lambda> at 0x7f7b8e32b1f0>
print(a(3))#9
b=lambda x,y:x+y
print(b(3,4))#7
- lambda 函数不能包含命令
- 包含的表达式不能超过一个
lambda匿名函数的格式:冒号前是参数,可以有多个,用逗号隔开,冒号右边的为表达式。其实lambda返回值是一个函数的地址,也就是函数对象。
二十五、单下划线和双下划线
Python 用下划线作为变量前缀和后缀指定特殊变量。
_xxx
不能用’from module import *'导入
__xxx__
系统定义名字
__xxx
类中的私有变量名
“单下划线” 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量;
“双下划线” 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数
据。
二十六、__new__和__init__的区别
- __new__是一个静态方法,而__init__是一个实例方法.
- __new__方法会返回一个创建的实例,而__init__什么都不返回.
- 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
- 当创建一个新实例时调用__new__,初始化一个实例时用__init__.
二十七、新式类和旧式类
- 新式类是在创建的时候继承内置object对象(或者是从内置类型,如list,dict等),而旧式类是直接声明的。object对象定义了一系列特殊的方法实现所有对象的默认行为,如:new,init。
- 新式类的MRO(method resolution order 基类搜索顺序)算法采用C3算法广度优先搜索,而旧式类的MRO算法是采用深度优先搜索
- 新式类相同父类只执行一次构造函数,旧式类重复执行多次。
class A():
def foo1(self):
print("A")
class B(A):
def foo2(self):
pass
class C(A):
def foo1(self):
print("C")
class D(B, C):
pass
d = D()
d.foo1()#新式类里是C,旧式类里是A
二十八、read, readline和readlines
- read 读取整个文件
- readline 读取下一行,使用生成器方法
- readlines 读取整个文件到一个迭代器以供我们遍历
二十九、with
- 实现了上下文协议的对象就可以使用with语句。对于实现了上下文协议的对象,我们通常称为上下文管理器。
- 上下文协议:实现了__enter__和__exit__就是上下文协议。
with open(r'c:\test.txt', 'r') as f:
data = f.read()
此例当中open函数__enter__返回的文件对象赋值给了f;with会自已获取上下文件的异常信息;最后,出现异常或with结束后就会__exit__。
普通的读取上下文协议需要写close(),但使用with就不需要。提高了code的简洁性。
三十、range和xrange的区别
1.range和xrange都是在循环中使用,输出结果一样。range返回的是一个list对象,而xrange返回的是一个生成器对象(xrange object)。
2.xrange则不会直接生成一个list,而是每次调用返回其中的一个值,内存空间使用极少,因而性能非常好。
python3中取消了range,并把xrange重新命名为range,所以使用不了range了。
三十一、位运算
&
:与运算,两个数的二进制相应位为1则为1否则为0,如110&010=010
|
:或运算,两个数的二进制相应位有一个为1时为1,否则为0,如110|010=110
^
:异或运算,两个数的二进制相应位相异时结果为1,否则为0,如110 ^010=100
~
:取反运算,对数据的每个二进制位取反,即把1变为0,,把0变为1,如 ~110=001
<<
:左移运算,各二进制全部左移若干位,由"<<“右边的数指定移动的位数,高位丢弃,低位补0,简单点就是x<<n=x*(2)^2,如4<<2=16
>>
:右移运算,各二进制全部左移若干位,由”>>"左边的数指定移动的位数,低位丢弃,高位补0,简单点就是x>>n=x÷(2)^2,如4>>2=1
位运算判断奇偶性:将数值和1进行&
,结果为0为偶数,1为奇数。由于1的二进制为0……01,所以任何数和1的&
结果是0……0x;当数值为奇数时,它的二进制数为……1(前面一堆不管,后面必为1),此时和1进行&
,由于最后一位为1,则x=1;要是偶数,那么最后一位就为0(0……00),此时和1进行&
,由于最后一位为0,则x=0。
三十二、any和all
any():可迭代对象中任意存在每一个元素为True则返回True
def any(iterable):
for element in iterable:
if element:
return True
return False
all(): 可迭代对象中所有元素都为True则返回True
def all(iterable):
for element in iterable:
if not element:
return False
return True
三十二、正数与负数的求模、整除运算
整除:5//-2=-3(-5//2=-3),//是一种向下取整的运算,5/-2=-2.5.向下就是-3
求模:7%(-3)=-2(-7%3=2),当一正一付时,x%y=x-x//yy (-7-(-33)=2, 7-(-3*3)=-2)