一、函数对象
函数(Function)作为程序语言中不可或缺的一部分,但函数作为第一类对象(First-Class Object)却是 Python 函数的一大特性。
那到底什么是第一类对象(First-Class Object)呢?
在 Python 中万物皆为对象,函数也不例外,函数作为对象可以赋值给一个变量、可以作为元素添加到集合对象中、可作为参数值传递给其它函数,还可以当做函数的返回值,这些特性就是第一类对象所特有的。xx
1.函数是第一类对象,函数身为一个对象,拥有对象模型的三个通用属性:id、类型、和值。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
def foo():
print('from foo')
foo()
print(id(foo))
print(type(foo))
print(foo)
输出
from foo
4406808360
2.函数的特点:
1).函数可以被引用,即函数可以赋值给一个变量
还可以把该函数赋值给更多的变量,唯一变化的是该函数对象的引用计数不断地增加,本质上这些变量最终指向的都是同一个函数对象。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
def foo():
print('from foo')
foo()
func=foo #引用,赋值
print(foo)
print(func)
func()
输出
from foo
from foo
2).函数可以当做参数传递
def foo():
print('from foo')
def bar(func):
print(func)
func()
bar(foo)
输出
from foo
3).函数可以作返回值
函数接受一个或多个函数作为输入或者函数输出(返回)的值是函数时,我们称这样的函数为高阶函数
def foo():
print('from foo')
def bar(func):
return func
f=bar(foo)
print(f)
f()
输出
from foo
4).函数可以当作容器类型的元素 ---> 可理解为函数成变量名了,输出其则是输出内存地址
容器对象(list、dict、set等)中可以存放任何对象,包括整数、字符串,函数也可以作存放到容器对象中
def foo():
print('from foo')
dic={'func':foo}
foo()
print(dic['func'])
----->
dic['func']()
输出
from foo
from foo
3.函数还可以嵌套 -- 分两种:
1).函数的嵌套调用:
在一个函数内部调用另一个函数:
2).函数的嵌套定义:
一层一层定义下来,定义阶段没有执行
def f1():
def f2():
print('from f2')
def f3():
print('from f3')
# f3()
# f2()
f1()
结果:
无
def f1():
def f2():
print('from f2')
def f3():
print('from f3')
f3()
f2()
f1()
结果:
from f2
from f3
应用
def get(a):
def foo(t): #2
return t[1:]
new_a = foo(a) #1
return len(new_a)
print(get('python'))
结果:
5
二、命名空间与作用域
命名空间是名字和对象的映射,就像是字典,key是变量名,value是变量的值
1.命名空间的定义
name='egon' #定义变量
def func(): #定义函数
pass
class Foo: #定义类
pass
2.命名空间的分类:内置名称空间、全局、局部
1.内置名称空间: 随着python解释器的启动而产生,包括异常类型、内建函数和特殊方法,可以代码中任意地方调用
print(sum)
print(max)
print(min)
print(max([1,2,3]))
print(max(1,2,3))
print(max('n','b','a'))
import builtins
for i in dir(builtins): #打印所有的内置函数
print(i)
输出
3
3
n
round
set
setattr
...
2.全局名称空间:文件的执行会产生全局名称空间,指的是文件级别定义的名字都会放入该空间
x=1 #全局命名空间
def func():
money=2000 #非全局
x=2
print('func')
print(x)
print(func)
func()
3.局部名称空间:调用函数时会产生局部名称空间,只在函数调用时临时绑定,调用结束解绑定 -- 所以函数里面的函数只会在调用了上一级别函数时候产生。函数定义阶段不会产生,只检测语法
x=10000 全局
def func():
x=1 局部
print(x)
def f1():
print(x)
func()
结果:
1
del x 解除绑定关系
3.作用域 -- 当前所在的范围就是当前作用域,只能从当前作用域开始找(如在全局那就是全局开始找)
命名空间的可见性就是作用域
1. 全局作用域:内置名称空间,全局名称空间
2. 局部作用域:局部名称空间
名字的查找顺序:局部名称空间---》全局名层空间---》内置名称空间
查看全局作用域内的名字:gloabls()
查看局部作用域内的名字:locals()
全局作用域的名字:全局有效,在任何位置都能被访问到,除非del删掉,否则会一直存活到文件执行完毕
局部作用域的名字:局部有效,只能在局部范围调用,只在函数调用时才有效,调用结束就失效
x=1000
def func(y):
x=2
print(locals())
print(globals())
func(1)
输出
{'y': 1, 'x': 2}
{'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10c436c88>, '__package__': None, '__cached__': None, '__file__': '/Users/hexin/PycharmProjects/py3/day4/2.py', 'func': , '__builtins__': , '__spec__': None, '__doc__': None, 'time': , '__name__': '__main__', 'x': 1000}
如果为一个定义在函数外的变量赋值,那么得告诉Python这个变量名不是局部的,而且 全局 的。使用global语句完成这一功能:
global x :声明x是全局 --- 执行过程中对外部的状态的修改
x = 1000
deff1():globalx
x=0print(x)
f1()print(x)
结果:10000
x=3
def func():
global x
x=2
print('x is', x)
def dun():
print('x is', x)
func()
dun()
结果:
xis 2xis 2
四、闭包函数
简单来说,一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。这个返回的函数B就叫做闭包。-- 不准确
闭包函数须满足以下条件:
1. 定义在内部函数;
2. 包含对外部作用域而非全局作用域的引用;
---> 里面的参数都是局部内部的作用域里面的
例
def f1():
x = 1
def f2():
print(x)
return f2
f=f1()
print(f)
x=100
f()
print(x)
输出
.f2 at 0x107714400>
1
100
闭包的应用 --- 惰性计算,这样能随时定义,只要加上()就可以执行
from urllib.request import urlopen #爬网页
def index(url):
def get():
return urlopen(url).read()
return get
oldboy=index('http://crm.oldboyedu.com')
print(oldboy().decode('utf-8'))
输出
!DOCTYPE html>
OldboyCRM...
闭包特点:
闭包函数引用的是局部作用域
五、装饰器 --- @a 就是将函数b当参数传递给函数a(参数),并用原来的函数b名字返回新的函数b = a(b)
做了---> 把正下方的函数当作参数传进来,得到的结果重新绑定给参数名
叠加多行的执行顺序:自上而下执行 --->考虑执行顺序,上面的装饰器装饰的是它下面所有的了
1.定义
(装饰器就是一种的闭包的应用,只不过其传递的是函数:)
装饰器:修饰别人的工具,修饰添加功能,工具指的是函数
装饰器本身可以是任何可调用对象,被装饰的对象也可以是任意可调用对象 ---->就目前看,可调用对象是函数,那么装饰器本身就是函数,被装饰的也是函数。
原则:
1.不修改源代码
2.不修改调用方法
目标:添加新功能
2.为什么要用装饰器?
开放封闭原则:对修改是封闭的,对扩展是开放的
装饰器就是为了在不修改被装饰对象的源代码以及调用方式的前提下,为其添加新功能 (原来的添加新功能 )
3.装饰器的实现
装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”
直接看示意图,其中 a 为与装饰器 @a 对应的函数, b 为装饰器修饰的函数,装饰器@a的作用是:
简而言之:@a 就是将函数b当参数传递给函数a(参数),并用原来的函数b名字返回新的函数b = a(b)
例如
def a(name): #与装饰器对应的函数
return name()
@a #装饰器 b = a(b)
def b(): #被装饰函数
print('hexin')
输出
hexin
defa(name):print("first")returnname
@adefb():print('hexin')
b()
输出:
first
hexin
(叠加多行的执行顺序:自上而下执行)
装饰器可以叠加,叠加先执行上面的装饰器,但是却把下面的一起当被装饰的函数,这样反而先中途先执行下面的装饰器了
解析过程是这样子的:
1.python 解释器发现@a,就去调用与其对应的函数a (a 函数)
2.a 函数调用前要指定一个参数,传入的就是@a下面修饰的函数,也就是函数b
3.a() 函数执行,调用函数b(),b() 打印“hexin”
4.原来被装饰的有返回值:---添加一个return xxx
5.原来被装饰的是有参: ---用可变长参数
装饰器的基本框架:
def timer(func):
def wrapper():
func()
return wrapper
带参数的框架:
def timer(func):
def wrapper(*args,**kwargs):
func(*args,**kwargs)
return wrapper
6.有参装饰器: 最多再套这一层传参数(一共三层函数)
外面再套一层函数带传入值的,又成了新的闭包函数
装饰器可以叠加,叠加先执行上面的装饰器,但是却把下面的一起当被装饰的函数,这样反而先中途先执行下面的装饰器了
7.----自己执行的------:
先执行生成新的,再带入
7.装饰器的应用
例子1: 无参
import time
def timmer(func):
def wrapper():
start_time=time.time()
func() #index()
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
return wrapper
@timmer #index=timmer(index)
def index():
time.sleep(1)
print('welcome to index')
index() #还用原来的调用方法,只是加了装饰器
输出
welcome to index
run time is 1.005241870880127
例子2:原来被装饰的有参,有返回值
login_user={'user':None,'status':False}
def auth(func):
def wrapper(*args,**kwargs):
if login_user['user'] and login_user['status']:
res=func(*args,**kwargs)
return res
else:
name=input('请输入用户名: ')
password=input('请输入密码: ')
if name == 'hexin' and password == '123':
login_user['user']='hexin' #更改全局变量值,不用再认证
login_user['status']=True
print('\033[45mlogin successful\033[0m')
res=func(*args,**kwargs)
return res
else:
print('\033[45mlogin err\033[0m')
return wrapper
@auth #index=auth(index)
def index():
print('welcome to index page')
@auth #home=auth(home)
def home(name):
print('%s welcome to home page' %name)
index()
home('hexin')
输出
请输入用户名: heixn
请输入密码: 123
login err
请输入用户名: hexin
请输入密码: 123
login successful
hexin welcome to home page
六、可迭代对象和迭代器
1.迭代的概念:
上一次输出的结果为下一次输入的初始值,重复的过程称为迭代,每次重复即一次迭代,并且每次迭代的结果是下一次迭代的初始值。
注意:
下面的只是重复不是迭代:
while True: #只满足重复,因而不是迭代
print('====>')
下面才为迭代:
l = [1, 2, 3]
count = 0
while count < len(l): # 只满足重复,因而不是迭代,加上+1就是了
print('====>', l[count])
count += 1
2.可迭代的对象
(只要这个数据类型下面有__iter__方法,就是可迭代的对象。)
--->内置__iter__方法的数据类型,都是可迭代的对象。
str、元组、list是可迭代对象,dict是可迭代对象,set也是可迭代对象。
[1,2].__iter__()
'hello'.__iter__()
(1,2).__iter__()
{'a':1,'b':2}.__iter__()
{1,2,3}.__iter__()
例如:
x = [1, 2, 3]
y = iter(x)
z = iter(x)
print(next(y))
print(next(y))
print(next(z))
print(type(x))
print(type(y))
输出
1
2
1
执行可迭代对象的__iter__方法,得到的结果就是迭代器,迭代器对象有__next__方法
如下图所示
这里x是一个可迭代对象,y和z是两个独立的迭代器,迭代器内部持有一个状态,该状态用于记录当前迭代所在的位置,以方便下次迭代的时候获取正确的元素。
迭代器有一种具体的迭代器类型,比如list_iterator,set_iterator。可迭代对象实现了__iter__方法,该方法返回一个迭代器对象。
3.迭代器
1.为什么要有迭代器?
对于没有索引的数据类型,必须提供一种不依赖索引的迭代方式。
2.迭代器定义:s.__iter__() == iter(s) i.__nest__() == next(i) --》所以取迭代器的值就直接用next()
迭代器:可迭代对象执行__iter__方法,得到的结果就是迭代器,迭代器对象有_ _next__方法
它是一个带状态的对象,他能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter__和__next__()方法的对象都是迭代器。__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常(不是错误,只是告诉你取完了)
3.迭代器的实现 -->下面有个__next__方法取值
例:
i=[1,2,3].__iter__()
print(i) #迭代器
print(i.__next__())
print(i.__next__())
print(i.__next__())
#print(i.__next__()) #抛出异常:StopIteration
输出
1
2
3
每次调用next()方法的时候做两件事:
为下一次调用next()方法修改状态
为当前这次调用生成返回结果
迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。
4.如何判断迭代器对象和可迭代对象
from collections import Iterable,Iterator
'abc'.__iter__()
().__iter__()
[].__iter__()
{'a':1}.__iter__()
{1,2}.__iter__()
f=open('a.txt','w')
f.__iter__()
#判断是否为可迭代对象,以下都是
print(isinstance('abc',Iterable))
print(isinstance([],Iterable))
print(isinstance((),Iterable))
print(isinstance({'a':1},Iterable))
print(isinstance({1,2},Iterable))
print(isinstance(f,Iterable))
#判断是否为迭代器,只有文件是
print(isinstance('abc',Iterator))
print(isinstance([],Iterator))
print(isinstance((),Iterator))
print(isinstance({'a':1},Iterator))
print(isinstance({1,2},Iterator))
print(isinstance(f,Iterator))
输出
True
True
True
True
True
True
False
False
False
False
False
True
可迭代对象:只有__iter__方法,执行该方法得到的迭代器对象
迭代协议:
迭代器对象:有__iter__和__next__()方法
对于迭代器对象来说,执行__iter__方法,得到的结果仍然是它本身
5.迭代器的优点和缺点
优点:
1.提供了一种不依赖下标的迭代方式
2.就跌迭代器本身来说,更节省内存 -- 在同一时间只有一个值在内存里
缺点:
1. 无法获取迭代器对象的长度(不next完不知道多长)
2. 不如序列类型取值灵活,是一次性的(取完就没了),只能往后取值,不能往前退
6.额外
for循环取字典key原理: -- for循环原理,__iter__方法用途
dic=['name':'egon','age':18,'heght':180]
for k in dic: for循环会把in后面的dic执行__iter__方法把它变成迭代器,然后一次for循环就是执行一次__next__()给k
print(k)
监控抛异常:
try:
xxx
except StopIteration:
break
七、生成器
1.概念
生成器函数:只要函数体包含yield关键字,该函数就是生成器函数
生成器就是迭代器
把函数变成了迭代器
原本用return只能返回一个结果,然后就不继续执行了:
def foo():
return 1
return 2
return 3
return 4
res1=foo()
print(res1)
res2=foo()
print(res2)
结果都是1
换成yield变成了迭代器就能多次返回:
def foo():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
print('fourth')
yield 4
print('fifth')
g=foo()
for i in g: #g执行了__iter__又执行了next()
print(i)
结果:
first
1
second
2
third
3
fourth
4
fifth
重点:
而且加yield后再执行函数只是变成了生成器(迭代器)(next后也会暂停住),不会有函数的执行结果。要想触发迭代器的执行只有使用next(),这样也就触发了函数的执行(只是执行了,还需要取其返回值)
--->def xxx()
g=xxx()
print(next(g)) 这样才得到了执行函数的结果
2.yield功能
1).相当于为函数封装好__iter__和__next__方法
2).return只能返回一次值,函数就终止了
而yield能返回多次值,每次返回都会将函数暂停,下一次next会从上一次暂停的位置继续执行。
3.注意:
一般都会加个while True循环来多次next,这样就不用重复写,照样能运行结束,只是下次next会接着上次next停住(yield)的位置继续
模拟管道|grep
八、内置函数
简单来说就是python3本身就自带的函数。
abs(x)
abs()返回一个数字的绝对值。如果给出复数,返回值就是该复数的模
print(abs(-1100))
输出:1100
all()
如果iterable的所有元素不为0、''、False或者iterable为空,all(iterable)返回True,否则返回False;
print(all(['a', 'b', 'c', 'd'])) #列表list,元素都不为空或0
print(all(['a', 'b', '', 'd'])) #列表list,存在一个为空的元素
print(all([0,1,2,3])) #列表list,存在一个为0的元素
print(all(('a', 'b', 'c', 'd'))) #元组tuple,元素都不为空或0
print(all(('a', 'b', '', 'd'))) #元组tuple,存在一个为空的元素
print(all((0,1,2,3))) #元组tuple,存在一个为0的元素
print(all([])) # 空列表
print(all(())) # 空元组
True
False
False
True
False
False
True
True
注意:空元组、空列表返回值为True,这里要特别注意
any()
如果所有元素中有一个值非0、''或False,那么结果就为True,当iterable所有的值都是0、''或False时,那么结果为False,
print(any(['a', 'b', 'c', 'd'])) #列表list,元素都不为空或0
print(any(['a', 'b', '', 'd'])) #列表list,存在一个为空的元素
print(any([0,1,2,3])) #列表list,存在一个为0的元素
print(any(('a', 'b', 'c', 'd'))) #元组tuple,元素都不为空或0
print(any(('a', 'b', '', 'd'))) #元组tuple,存在一个为空的元素
print(any((0,1,2,3))) #元组tuple,存在一个为0的元素
print(any([])) # 空列表
print(any(())) # 空元组
True
True
True
True
True
True
False
False
ascii()
调用对象的__repr__()方法,获得该方法的返回值.
print(ascii([1,2,3,1,22,123])) #[1, 2, 3, 1, 22, 123]
bin()
三个函数功能为:将十进制数分别转换为2进制。
print(bin(10)) #0b1010
bool()
测试一个对象是True还是False.
print(bool([])) #False
bytes()
将一个字符串转换成字节类型
s="apple"
v=bytes(s,encoding="utf-8")
print(v) #b'apple'
callable(object)
callable()函数用于测试对象是否可调用,如果可以则返回1(真);否则返回0(假)。可调用对象包括函数、方法、代码对象、类和已经定义了 调用 方法的类实例。
a = '123'
print(callable(a)) #False
chr(i) 相反->ord(i)
chr()函数返回ASCII码对应的字符串。
print(chr(65)) #A
complex(real[,imaginary])
complex()函数可把字符串或数字转换为复数。
print(complex(2,1)) #(2+1j)
delattr()
删除对象的属性
dict()
创建数据字典
print(dict()) #{}
dir()
不带参数时返回当前范围内的变量,方法和定义的类型列表,带参数时返回参数的属性,方法列表
print(dir())
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'time']
divmod(x,y)
divmod(x,y)函数完成除法运算,返回商和余数。
print(divmod(10,3)) #(3, 1)
enumerate()
返回一个可以枚举的对象,该对象的next()方法将返回一个元组
s = ["a","b","c"]
for i ,v in enumerate(s,1):
print(i,v)
1 a
2 b
3 c
eval()
将字符串str当成有效的表达式来求值并返回计算结果,
将字符串执行转成对应的类型
s = "1 + 3 +5"
print(eval(s)) #9
exec()
执行字符串或complie方法编译过的字符串,没有返回值
float(x)
float()函数把一个数字或字符串转换成浮点数。
print(float("12")) #12.0
format()
格式化输出字符串
print("i am {0},age{1}".format("tom",18))
i am tom,age18
frozenset()
创建一个不可修改的集合
set和frozenset最本质的区别是前者是可变的,后者是不可变的。当集合对象会被改变时(例如删除,添加元素),只能使用set,
一般来说使用fronzet的地方都可以使用set。
参数iterable:可迭代对象。
globals()
返回一个描述当前全局变量的字典
a = "apple"
print(globals())
{'__package__': None, '__file__': '/Users/hexin/PycharmProjects/py3/day4/2.py', '__name__': '__main__', 'a': 'apple', 'time': , '__cached__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10bd73c88>, '__builtins__': , '__spec__': None, '__doc__': None}
hash()
哈希值hash(object)
注意:
可哈希的即不可变数据类型,不可哈希即可变数据类型
如果对象object为哈希表类型,返回对象object的哈希值。哈希值为整数,在字典查找中,哈希值用于快递比价字典的键。
两个数值如果相等,则哈希值也相等。
1. 只要校验的内容一致,那hash得到结果永远一样
2. 不可逆
3. 只要采用的哈希算法一样,那无论被校验的内容有多长,hash的到的结果长度都一样
help()
返回对象的帮助文档
调用内建的帮助系统,如果不包含参数,交互式帮助系统将在控制台启动。如果参数为字串,则可以是模块,类,方法等名称,并且帮助页面将会在控制台打印。参数也可以为任意对象
hex(x)
hex()函数可把整数转换成十六进制数。
print(hex(12)) #0xc
id()
返回对象的内存地址
a = "apple"
print(id(a)) #4562197840
input()
获取用户输入内容
int(x[,base])
int()函数把数字和字符串转换成一个整数,base为可选的基数。
iter()
返回一个iterator对象。
len()函数返回字符串和序列的长度。
print(len('aa')) #2
list(x)
list()函数可将序列对象转换成列表。
print(list("hello world"))
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
l=[] #l=list([])
locals()
打印当前可用的局部变量的字典
max(x[,y,z...])
max()函数返回给定参数的最大值,参数可以为序列。
print(max(1,2,3,4)) #4
min(x[,y,z...])
min()函数返回给定参数的最小值,参数可以为序列。
print(min(1,2,3,4)) #1
next()
返回一个可迭代数据结构(如列表)中的下一项
object()
获取一个新的,无特性(geatureless)对象。Object是所有类的基类。它提供的方法将在所有的类型实例中共享。
oct(x)
oct()函数可把给出的整数转换成八进制数。
print(oct(12)) #0o14
ord(x)
ord()函数返回一个字符串参数的ASCII码或Unicode值。
print(ord("a")) #97
open()
打开文件open(filename [, mode [, bufsize]])
打开一个文件,返回一个file对象。 如果文件无法打开,将处罚IOError异常
pow(x,y[,z])
pow()函数返回以x为底,y为指数的幂。如果给出z值,该函数就计算x的y次幂值被z取模的值。
print(pow(2,5)) #32
print(pow(2,5,3)) #2
range([lower,]stop[,step])
range()函数可按参数生成连续的有序整数列表。
print(range(1,10,2)) #range(1, 10, 2)
repr()
将任意值转换为字符串,供计时器读取的形式
reversed()
反转,逆序对象
round(x[,n])
round()函数返回浮点数x的四舍五入值。如给出n值,则代表取小数点后几位 (四舍五入到小数点后的位数)
print(round(5.9))
set()
将对象转换成集合
slice()
切片功能
s = ["a","b""c","d"]
print(slice(1,3,s))
slice(1, 3, ['a', 'bc', 'd'])
sorted()
排序
列表排序,按数轴方向排,高阶函数,以绝对值大小排序,字符串排序,按照ASCII的大小排序,如果需要排序的是一个元组,则需要使用参数key,也就是关键字。反向排序,reserve=True
str(obj)
str()函数把对象转换成可打印字符串。
print(str(4)) #4
sum()
求和
tuple(x)
tuple()函数把序列对象转换成tuple。
print(tuple("hello world"))
('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd')
type(obj)
type()函数可返回对象的数据类型。
print(type('123'))
print(type(1))
vars()
本函数是实现返回对象object的属性和属性值的字典对象。如果默认不输入参数,就打印当前调用位置的属性和属性值,相当于locals()的功能。如果有参数输入,就只打印这个参数相应的属性和属性值。
print(vars())
#{'__name__': '__main__', '__spec__': None, '__package__': None, '__builtins__': , 'time': , '__cached__': None, '__doc__': None, '__file__': '/Users/hexin/PycharmProjects/py3/day4/2.py', '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10e5f3c88>}
print(vars(time))
View Code
__import__:
以字符串形式导入模块
import time
m=__import__('time') #以字符串的形式导入模块
zip()
将对象逐一配对,产生的是迭代器,for循环的等后就取完了
s='helloo'
l=[1,2,3,4,5]
z=zip(s,l)
print(z)
for i in z:
print(i)
('h', 1)
('e', 2)
('l', 3)
('l', 4)
('o', 5)