欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

学习python时收集的一些tips(持续更新)

程序员文章站 2022-03-04 23:50:04
...

@(Python学习笔记)
#python学习笔记

@author:silent_sunshine

  • 使用sort()方法对列表进行永久性排序
    • 向方法传递参数reverse=True进行倒序
  • 使用sorted()方法对列表进行临时性排序
  • 使用reverse()方法对列表进行顺序反转
    • 注:不是按字母,而是按原顺序
  • 使用len()方法计算列表长度
  • 使用强制转换时,记得加上try except异常处理
  • 如果你只读取文件,就不要请求写入权限,可以避免bug的发生
  • json.dumps(struct)将 python基本数据结构—>字符串
  • json.loads(string)将字符串—>python基本数据结构
  • python 不支持switch
  • 字典只有‘键‘可以迭代,如要迭代’值‘或’键值对‘需使用d.values()d.items()
  • enumerate(iterator)遍历迭代器,返回索引和元素
  • expression1 if condition else expression2 for item in items 等价于:
for item in items:
    if condition:
        expression1
    else:
        expression2

没有else时:expression for item in items if condition等价于:

for item in items:
    if condition:
        expression

双重嵌套式: expression for x in xx for y in yy if condition等价于:

for x in xx:
    for y in yy:
        if condition:
            expression

注释、逗号后加个空格更美观
字符串格式化,替换%s %d内的内容,替换{}内的内容
'str %s %d' % (tupple)
'str {} {}'.format(a,b)

  • urllib.request.build_opener()的作用:
    • 1、build_opener 的作用
      要爬取的各种各样的网页,它们有一部填写需要验证码,有的需要cookie,还有更多许多高级的功能,它们会阻碍你爬,而我对于openurl单纯地理解就是打开网页。openurl打开一个网址,它可以是一个字符串或者是一个request对象。而build_opener就是多了handler,处理问题更专业,更个性化。
  • 存在运算符时两侧都要空一格
  • \r为将光标移到本行开头
  • unicode编码可以通过str.encode('unicode-escape').decode('unicode-escape')转换为中文
  • 从文件读取或写入二进制数据时,总应该以'rb''wb'形式打开文件
  • 文件的三种方法:
    • 使用urlretrieve()
urllib.request.urlretrieve(url=, filename=, reporthook=functionname)

url下载地址,filename下载后保存成的文件,reporthook函数调用过程中的回调函数(给回调函数传入三个值分别是recieve_block,block_size,total_size)

  • 直接写二进制文件
data = urllib2.urlopen(url).read()
\#data = requests.get(url).content
with open('filename','wb') as f:
	f.write(data)
  • *kwag 表示传入任意数量的参数,**kwarg 表示传入键值对的字典参数,*list**dict都为解包操作,以上*的用法皆为解包操作,可以使用_来丢弃没用的数据,_, interest = *(1, 2) 如:
def function(a,**kwarg):
    pass
dict = {'name':zhouqichun,'age':20,'uid':123}
function(a,dict)
#function(a,name=zhouqichun,age=20,uid=123)
  • __slots__魔法限定类的属性
  • 对于受保护的属性可以通过getter(访问器)和 setter(修改器)进行相应的访问和修改(@property包装器)
  • 对象可以动态的添加属性
  • 通过@staticmethon静态方法,再不创建对象实例的情况下,也可以调用类方法,和静态方法比较类似,Python还可以在类中定义类方法,类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,如:

class Clock(object):
    # 数字时钟   
    def __init__(self, hour=0, minute=0, second=0):
        self._hour = hour
        self._minute = minute
        self._second = second
    @classmethod
    def now(cls):
        ctime = localtime(time())
        return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)
def main():
    # 通过类方法创建对象并获取系统时间
    clock = Clock.now()
    while True:
        print(clock.show())
        sleep(1)
        clock.run()

