请带上游标卡尺
1.基础知识
1.1.关键字
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
1.2.类
内置方法
__delattr__: 删除属性
__dir__: 返回一个包含有所有属性名和方法名的有序列表
__eq__: 用来判断两个对象实例是否相等
__format__: 格式化实例时使用
__getattribute__: 属性访问拦截器,就是当这个类的属性被实例访问时,会自动调用类的__getattribute__方法
__ge__: 对象比较大小使用,相当于>=
__gt__: 对象比较大小使用,相当于>
__hash__: 生成唯一id,唯一标记对象
__init_subclass__: 当子类定义之后触发,
第一个参数是 cls 而不是常见的 self ,这是因为这个方法隐式地被 @classmethod 装饰(PEP-487)。
__init__ : 构造函数,在生成对象时调用
__le__: 对象比较大小使用,相当于<=
__lt__: 对象比较大小使用,相当于>=
@staticmethod __new__: 创建类实例的静态方法,优先 __init__() 初始化方法被调用
__ne__: 对象比较大小使用,相当于!=
__reduce_ex__: 用于pickle,可以指定版本
__reduce__: 用于pickle,不能指定版本
__repr__ : 打印,转换
__setattr__: 在对一个属性设置值的时候,会调用到这个函数
__sizeof__:
__str__: str(self)
@classmethod __subclasshook__: 接收两个类,判断一个类是不是另一个类的子类
__class__: 类的实例当成函数调用
__dict__: 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性
__doc__: 注释内容
__module__: 对象在哪个模块
私有属性
类私有属性在类外面无法直接获取,举例
class A:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
obj = A(1)
obj.__value = 2
print(obj.value)
print(obj.__value)
print(obj._A__value)
输出:
1
2
1
多继承
例子
class A:
def who(self):
print('A', end='')
class B(A):
def who(self):
super(B, self).who()
print('B', end='')
class C(A):
def who(self):
super(C, self).who()
print('C', end='')
class D(B, C):
def who(self):
super(D, self).who()
print('D', end='')
item = D()
item.who()
输出:ABCD
解释:
- Python 3中的类以及Python 2中的新式类使用C3算法来确定MRO,它是一种类似于广度优先搜索的方法;Python 2中的旧式类(经典类)使用深度优先搜索来确定MRO。在搞不清楚MRO的情况下,可以使用类的
mro
方法或__mro__
属性来获得类的MRO列表。 super()
函数的使用。在使用super
函数时,可以通过super(类型, 对象)
来指定对哪个对象以哪个类为起点向上搜索父类方法。所以上面B
类代码中的super(B, self).who()
表示以B类为起点,向上搜索self
(D类对象)的who
方法,所以会找到C
类中的who
方法,因为D
类对象的MRO列表是D --> B --> C --> A --> object
。
由上可以看出,多继承时MRO顺序不好判断,容易出错,特别是使用第三方库时(还得看源码),所以自己设计时一方面是尽量避免使用多继承,如果没办法一定要使用多继承,在重写父类方法时需要特别注意MRO顺序。
函数属性
__annotations__: 参数和返回值注解
__closure__: 函数闭包,自由变量的绑定(通常为None)
__code__: 编译成字节码的函数元数据和函数定义体
__defaults__: 形式参数的默认值
__get__: 实现只读描述符协议
__global__: 函数所在模块中的全局变量
__kwdefaults__: 仅限关键字形式参数的默认值
__name__: 函数名称
__qualname__: 函数限定名称
可附加的一些 方法
1.3.字符串
Python正则替换字符串函数re.sub
命令:re.sub(pattern, repl, string, count=0, flags=0)
re.sub 用于替换字符串的匹配项。如果没有匹配到规则,则原字符串不变。
第一个参数:规则
第二个参数:替换后的字符串
第三个参数:字符串
第四个参数:替换个数。默认为0,表示每个匹配项都替换
1.4.list
list做减法操作
问题描述:假设我有这样两个list,
一个是list1,list1 = [1, 2, 3, 4, 5]
一个是list2,list2 = [1, 4, 5]
我们如何得到一个新的list,list3,
list3中包括所有不在list2中出现的list1中的元素。
即:list3 = list1 - list2
解决方案:我们可以用set(集合)操作
list3 = list(set(list1) - set(list2))
set操作会将一个list转换成一个集合。
假设:list_t = [1, 2, 3, 1]
那么:list(set(list_t)) = [1, 2, 3]
是的,重复的项会被删除。
其他方案:list3 = [i for i in list1 if i not in list2] #可用于list分片
这样写也很直观。
但是在list很大的时候,没有set方法快。
额外知识:a = [[i,j] for i in range(2) for j in range(3)]
这个和“其他方案”非常相似,
都是在[]里面进行判断和计算。
感觉很方便,至少比下面这样要方便、要快:
a = []
for i in range(2):
for j in range(3):
a.append([i,j])
某些问题:如果list_tmp = [[1,2],[2,3]]
即list里面不是单独的元素组成,而是list,
那么set(list_tmp)会出问题。
1.5.import
from xx import *
形式容易造成命名冲突,建议慎用!
主模块的名称在运行时会被Python强制命名为__main__
,相对导入方式可以解决导入当前目录、上级目录、上上级目录下模块的需求,但使用相对导入的文件不能直接以脚本方式运行,只能以模块方式(-m选项)运行,并且当前路径要处于import语句定位的路径或更高层路径。
pip freeze > requirements.txt生成该文件
ip install -r requirements.txt安装该文件所有库
循环导入
目前有2个python文件a.py,b.py,一个配置文件d.yaml
- a文件需要导入d文件配置,b文件依赖a文件的变量a1,负责重写d文件
- a文件中有个逻辑中需要重写d文件,所以也导入了b文件,调用了b文件中的重写逻辑
- 这样就导致了a-b-a的循环导入
解决方案:
- 新建c.py文件
- a文件中变量a1移入c文件
- 至此b文件不依赖a文件,改为了依赖c文件,打破了循环依赖
总结:
循环依赖在复杂项目中容易出现,解决方案都是打破循环,重点是需要理清逻辑,将循环依赖部分隔离出去
1.6.异常
使用异常时如果没有引发异常是很快的,但是如果会时常引发异常,建议使用if-else,因为捕获异常是很昂贵的。
1.7.循环
迭代器每次取出n个元素
比如每次取出两个元素
nums = [1, 2, 3, 4, 5]
num_iter = iter(nums)
for o, e in zip_longest(*([num_iter] * 2), fillvalue=0):
print(o, e)
这里有一个hack的写法,就是[num_iter] * 2
- 利用了iter的性质,每次是取迭代器下一个元素
- *([num_iter] * 2)进行了解包,也就是解成了两个参数,注意这两个参数其实是同一个对象num_iter
- zip_longest将这两个参数每次一个参数取一个元素,也就是num_iter每次取两个元素
1.8.pip
离线安装包
-
下载
pip download -d 目录 模块名(-r requestments.txt)
-
安装
pip install --no-index --find-links=d:\packages 模块名 (-r requirements.txt)
安装指定平台指定python版本的包
pip download -d . --only-binary=:all: --platform manylinux1_x86_64 --python-version 36 sklearn==0.0
1.9.函数
函数参数谨慎使用列表字典对象,举例
def extend_list(val, items=[]):
items.append(val)
return items
list1 = extend_list(10)
list2 = extend_list(123, [])
list3 = extend_list('a')
print(list1)
print(list2)
print(list3)
输出:
[10, 'a']
[123]
[10, 'a']
Python函数在定义的时候,默认参数items
的值就被计算出来了,即[]
。因为默认参数items
引用了对象[]
,每次调用该函数,如果对items
引用的列表进行了操作,下次调用时,默认参数还是引用之前的那个列表而不是重新赋值为[]
2.附加知识
2.1.反编译
安装反编译库,命令如下
pip install uncompyle
uncompyle6 –help 查看帮助 uncompyle6 models.pyc > models.py 将models.pyc反编译成py文件 uncompile -o . *.pyc 将当前文件夹中所有的pyc文件反编译成后缀名为.pyc_dis的源文件
2.2.缓存
介绍functools.lru_cache使用,我们先看下区别,测试代码如下
import timeit
from functools import lru_cache
@lru_cache()
def f1(n):
return 0 if n == 0 else(1 if n == 1 else f1(n - 1) + f1(n - 2))
def f2(n):
return 0 if n == 0 else(1 if n == 1 else f2(n - 1) + f2(n - 2))
print(timeit.timeit('f2(40)', globals=globals(), number=1))
f2(40)耗时60.84906945534581s,f1(40)耗时6.381289836242231e-05s,区别巨大。
@lru_cache()使能够“被冻结”的函数缓存,能够“被冻结”的指的是:在函数参数不变的情况下,函数返回值不变,从而大大提升了效率。
2.3.元类
元类是用来创建类的
2.4.functools
partial:用来”冻结”一个函数的参数,并返回”冻结”参数后的新函数,比如timeit.timeit需要使用类方法时,需要用partial:
class Solution(object):
def f(self, x: int, y: int) -> int:
x = x ^ y
timeit.timeit(partial(s.f, x, y))
2.5.itertools
itertools.accumulate
(iterable[, func])
创建一个迭代器,返回累加和或其他二元函数的累加结果(通过可选参数 func 指定)。如果提供了 func ,它应是2个参数的函数。输入 iterable 元素类型应是 func 能支持的任意类型。如果可迭代对象的输入为空,输出也为空。
大致相当于:
def accumulate(iterable, func=operator.add):
'Return running totals'
# accumulate([1,2,3,4,5]) --> 1 3 6 10 15
# accumulate([1,2,3,4,5], operator.mul) --> 1 2 6 24 120
it = iter(iterable)
try:
total = next(it)
except StopIteration:
return
yield total
for element in it:
total = func(total, element)
yield total
func 参数有几种用法。它可以被设为 min()
最终得到一个最小值,或者设为 max()
最终得到一个最大值,或设为 operator.mul()
最终得到一个乘积。
>>> data = [3, 4, 6, 2, 1, 9, 0, 7, 5, 8]
>>> list(accumulate(data, operator.mul)) # running product
[3, 12, 72, 144, 144, 1296, 0, 0, 0, 0]
>>> list(accumulate(data, max)) # running maximum
[3, 4, 6, 6, 6, 9, 9, 9, 9, 9]
# Amortize a 5% loan of 1000 with 4 annual payments of 90
>>> cashflows = [1000, -90, -90, -90, -90]
>>> list(accumulate(cashflows, lambda bal, pmt: bal*1.05 + pmt))
[1000, 960.0, 918.0, 873.9000000000001, 827.5950000000001]
2.6.调用linux系统命令
python调用linux命令方式有好几种,比如os.system和subprocess的一些函数等,这些实际上都是对c语言调用linux系统命令的包装而已,有时会遇到使用python调用linux命令和自己在shell中调用出现不一致的情况,下面举个例子来说明可能有哪些区别。
事情的起因是这样的,近日接到一个需求要将md转doc,于是用到了pandoc这个库,首先我本地安装了pandoc,使用体验不错,顺便学习了md的标准法。
本地使用pycharm,在python中使用pypandoc来进行文件转换,结果发现内存暴涨的问题,后面看了pypandoc代码,发现是使用subprocess.Popen调用linux下pandoc相应命令来运行,所以理论上应该和再shell中调用pandoc命令效果一致才对。
接下来查看进程详情才发现python使用的是/bin/sh,而我自己用的是/bin/bash,于是将/bin/sh的软链接改成了/bin/bash,但是问题仍未解决,最后抱着试一试的态度在命令行中运行了python脚本,结果发现竟然问题解决了。
后面本地将/bin/sh软链接恢复后发现问题解决,最终确定问题根源只有pycharm。
总结:虽然上面的例子中的问题根源不是由python调用linux命令引起,但是在定位问题的过程中能发现python调用linux命令是会指定某个shell来运行命令的,这可能会导致一些问题,需要我们额外注意。
2.7.hook
目的是为了预留功能,比如在一件事情做之前,可以选择做事情A(当然也可以不做),那么可以实现这个一个事情A,这就是一个hook,举例:
class Watermelon:
def __init__(self):
self.say_it_hook = None
def register_say_it_hook(self, say_it_hook):
self.say_it_hook = say_it_hook
def say_it(self, content):
if self.say_it_hook is not None:
self.say_it_hook(content)
print('so big')
watermelon = Watermelon()
watermelon.register_say_it_hook(lambda x: print(x))
watermelon.say_it('have fun')
3.常用场景
3.1 获取可迭代对象中满足条件的第一个元素
next((_ for _ in range(100) if _ %2 == 0), 1))