python深入

整个四月不发博文的话也太咸鱼了,刚好最近在写一个简单的qq机器人,记一点python的语法糖和开发过程中的收获

经常忘的语法

print(b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore'))


#两种格式化输出:
#1
print('%2d-%02d' % (3, 1))
print('%.2f' % 3.1415926)
print('%x' % 16)
print('%s%s' % ('emmmm',' u are right'))
#2
print('{0}, 说大雾翻车只考了 {1:.1f}%'.format('大佬舍友', 95.125))


ar.append('sth')
pop=ar.pop() #删除最后一项并弹出,字典也有这个方法


ar.extend([sth]) #一切迭代器均可使用该方法,extend的参数必须为可迭代对象,多数情况下只有extend的参数与操作对象类型相同才能得到理想的结果,emmm这就引出了下面鸭子类型概念
#如:
list=[x*y for x in range(1,5) for y in range(1,3)]
list.extend('141')
print(list)
#输出:[1, 2, 2, 4, 3, 6, 4, 8, '1', '4', '1']


dir.get("no1",None)


#set类似于无重复列表,相对于列表的insert有add方法


#函数默认参数必须指向不变对象

鸭子类型

当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子

在鸭子类型的编程形式当中,类型不是我们关心的第一要素,真正重要的在于这个对象的行为。

这涉及到python对多态实现的要求,在非动态语言的面向对象编程中,函数可以在传入参数时表现多态性,函数设定传入参数类型后,这个参数的子类也可以正常的被传入该函数,比如下面写的几行简单的java

class course{
    String name;
    course(String name){
        this.name=name;
    }
    public void fail(){
        System.out.println("kev1n 挂掉了"+this.name+"这一科");
    }
}

class english{
    String name;
    english(String name){
        this.name=name;
    }
    public void fail(){
        System.out.println("kev1n 挂掉了"+this.name+"这一科");
    }
}

class math extends course{
    String name;
    math(String name){
        super(name);
    }
}
//能通过编译的test类
class test{
    public static void exam(course a){
        a.fail();
    }
}

/*不能通过编译的test类
class test{
    public static void exam(english a){
        a.fail();
    }
}
*/

public class Main{
    public static void main(String[] args){
        course GaoShu=new course("高数");
        test.exam(GaoShu);
    }
}
//输出结果:kev1n 挂掉了高数这一科

虽然english类看起来跟course类一模一样,而且也实现了fail()方法,但它们没有继承关系,所以无法通过编译,下面再用python来写一下这段代码

class course:
	def __init__(self,name):
		self.name=name
	def fail(self):
		print("kev1n挂掉了{0}这一科".format(self.name))

class english:
	def __init__(self,name):
		self.name=name
	def fail(self):
		print("kev1n挂掉了{0}这一科".format(self.name))
            
class math(course):
    def __init__(self,name):
        super(math,self).__init__(name)

class test:
    def __init__(self):
        pass
    def exam(self,varClass):
        varClass.fail()
        

if __name__ == '__main__':
    GaoShu=math("高数")
    JingDu=english("英语精读")
    Test=test()
    Test.exam(GaoShu)
    Test.exam(JingDu)
'''    
输出结果:
kev1n挂掉了高数这一科
kev1n挂掉了英语精读这一科
'''

def exam(self,varClass):这一行中甚至都没有对传入类的类型声明,所以只要传入的类中实现了fail()方法,统统能通过编译

动态语言果然可以为所欲为=。=

所以就不难理解为什么上面那段代码ar.extend("233"),字符串可以分成多个字符加入列表中了,只要字符串和列表这两个可迭代对象都实现了迭代的函数,他们就可以迭代并拼到一起

对生成器和迭代器的理解

用的机器人轮子是nonebot,是基于异步IO实现的,而理解异步IO首先要用好生成器

列表生成器

首先是列表生成器,比较容易理解,它可以把for循环生产列表的语法变得精简

L = []
for x in range(1, 11):
	L.append(x * x)

#上面的生成代码压缩为一行:

[x*x for x in range(1,11)]

#此外,还有两层循环的情况,三层循环就很少用到列表生成器了:

[m + n for m in 'ABC' for n in 'XYZ']#生成全排列['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'],靠后的为内层循环

[d for d in os.listdir('/')] # os.listdir可以列出文件和目录


最简单的生成器

把列表生成器的[ ]改为( )就创建了一个最简单的生成器

引用一篇博客里面的话

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

而生成器也是可以迭代的,但是你 只可以读取它一次 ,因为它并不把所有的值放在内存中,它是实时地生成数据。

生成器可以用next()或最简单的for循环来访问,但for循环的访问是一次性的

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

---------------------------------------------------------------------------

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g = (x * x for x in range(10))

---------------------------------------------------------------------------

>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

yield构建函数生成器

引用栈溢出一个回答的翻译博文

yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。

>>> def createGenerator() :
...    mylist = range(3)
...    for i in mylist :
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

这个例子没什么用途,但是它让你知道,这个函数会返回一大批你只需要读一次的值.为了精通 yield ,你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象,这有点蹊跷不是吗。

那么,函数内的代码什么时候执行呢?当你使用for进行迭代的时候.现在到了关键点了!第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。

如果生成器内部没有定义 yield 关键字,那么这个生成器被认为成空的。这种情况可能因为是循环进行没了,或者是没有满足 if/else 条件。

所以yield类似于返回语句,把函数以生成器的形式返回

就可以实现一个计算斐波那契数列的简单生成器:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

异步IO

协程(并非某app

参考官方文档

一个简单的协程,两段代码的对比

import asyncio
import time


async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)


async def main():
    print(f"started at {time.strftime('%X')}")

    await say_after(1, 'hello')
    await say_after(2, 'world')

    print(f"finished at {time.strftime('%X')}")


asyncio.run(main())

'''
输出:
started at 18:55:54
hello
world
finished at 18:55:57
'''
import asyncio
import time


async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)
    
async def main():
    task1 = asyncio.create_task(
        say_after(1, 'hello'))

    task2 = asyncio.create_task(
        say_after(2, 'world'))

    print(f"started at {time.strftime('%X')}")

    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    await task1
    await task2

    print(f"finished at {time.strftime('%X')}")
    
    
asyncio.run(main())
   
'''
输出:
started at 18:56:40
hello
hello
finished at 18:56:42
'''

可以看出,上面的代码是顺序完成两个任务的,总计使用3秒;而下面的创建的task,由asyncio.create_task()使代码进入了事件循环,排入日程中交替循环。在运行过程中,task1未执行完马上开始执行task2,并且不断循环,直到两个协程全部执行完毕,就达成了并发执行两个任务的要求

Written by
crumbledwall

Keep Curious.