if __name__ == '__main__':
    main()
  • 类的三种关系is-a, has-a, use-a, 如老王is-a people, 他 has-a car, 他 use-a 钥匙开车
  • 可以用abc模块的ABCMeta, abstractmethon对类进行抽象,抽象类不能创建实例(即对象),抽象类是一种特殊的类,它生下来就是作为父类存在的,一旦对象化就会报错。同样,抽象函数定义在抽象类之中,子类必须重写该函数才能使用。相应的抽象函数,则是使用装饰器@abstractmethod 来表示。
  • 正则表达式
    • 字符集[]匹配与匹配结果相同
    • 要匹配特殊字符*+.\等要加转义字符\
    • /D匹配所有非十进制数的字符,相当于[^0-9]
    • ?前面匹配至多一个
    • re.compile(patten)预编译匹配格式
    • (?P<name>)(?P=name)使用名称来标识匹配结果,返回结果即字典,name为键名,()圆括号匹配可以进行分组然后返回元组(子组),可以调用group()法使用匹配结果
    • {n}呢个连续的正则表达式的副本,如:\d{3}匹配三个数字
      非贪婪操作符,是正则表达式匹配尽可能少的字符

创建服务器

创建套接字对象:socket(socket.family, sockey.type, protocol=0)
TCP/IP(传输控制协议):tcp = socket(AF_INET, SOCK_STREAM) UDP/IP(用户数据报协议):udp = socket(AF_INET, SOCK_DGRAM)
套接字绑定地址:socket.bind(host, port)
套接字监听:socket.listen()
进入无限循环:接受客户端请求socket.accept(),接受数据client.recv(data_size),发送数据client.sent(data)

创建客户端

可以通过time.timeit()来测试某项动作执行的时间

因特网编程:FTP,SMTP,NNTP

FTP有两种传输模式:主动和被动
一般客户端的工作流程:1.连接服务器 2.登录服务器 3.发送操作指令 4.退出

  • SMTP通信时,只有一个25端口号
  • 通过platform.python_verson()可以检查python的版本
    私有信息要存放在编译过的字节码文件.pyc中

多线程编程

喜欢网易云音乐,喜欢里面的每日推荐,因为每次打开每日推荐都能发现惊喜,总能找到一两首能让我添加到我喜欢里面,不会特意去点开排行榜,然后点击最靠前的几首歌,也不会去点击一些最热歌单,吸引我的仅仅是每日推荐和每首歌评论背后的喜怒哀乐百态人生。

