Python基础
基础数据类型
字符串
1.单引号和双引号均可
2.字符串的引号之前加上r表示不要转义
3.'''这里是字符串'''
表示多行字符串
4.a='ABC'
表示创建一个字符串对象,并将其赋值给a变量
- 因此这段代码的执行结果应该能明了
a = 'ABC'
b = a
a = 'XYZ'
print(b)
- 结果应该是
ABC
而不是XYZ
,因为这是两个不同的对象,对应不同的内存
5.常用函数
①ord('A')
——获取字符的十进制Unicode编码
②chr(65)
——将十进制Unicode编码转换为字符
③len('ABC')
——获取字符串长度
④'ABC'.encode('ascii')
——使用指定编码表示字符串
⑤'ABC'.decode('ascii',errors='ignore')
——使用指定编码表示字符串,并且忽略其中的错误编码,比如中文字符不能用ascii编码。
⑥'name:{0},age:{1}'.format('torcy', 18)
——格式化字符串
6.字符串默认使用Unicode编码,即每一个字符都是2个字节,也可以加上前缀b表示使用每个字符是byte
类型,即每个字符占用一个字节
7.字符串格式化
①普通格式化方式与c语言一致,但是用%
隔开格式化列表和值,比如
print('hello, this is %s' % 'Torcy')
布尔值
1.True
和False
2.关于布尔值的运算符:and
、or
、not
空值
1.None
列表list——[]
1.list是一种有序集合,值是任意数据类型
2.索引从0开始,不能越界,负数表示倒数第几个
3.空list——[]
,长度为0
4.常用方法
name = ['torcy','lollip']
①len(name)
——获取list元素的个数
②name.apend('tc')
——在list末尾追加一个元素
③name.insert(1, 'tc')
——插入元素到索引为1的位置
④x=name.pop()
——删除末尾一个元素,并返回这个元素
- pop中也可以指定某个索引
元组tuple——()
1.tuple也是有序列表,唯一和list不同的是,一经初始化不可修改
2.末尾额外的一个逗号可以消除歧义
t=(1,)
——表示有一个元素1的tuplet=(1)
——表示数字1,因为括号是数学运算符
3.如果tuple的元素是某个空间,而这块空间中的值可以改变,这似乎成了“可变的”tuple
字典dict——{}
1.哈希表
2.访问不存在的key会报错
3.in
操作符来判断key是否在一个dict中,返回布尔值
4.常用方法:
dict={'name':'torcy','age':18,'gender':'male'}
dict.get('hobby')
——获取dict中的某个元素,如果不存在返回Nonedict.get('hobby',-1)
——获取dict中的某个元素,如果不存在返回-1dict.pop('gender')
——删除一个元素dict.values()
——获取它的value集合
集合set——[]
1.key不能重复,数学意义上的无序和无重元素的集合
2.要创建一个set需要提供一个list作为输入集合,同时自动去重
3.可以做数学上的集合运算
s1=set([1,2,3])
s2=set([2,3,4])
- 并——s1 | s2
- 交——s1 & s2
4.常用方法:
s=set([1,2,2,3,4,4,5,4]) #s={1,2,3,4,5}
s.add(6)
——添加一个元素s.remove(4)
——删除一个元素
循环
1.for in
循环,用来迭代list或者tuple中的元素
除法
1./
精确除法,结果总是一个浮点数
2.//
取整除法,结果总是一个整数,只取整数部分,而不是四舍五入
编码
1.计算机内存中统一使用Unicode编码,在磁盘上或者传输时总是转换为UTF-8编码
2.服务器动态生成的网页内容应该是Unicode的,所以它需要转换为UTF-8编码来传输
常用方法
字符串
s表示一个字符串文本
s.isalpha()
- 如果s中全部是字母,则返回True,否则False
s.isdigit()
- 如果s中全部是数字,则返回True,否则False
s.split('str')
- 按照str作为分隔符进行分割,返回一个tuple,注意str可能不止是一个字符
s.join(iterable)
- 将可迭代对象中str字符串以s为分隔符,连接成一个新的字符串
s.isupper()
- s中的字母全为大写(不管其他字符),就返回True,否则返回False
s.islower()
- s中的字母全为小写(不管其他字符),就返回True,否则返回False
s.lstrip(' ')
- 去掉字符串左边所有的空格字符,遇到非空格字符就结束
s.rstrip(' ')
- 去掉字符串右边所有的空格字符,遇到非空格字符就结束
s.ljust(width, [fill])
- 将字符串左对齐扩充为width位,右边用fill填充,默认空格
s.rjust(width, [fill])
- 将字符串右对齐扩充为width位,左边用fill填充,默认空格
s.center(width,[fill])
- 将字符串居中对齐,两边用fill填充,默认空格
s.lower()
- 将字符串中的字母全部转换为小写
s.upper()
- 将字符串中的字母全部转换为大写
s.capitalize()
- 首字母大小,其余小写,首字符若不是字母,则啥也不干
s.find(sub)
- 返回找到的子字符串的索引,未找到则返回-1
s.replace(old,new,[count])
- 将字符串中的old全部转换为new,还可以指定替换的次数
s.title()
- 将字符串中的每个单词的首字母大写,其余小写
常用内置函数
bin(x)
- 将一个整数转换成二进制字符串
hex(x)
- 将一个整数转换成十六进制字符串
int(x)
- x若是浮点数,则向0舍入
- int(str, base=16),将字符串按照16进制解析,返回十进制整数
chr(x)
- 返回这个整数所表示的Unicode字符
ord(ch)
- 返回这个Unicode字符的整数编码
divmod(a,b)
- 返回一个tuple——(a//b, a%b)
filter(func,iterable)
- 丢弃iterable中那些是func返回为False的元素,重新组成一个iterable
float(x)
- 将整数数字或者字符串转换成浮点数,可以有+/-/.,但若包含之外的非数字字符会报错
input([prompt])
- 先输出prompt,末尾不带换行符,然后从标准输入中读取一行,将其转换成字符串
len(s)
- 返回一个可迭代对象的元素个数
list(iterable)
- 将一个可迭代对象转换成列表,常用来转换字符串
max(a,b,c……)
- 返回其中的最大值
- max(iterable)——返回这个可迭代对象中的最大值
min(a,b,c……)
- 返回其中的最小值
- min(iterable)——返回这个可迭代对象中的最小值
next(iterator)
- 获取这个迭代器的下一个元素
range(x)
range(start,stop,step)
- 返回一个从0x-1且步长为1,或者,从startstop且步长为step的可迭代对象,常用在for……in循环中
round(num,precision)
- 将浮点数num舍入到小数点后precision位
- round(x),若x刚好在两个整数中间,则选择偶数,否则四舍五入
- round对整数并不起作用,应该使用
'%.2f' % 43
- 注意round并不总是准确的,由于浮点数并不能精确地表示
sum(iterable)
- 对该对象中的项求和返回,期望是一个数字列表
type(x)
- 返回一个值的类型
函数
1.函数名其实就是一个函数对象的引用,因此可以把一个函数名赋值给另一个变量,相当于给函数起了一个别名
2.定义函数使用def
3.pass语句可以暂时跳过当前代码块,在函数式编程中有用,让功能不完整的程序运行起来,不至于报错
4.函数返回值是一个tuple,不过括号可以省略不写,因此可以返回多个值
函数的参数
1.默认参数
- 默认参数从右向左,产生重载
- 默认参数必须指向不变对象(*)
2.可变参数
- 在形参前面加上一个星号*,表示这个形参的个数不定
- 这实际上函数接受到了一个tuple
- 如果要传入一个已有的list或tuple作为可变参数,可以在实参前面加上星号*
3.关键字参数
- 在形参前面加上两个星号**
- 可以传入任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def person(name,age,**kw):
print('name',name,'age',age,'other',kw)
###########################################
#在调用时可以只传入必选参数
person('torcy',19)
#也可以传入任意个关键字参数
person('torcy',20,gender='male')
#当然如果已有一个dict,也可加上**传入函数
d = {'city':'hz','job':'student'}
person('torcy',20,**d)
4.命名关键字参数
- 由于关键字参数可以传入任意的参数,但是我们希望限制传入的参数的名字,只接受这些参数
- 命名关键字形参必须是以key-value的形式,即必须带上参数名
- 如果已经有了一个可变参数,就可以不使用*分隔符
def person2(name,age,*,city,job)
print(name,age,city,job)
#*作为一个分隔符,后面的形参被视作命名关键字参数
5.参数组合
定义参数的顺序是必选参数、默认参数、可变参数、关键字参数、命名关键字参数
切片slice
1.对string、list、tuple均生效
2.如L=[1,2,3,4,5,6,7,8,9,0]
- L[0:5]——取出索引为0,1,2,3,4的元素,含头不含尾
- L[:3]——前面的0可以省略
- L[4:]——后一个索引省略,表示取出后面所有的
- L[-4:]——取出倒数第四个和后面所有的
- L[-2:-1]——取出倒数第二个
3.上述索引的步长默认为1,也可以指定步长 - L[1:4:2]——从第1个元素开始,取到第4个元素,步长为2
- L[::2]——取出第奇数个元素
- L[1::2]——取出第偶数个元素
迭代iteration
1.可以使用for……in循环来遍历一个可迭代对象——迭代
2.这个对象可以是
①list
②tuple
③string
④dict
- 默认只迭代key
for val in d.values()
——只迭代valuefor k,v in d.items()
——迭代key和value
3.判断一个元素是否是可迭代对象
通过collections模块中的Iterable类型
from collections import Iterable
isinstance('abc',Iterable) #True
isinstance(123,Iterable) #False
列表生成式
1.可以用来生成list的方法
2.几个栗子
- 生成list[1,2,3,4,5,6,7,8,9,10]
list(range(1,11))
——实际上是类型转换
- 生成[1,4,9,16,25,...,100]
[x*x for x in range(1,11)]
- 生成[4,16,36,64,100]
[x*x for x in range(1,11) if not x%2]
- 生成['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']全排列
[a+b for a in 'ABC' for b in 'XYZ']
- 把一个list中字符串全部变成小写
[s.lower() for s in L]
生成器
1.list生成式可以一次性生成整个list,但如果我们只是暂时想使用前面的几个元素,以后有可能会使用该list的后面的元素,如果一次性就生成整个list,会造成空间的浪费
2.所以我们需要报存生成这个list的方法,以便于在需要的时候扩展这个list
3.这个问题的解决办法就是使用生成器
4.将列表生成式中的[]
改成()
就会生成一个generator
5.generator是一个可迭代对象,使用for循环迭代,无需关心其边界,否则可以使用next(g)
方法来访问下一个元素
6.如果要用来生成算法比较复杂,就要考虑使用生成器函数了
任何一个函数体中,只要出现了yield关键字,就将这个函数变成了生成器函数
一个栗子:
- fibonacci数列生成的普通函数
def fib(max):
n,a,b = 0,0,1
while n < max:
print(b)
a,b = b,a+b
n = n + 1
return 'done'
- fibonacci数列生成的生成器函数
def fib(max):
n,a,b = 0,0,1
while n < max:
field b
a,b = b,a+b
n = n + 1
return 'done'
7.生成器函数的执行流程与普通函数有所区别
普通函数:
- 通过函数名调用时顺序执行,遇到return或者函数末尾就返回
生成器函数:
- 只在调用next()时才执行,遇到yield语句就返回,下一次next()的调用从上次返回的yield语句处继续执行
- 使用for循环来代替单独调用next()
迭代器
1.可以被next()函数调用并不断返回下一个值的对象称为迭代器Iterator
2.可以使用isinstance()判断一个对象是否是Iterator对象
3.一个对象可能是Iterable的,但不是Iterator对象,如list,dict,str
4.可以使用iter函数将Iterable的对象转换成Iterator对象
5.你可能会问,为什么list、dict、str等数据类型不是Iterator?
- 这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
- 可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
- Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的
高阶函数
1.函数名也是变量,可以把函数名赋值给变量,相当于给函数起了一个别名
2.一个可以接受函数作为参数的函数称为高阶函数
map
1.map(func,iterable)函数接受两个参数,一个是函数,一个是可迭代对象
2.遍历对象中的每个元素,并将其传入参数函数,将结果组成一个新的list
3.不会改变原可迭代对象,返回结果
reduce
1.reduce(func,iterable)函数接受两个参数,一个是函数,一个是可迭代对象
2.reduce遍历对象的每个元素,然后将结果与下一个元素继续传入参数函数
3.几个使用map/reduce的小栗子
from functools import reduce
def normalize(name):
return name[0].upper() + name[1:].lower()
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
#------------------------------------------
def prod(L):
def multiply(x,y):
return x*y
return reduce(multiply,L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
#-------------------------------------------
def str2float(str):
#save the index of '.'
l = len(str)
idx = str.index('.')
str = str.replace('.','')
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def chr2num(chr):
return DIGITS[chr]
def fn(x,y):
return x*10+y
return reduce(fn,list(map(chr2num, str))) / 10**(l-idx-1)
print(str2float("123.456"))
filter
1.filter(func,iterable)接受两个参数,一个函数和一个序列
2.filter的作用是,对每一个元素都调用func函数然后根据返回值是true还是false决定保留还是丢弃该元素
3.一些小栗子:
①删掉list中的偶数
def is_odd(n):
return n % 2 == 1
print(list(filter(is_odd,[1,2,3,4,5,6,7,8,9])))
②产生素数序列
#------------------
#从3开始的奇数序列
def _odd_iter():
n = 1
while True:
n += 2
yield n
#过滤函数
def _not_divisible(n):
return lambda x: x % n > 0
#素数生成器
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
#-------------------
for n in primes():
if n < 1000:
print(n)
else:
break
sorted
1.内置函数sorted可以对list进行排序
2.使用方法
①升序排序
sorted([36, 5, -12, 9, -21])
②使用自定义比较器
sorted([36, 5, -12, 9, -21],key=abs)
- key是一个命名关键字参数
- key指定的函数将作用于list每一个元素上,重组成新的list进行排序
③降序排序
sorted([36, 5, -12, 9, -21],key=abs, reverse = True)
- reverse也是一个命名关键字参数
函数作为返回值
1.一个栗子
def lazy_sum(*args):
def sum():
res = 0
for n in args:
res += n
return res
return sum
- 当调用lazy_sum(1,2,3,4,5,6)时,并不会立即求和,而只是将参数传递给了内部的sum函数
- lazy_sum()的返回值是一个函数(内部的sum函数),再次调用该函数才会执行求和运算
- 注意调用lazy_sum()每次返回的都是一个新函数
闭包
1.内函数使用外函数的变量,然后将内函数作为外函数的返回值返回
注意不要在内函数中使用任何会在外函数中发生变化的变量
2.如果一定要在内函数3中使用循环变量,解决办法就是在外函数1和内函数3中间,再加上一个函数2用来把循环变量传递给内函数3,在函数2中把函数3作为结果返回。此时3是2的闭包,2是1的闭包
3.一个小栗子
#计数器
def createCounter():
def hh():
i = 1
while True:
yield i
i += 1
it = hh()
def counter():
return next(it)
return counter;
- 将生成器作为闭包返回,每次调用闭包就会调用一次生成器
- 每调用一次返回一个整数
匿名函数
1.关键字lambda
表示匿名函数
2.lambda x: x*x
相当于
def f(x):
return x*x
3.函数内只能有一个表达式,不用写return,返回值就是该表达式的值
4.匿名函数的好处是:不用起函数名,简化代码
5.也可以将匿名函数赋值给一个变量,再利用该变量来调用该函数
装饰器
1.在代码运行期间给函数增加功能的方式称之为装饰器
偏函数
1.当频繁使用带有关键字参数的某个函数时(注意并不是默认参数,所以需要手动写出),可以创建一个偏函数指定使用该关键字参数
2.这得益于functools模块中的partial函数
- partial函数接受三个参数:要制定的函数对象、*args、**kw
3.一个栗子
int2 = functools.partial(int, base = 2)
- 每次调用int2函数,就会默认使用base=2
max10 = functools.partial(max, 10)
- 每次调用max10函数,就会默认把10加入到参数列表中
文件读写
1.使用内置的open函数
2.open函数接受两个参数:文件名,打开方式
f = open('test.txt','r')
- 以读的方式打开test.txt文件,如果文件不存在则报错
- 默认编码是UTF-8,也可以使用命名关键字参数encodeing='gbk',指定编码
- 可以使用命名关键字参数errors='ignore',忽略其中的错误编码,否则会报错
3.文件对象的方法
①f.read()方法可以一次性读取文件的所有内容,并将结果用一个str对象表示
②f.readline()方法每次读取文件的一行,每调用一次返回一行
③f.readlines()方法一次性读取文件的所有内容,并将结果按行保存在一个list中
④f.write('hello world')方法向文件写入一行内容
⑤f.close()方法关闭文件,文件使用后必须关闭以节约系统资源
- 如果文件打开错误就不会关闭,为了保证无论出错与否都能正确关闭文件,可以使用with语句来帮我们自动调用close方法
with open('test.txt', 'r') as f
⑥f.seek(offset,whence)方法移动文件指针,offset表示移动的字节数,正为向前,负为向后,whence是可选值,默认为0,表示相对于文件开头,1表示相对于当前位置,2表示相对于文件末尾
⑦f.tell()方法获取当前文件指针的位置
4.文件的操作模式
- 'r':读
- 'w':写
- 'a':追加
- 'r+' == r+w(可读可写,文件若不存在就报错(IOError))
- 'w+' == w+r(可读可写,文件若不存在就创建)
- 'a+' ==a+r(可追加可写,文件若不存在就创建)
对应的,如果是二进制文件,就都加一个b就好啦:
- 'rb' 'wb' 'ab' 'rb+' 'wb+' 'ab+'
StringIO和BytesIO
1.很多时候数据读写不一定是文件,也可以是内存中
StringIO
1.在内存中读写str
2.创建了StringIO对象之后,就可以向文件一样使用read(),write()方法了
3.常用方法:
①f.getvalue()获得f对象的值
②创建StringIO对象时,也可以使用一个字符串来初始化
- 注意初始化之后对象指针还是在0位置的,也就是说此时如果使用f.write()方法,写入的内容会从起始处写而覆盖原来初始化的内容
- 使用了write()方法之后,指针移动相应的大小,所以write()方法是追加
BytesIO
1.BytesIO对象允许在内存中读写字节
操作文件和目录
os模块和os.path模块
1.常用方法
- os.environ——当前操作系统中定义的环境变量
- os.path.abspath('.')——当前目录的绝对路径
- os.path.join('c:/users/torcy','testdir')——路径加上文件夹名,形成新的路径
- os.mkdir('c:/users/torcy/testdir')——创建一个目录
- os.rmdir('c:/users/torcy/testdir')——删除一个目录
把两个路径合并成一个时,不能直接拼接字符串,而要通过os.path.join()函数,这样可以处理不同操作系统中的路径分隔符
- os.path.split()——把一个路径拆分为两部分,后一部分总是最后的目录或者文件名,返回一个tuple
- os.path.splitext(’c:/users/torcy/test.txt‘)——直接得到文件的扩展名,返回('c:/users/torcy',',txt')
- os.rename('test.txt','test.py')——对文件重命名
- os.remove('test.txt')——删除文件
- os.listdir('c:/users/torcy/')——列出当前目录下的所有文件或文件夹
- os.path.isdir('test')——判断test是否是一个目录
- os.path.isfile('test')——判断test是否是一个文件
2.一个小栗子
①实现dir命令
for i in [(time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(os.path.getatime(name))), '<DIR>' if os.path.isdir(name) else ' ', str(os.path.getsize(name)).rjust(4), name)
for name in os.listdir('.')]:
print(str(i).strip('(').strip(')'))
②在当前目录及子目录下递归查找包含指定字符串的文件
def find_file(s = '', d = '.'):
items = os.listdir(d)
dirs,files = [], []
for i in items:
if os.path.isfile(i):
files.append(i)
elif os.path.isdir(i):
dirs.append(i)
for i in files:
if i.find(s) >= 0:
print(i)
for di in dirs:
find_file(s,di)
print(find_file('test'))
序列化
pickle模块
1.变量从内存中编程可存储或传输数据的过程称之为序列化(pickling、serialization)
2.常用方法:
d = dict(name='torcy',age=20,score=88)
- pickle.dumps(d)——将对象序列化成bytes类型,然后就可以把bytes写入文件
- pickle.dump(d,f)——将对象序列化成bytes类型,并写入f类文件对象
- pickle.loads(b)——将序列化数据反序列化成一个对象,返回这个对象
- pickle.load(f)——从一个类文件对象中读入数据,反序列化成一个对象,并返回
json模块
1.json不仅是标准格式,而且比XML更快
- json.dumps(d)——返回一个str
- json.dump(d,f)——将序列化数据写入类文件对象
- json.load(f)——从类文件对象中读入数据,反序列化成对象,并返回- json.loads(json_str)——将json字符串反序列化成对象,返回这个对象
海龟绘图
fd(dis)——前进dis距离,forward
bk(dis)——后退dis距离,backward
lt(90)——左转90度,left
rt(90)——右转90度,right
goto(x,y)——移动到(x,y),是否划线由笔抬起或落下决定
setx(x)——移动x坐标
sety(y)——移动y坐标
seth(90)——旋转海龟的朝向,值为0:上;270:下;180:左;90:右
home()——将海龟归位
circle(radius,extent圆心角,steps边的数量)——圆心在海龟的左边,圆实际上由多边形转换而来
dot(size>=1,color)——画一个点
pu()——抬起笔,penup
pd()——放下笔,pendown
undo()——撤销一次动作,可以做逆向动画
speed(6)——参数0-10逐渐加快,6为正常
pos()——返回当前坐标为一个tuple,position
xcor(x)——返回或设置x坐标
ycor(y)——返回或设置y坐标
heading()——返回当前朝向
distance(x,y)——计算当前海龟到(x,y)的直线距离
width()——设置线条宽度
pen()——设置或返回画笔的当前参数,可以保留以后续恢复
isdown()——画笔是否落下,注意没有isup函数
pencolor('#74a7b9')——画笔颜色
fillcolor('#74a7b9')——填充颜色
color(pen,fill)——同时设置画笔和填充颜色
begin_fill()——填充图形前调用
end_fill()——结束填充后调用
write('text',move,align,font)
- move:写完后是否移动画笔到右下角;align:对齐方式;font:一个tuple('Courier New',16,'normal')
ht()——隐藏海龟,显著加快绘制速度,hideturtle
st()——显示海龟,showturtle
isvisible()——海龟是否可见,返回布尔值
shape()——设置或返回海龟的形状,arrow,classic,turtle,square
colormode(255)——设置colormode,可以为1或255
bgcolor('#ffffff')——窗口的背景色
bgpic('bg.gif')——背景图片,只支持gif格式,只改后缀名是没有用的!!!
delay()——动画延迟,毫秒
numinput(title, prompt, default=None, minval=None, maxval=None)——弹出窗口接受数字输入
textinput(title, prompt)——弹出窗口接收文本输入
title('title')——GUI窗口的标题
done()——必须作为绘图结束的函数调用
常用事件
listen()——开始监听键盘事件
ontimer(func,t)——定时器,在t时间之后执行func函数,毫秒
1.鼠标事件
- func函数必须有两个形参x,y,传入鼠标的坐标
- key=1默认为左键
onclick(func,key=1)——点击海龟时执行func函数,1表示左键
onscreenclick(func,key=1)——屏幕被点击时执行func函数
onrelease(func,key=1)——鼠标释放后执行func函数
ondrag(func,key=1)——鼠标点击后拖动时执行func函数
2.键盘事件
- 使用键盘事件之前必须调用listen函数
onkeyrelease(func,key)——指定key键释放时调用func函数
onkeypress(func,key)——指定key键按下时调用func函数