进程是执行中的程序,线程是在统一进程下执行的共享相同的上下文
多线程编程过程:

  • 1.设置GIL

  • 2.进入线程一个线程

  • 3.执行一下操作:

    • a.执行指定数量的字节码
    • b.主动让出控制权
  • 4.将线程设置为睡眠状态(切出线程

  • 5.解除锁

  • 6.重复上述步骤

  • 进程中的包线程包括主线程和子线程,没有限制的话,一般不会互相影响.

  • 使用threading.Thread()实例的三种方法:
    1.创建Thread()实例,传入方法
    2.创建Thread()实例,传入类方法
    3.创建Thread()子类,重载run()方法
    上下文管理器——with语句:with语块内,会在语块结束时,自动调用关闭函数,可以参考打开文件的操作

  • 使用2to3工具能将python2的代码移植到python3 如:2to3 -w text.py

  • atexit模块和register()函数
    atexit在程序即将结束前执行的代码,register()函数用来注册程序退出时的回调函数,然后在回调函数中做一些资源清理的操作(也可以使用@register装饰器来注册回调函数)

    • 注意:
      1,如果程序是非正常crash,或通过os._exit()退出,注册的回调函数将不会被调用。

      2,也可以通过sys.exitfunc来注册回调,但通过它只能注册一个回调,而且还不支持参数。

      3,建议使用atexit来注册回调函数。

    • 不过请特别注意:
      1,不要在程序中同时使用这两种方式,否则通过atexit注册的回调可能不会被正常调用。通过查阅atexit的源码,原来它内部是通过sys.exitfunc来实现的,它先把注册的回调函数放到一个列表中,当程序退出时,按先进后出的顺序调用注册的回调。如果回调函数在执行过程中抛出了异常,atexit会打印异常的文字信息,并继续执行下一下回调,直到所有的回调都执行完毕,它会重新抛出最后接收到的异常。

      2,如果使用的Python版本是2.6及以后的版本,还可以用装饰器的语法来注册回调函数。

      3,如果注册顺序是A,B,C,那么最后调用的顺序是相反的,C,B,A

  • GUI编程:
    1.创建顶层窗口
    2.创建组件
    3.连接应用代码
    4.进入主循环

数据库编程:

  • lambda表达式,可以降低代码复杂性,提高可读性:lambda 参数:表达式,如:
result = lambda x, y : x+y
result(3, 8)
print(result)# 输出8

web编程:CGI, WSGI

  • zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zipped = zip(a,b)     # 打包为元组的列表
[(1, 4), (2, 5), (3, 6)]
zip(a,c)              # 元素个数与最短的列表一致
[(1, 4), (2, 5), (3, 6)]
zip(*zipped)          # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
[(1, 2, 3), (4, 5, 6)]
  • 如果不能确定代码是否能够正确运行,往往要加入异常处理,对于flow-control(流程控制)一般不做异常处理,防止滥用异常处理
  • 我们不能随意修改全部变量的值,如果想要在函数内部修改全局变量的值需在里面声明global
  • 对于嵌套函数来说,要访问外部函数定义的变量时,需加上nonlocal
  • 闭包其实和嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。
  • 对于嵌套函数,内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问,嵌套函数的使用,能保证数据的隐私性,提高程序运行效率
  • 所谓函数式编程,是指代码中每一块都是不可变的(immutable),都由纯函数(pure function)的形式组成。这里的纯函数,是指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出,没有任何副作用。python中提供的map(),filter(),reduce()结合匿名函数可以更好的实现函数式编程
  • map(function, iterable) 函数,它表示,对 iterable 中的每个元素,都运用 function 这个函数,最后返回一个新的可遍历的集合。
  • filter(function, iterable) 函数,它和 map 函数类似,function 同样表示一个函数对象。filter() 函数表示对 iterable 中的每个元素,都使用 function 判断,并返回True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。
  • reduce(function, iterable) 函数,它通常用来对一个集合做一些累积操作。function 同样是一个函数对象,规定它有两个参数,表示对 iterable中的每个元素以及上一次调用后的结果,运用 function 进行计算,所以最后返回的是一个单独的数值。
  • 对于棱形继承的类,可以通过super()来解决冲突,内部机理为C3算法解决mro
  • 在大型工程中尽可能使用绝对位置是第一要义。对于一个独立的项目,所有的模块的追寻方式,最好从项目的根目录开始追溯,这叫做相对的绝对路径。
  • 对于每一个项目来说,最好要有一个独立的运行环境(virtualenv)来保持包和模块的纯净性
  • 容器是可迭代对象,可迭代对象调用 iter() 函数,可以得到一个迭代器。其中的方法next(iterator, default)相当于指针,迭代器遍历是一次性的,超过之后如有设置默认值则返回默认值,如没有提供默认值将引发StopIteration Error。生成器在python中是用小括号括起来,迭代器相当于一个有限集,而生成器则是一个无限集,生成器相当于懒人版的迭代器,生成器只有被使用时才会被调用,才会占用内存,因此生成器节约了大量的内存。
  • yield x 当程序运行到yield时会暂停,然后跳出,跳出过程中x将成为next()的返回值,返回一个生成器对象,每次next(gen) 函数被调用的时候,暂停的程序就又复活了,从 yield 这里向下继续执行;同时注意,局部变量i 并没有被清除掉,而是会继续累加。
  • C10K问题:同时连接到服务器的客户数量达到了一万个进程上下文切换会占用大量的资源
  • NGINX在高并发情况下,能实现保持低资源低消耗高性能
  • 同步(sync)和异步(async)
  • 并发编程:
    • 协程(异步):Asyncio(切换任务)
      • async修饰词声明异步函数,调用异步函数可以得到一个协程对象
        协程执行的方法:
        1.通过await()来调用,await是同步调用
        2.通过asyncio.create_task()来调用
        3.通过asyncio.run()来调用(python3.7之后才支持)
    • 多线程:(切换线程)
      Futures
      Threadings
  • 如果是 I/O bound,并且 I/O 操作很慢,需要很多任务 / 线程协同实现,那么使用Asyncio 更合适。
  • 如果是 I/O bound,但是 I/O 操作很快,只需要有限数量的任务 / 线程,那么使用多线程就可以了。
  • 如果是 CPU bound,则需要使用多进程来提高程序运行效率。
  • 如果 I/O 操作非常多、非常 heavy,需要建立的连接也比较多时,我们一般会选择 Asyncio。因为 Asyncio 的任务切换更加轻量化,并且它能启动的任务数也远比多线程启动的线程数要多。当然,如果 I/O 的操作不是那么的heavy,那么使用多线程也就足够了。
    • 多进程:
      • 协程和多线程的区别,主要在于两点,一是协程为单线程;二是协程由用户决定,在哪些地方交出控制权,切换到下一个任务。
      • 阻塞(block):阻塞主要是同步编程中的概念:执行一个系统调用,如果暂时没有返回结果,这个调用就不会返回,那这个系统调用后面的应用代码也不会执行,整个应用被“阻塞”了。
    • 并发(concurrency):发并不是指同一时刻有多个操作(thread、task)同时进行。相反,某个特定的时刻,它只允许有一个操作发生,只不过线程 / 任务之间会互相切换,直到完成。
    • 并行(parallelism):至于所谓的并行,指的才是同一时刻、同时发生。例如,在多核处理器中可以在同一时刻同时运行多进程,或则利用多台主机运行进程,提高运行速度
  • 并发通常应用于 I/O 操作频繁的场景,比如你要从网站上下载多个文件,I/O 操作的时间可能会比 CPU 运行处理的时间长得多,时间花费I/O block上,当出现I/O block时,会调用其他线程。而并行则更多应用于 CPU heavy 的场景,比如 MapReduce 中的并行计算,为了加快运行速度,一般会用多台机器、多个处理器来完成
  • race condition(竞争条件):由于两个或者多个进程竞争使用不能被同时访问的资源,使得这些进程有可能因为时间上推进的先后原因而出现问题,这叫做竞争条件(Race Condition)。
    虽然线程的数量可以自己定义,但是线程数并不是越多越好,因为线程的创建、维护和删除也会有一定的开销。花费的时间主要在线程的切换上。
    ##python的垃圾回收机制:
  • 当引用计数为0时,说明对象不可达,将被垃圾回收
  • 可通过del 和gc.collect()手动回收内存,对于循环引用可通过显示调用gc.collect()回收垃圾
  • 调试内存泄漏,可通过objgraph包调用show_refs()show_blackrefs()查看对象引用关系,查看是否有循环引用
    GIL(全局解释锁):一是设计者为了规避类似于内存管理这样的复杂的竞争风险问题(race condition); 二是因为 CPython 大量使用 C 语言库,但大部分 C 语言库都不是原生线程安全的(线程安全会降低性能和增加复杂度)。CPython 中还有另一个机制,叫做check_interval,意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况。每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。复制代码计,主要是为了方便 CPython 解释器层面的编写者,而不是 Python 应用层面的程序员。
  • 装饰器/修饰器:
  • 使用装饰器的好处:代码更加简洁; 逻辑更加清晰; 程序的层次化、分离化更加明显。

编程规范:

统一的编程规范能提高开发效率。而开发效率,关乎三类对象,也就是阅读者、编程者和机器。他们的优先级是阅读者的体验 >> 编程者的体验 >> 机器的体验。

  • 当你和 None 比较时候永远使用is
  • keys() 方法会在遍历前生成一个临时的列表,导致上面的代码消耗大量内存并且运行缓慢。正确的方式,是使用默认的iterator。默认的 iterator 不会分配新内存,也就不会造成上面的性能问题:
  • 每行最大长度请限制在 79 个字符。
  • 冒号经常被用来初始化字典,冒号后面也要跟一个空格。
  • 全局的类和函数的上方需要空两个空行,而类的函数之间需要空一个空行。
  • 一行中代码长度过长:
    • 第一种,通过括号来将过长的运算进行封装,此时虽然跨行,但是仍处于一个逻辑引用之下。solve1 函数的参数过多,直接换行,不过请注意,要考虑第二行参数和第一行第一个参数对齐,这样可以让函数变得非常美观的同时,更易于阅读。当然,函数调用也可以使用类似的方式,只需要用一对括号将其包裹起来。
    • 第二种,则是通过换行符来实现。这个方法更为直接,你可以从 solve2 和第二个函数调用看出来。
  • assert语句:;用于测试一个条件是否满足,一定记住,不要在使用
  • assert 时加入括号,另外-O选项能让assert失效,对于程序中的一些run-time error,请记得使用异常处理。
  • assert expression1 相当于
    if __debug__:
        if not expression1:
            raise AssertError
    assert expression1, expression2 相当于
    if __debug__:
        if not expression:
            raise AssertError(expression)

##上下文管理器

  • 目的:对文件进行输入输出、数据库连接断开等资源管理操作后,要及时将资源释放,防止出现资源泄漏,轻则系统运行缓慢,重则系统崩溃
  • 基于类的上下文管理器:
#事例
class FileManager:
	def __init__(self, name, mode):
		print('calling __init__ method')
		self.name = name
		self.mode = mode
		self.file = None
	def __enter__(self):
		print('calling __enter__ method')
		self.file = open(self.name, self.mode)
		return self.file
	def __exit__(self, exc_type, exc_val, exc_tb):
		print('calling __exit__ method')
		if self.file:
			self.file.close()
	
with FileManager('test.txt', 'w') as f:
	print('ready to write to file')
	f.write('hello world')

## 输出
#calling __init__ method
#calling __enter__ method
#ready to write to file calling #__exit__ method

需要注意的是,当我们用类来创建上下文管理器时,必须保证这个类包括方法__enter__()和方法__exit__()。其中,方法__enter__()”返回需要被管理的资源,方法__exit__()里通常会存在一些释放、清理资源的操作,比如这个例子中的关闭文件等等。
而当我们用 with 语句,执行这个上下文管理器时:

  • 下面这四步操作会依次发生:
  1. 方法__init__()被调用,程序初始化对象 FileManager,使得文件名(name) 是"test.txt",文件模式 (mode) 是’w’;
  2. 方法__enter__()被调用,文件“test.txt”以写入的模式被打开,并且返回FileManager 对象赋予变量 f;
  3. 字符串“hello world”被写入文件“test.txt”
  4. 方法__exit__()被调用,负责关闭之前打开的文件流

值得一提的是,方法__exit__()中的参数exc_type, exc_val, exc_tb,分别表示 exception_typeexception_valuetraceback。当我们执行含有上下文管理器的 with 语句时,如果有异常抛出,异常的信息就会包含在这三个变量中,传入方法__exit__()。处理完异常后,也别忘了加上return True这条语句,否则仍然会抛出异常。

  • 基于生成器的资源管理器:
from contextmanager import contextmanager

@contextmanager
def file_manager(file, mode):
	try:
		f = open(file, mode)
		yield f
	finally:
		f.close
with file_manager('test.txt', 'w') as f:
	f.write('hello world')

使用基于生成器的上下文管理器时,我们不再用定义__enter__()__exit__()方法,但请务必加上装饰器 @contextmanager,这一点新手很容易疏忽。
讲完这两种不同原理的上下文管理器后,还需要强调的是,基于类的上下文管理器和基于生成器的上下文管理器,这两者在功能上是一致的。基于类的上下文管理器更加 flexible,适用于大型的系统开发; 而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。

##单元测试

  • 重要性:完成产品的功能需求只是很基础的一部分,如何保证所写代码的稳定、高效、无误,才是我们工作的关键,单元测试能够提高代码的质量,减少bug的发生,有利于系统维护。

  • 缺点:繁琐,增加了工作量

  • 单元测试的工具:

    • 基础:unittestDoctest
    • Tools:pytestHypothesistoxUnittest2mock
      ###单元测试的三种技巧
      Python 单元测试的几个技巧,分别是 mockside_effectpatch。这三者用法不一样,但都是一个核心思想,即用虚假的实现,来替换掉被测试函数的一些依赖项,让我们能把更多的精力放在需要被测试的功能上。
      mock 的意思,便是通过一个虚假对象,来代替被测试函数或模块需要的对象。
  • 可以通过pdb包对python代码进行调试,通过cProfile包对python代码进行性能分析

##Context Manager
说到python的上下文管理器,最常使用的便是open()函数,上下文管理器能够减轻开发者的负担,不用花太多精力去注意文件的关闭等等

with open(filename) as f:
	data = f.readlines()

当然我们也可以自己实现上下文管理器,方法有两种:

  • 类实现:
class CustomerOpen():
	def __init__(self, filename):
		self.file = open(filename)
	
	def __enter__(self):
		return self.file
	
	def __exit__(self):
		self.file.close()

with CustomerOpen(filename) as f:
	data = f.readlines()
  • 函数实现:
from contextlib import contextmanager

@contextmanager
def openfile(filename):
	try:
		f = open(filename)
		yield f
	finally:
		f.close()

with openfile(filename) as f:
	data = f.readlines()

tips:这两者分别何时该使用呢?类实现主要是需要进行逻辑封装时更好,而函数实现主要是进行一些简单操作时更好

  • 在列表中和集合中搜索一个元素的过程不同,如:
s = set(['s', 'p', 'a', 'm']) 
l = ['s', 'p', 'a', 'm'] 
def lookup_set(s): 
	return 's' in s 
def lookup_list(l): 
	return 's' in l

虽然两个的运行结果相同,但当列表很长时,因为列表查找元素是一个一个的比对,该过程将会很耗费时间;而集合则不同,在元素很多时,依然能保持很快的速度,因为集合的实现为哈希表,它能告诉编译器到哪去比对,字典同集合一样也是通过哈希表实现的,所以它也比列表更快。当然,集合和字典相较列表更耗费空间。

  • python中的字符串是不变类,即不可更改,一般改变的是创建新的字符串对象
  • 可以通过pycodestyle工具在命令行检查你的代码是否符合PEP8,如:
$ pip install pycodestyle
$ pycodestyle test.py

当然你也可以使用autopep8工具在命令行自动对你的代码进行调整,如:

$ pip install autopep8
$ autopep8 ——in—place test.py 
# ——in—place标志,程序将会直接在控制端输出修改后的代码让你检查
# --aggressive标志将进行更实质性的改变,可以多次使用以获得更大的效果。
  • 要判断一个对象是否是None,最好使用is,如:if a is None
  • 判断字典中是否有某一元素,不要使用dict.has_key(key),最好用if key in dict:,或者使用dict.get(key, 'default'),如:
d = {'hello':1, 'world':2}
print(d.get('hello', 'default')) # print 1
print(d.gett('wtf', 'default')) # print default
  • python 3.0后,filter(function, iterable)返回iterator而不是list
  • 将一个列表赋值给另一个对象时,这两个对象都指向同一个列表,如:
a = [1, 2, 4]
b = a # a和b指向同一个列表,修改其中一个另一个也受影响,副作用
  • 使用\对长代码进行续行时,当有空格出现在\之后时可能会是代码出现错误,折中的方法可以通过括号,当然如果单行代码太长,这可能是同时做太多事的标志,考虑重构代码

包管理

项目的一般文件结构

相关标签: 笔记