Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
803 lines (590 sloc) 25.7 KB

Python基础

目录

数据类型和变量

数据类型

Python可以直接表达的数据类型包括:整数,浮点数,复数,字符串,布尔值和空值。

整数 int

任意大小,正负皆可。并且可以用0b、0o、0x分别表示二进制、八进制和十六进制数:

>>> 0b10 # 也可以是 0B10
2
>>> 0o10 # 也可以是 0O10
8
>>> 0x10 # 也可以是 0X10
16

# 十进制转其他进制
>>> bin(2)
'0b10'
>>> oct(8)
'0o10'
>>> hex(16)
'0x10'

# 其他进制转十进制(也可将字符串转换为十进制)
>>> int(0b10) # 也可以是 int('10', 2)
2
>>> int(0o10) # 也可以是 int('10', 8)
8
>>> int(0x10) # 也可以是 int('10', 16)
16
浮点数

除了普通的写法之外,也可使用科学记数法,即1.23x10^9就是1.23e9 ,0.000012可以写成1.2e-5,等等。

整数运算永远是精确的(结果也是整数),浮点数运算则有四舍五入的误差

Python提供两种除法+求余运算:

符号 用途
/ 结果为浮点数,即使两数整除,结果也是浮点数
// 结果为整数,也称地板除,对结果向下取整
% 结果为整数,用于计算余数
复数

Python中复数用j表示虚数部分,比如说 x=2.4 + 5.6j, 并且可以通过复数的类属性 real 取得实部,类属性 imag 取得虚部,通过类方法 conjugate() 获得共轭复数。

>>> x = 2.4 + 5.6j
>>> x.real
2.4
>>> x.imag
5.6
>>> x.conjugate()
(2.4-5.6j)
字符串

使用单引号或双引号括起。而引号本身需要使用转义符\来表达。 用\'来表示'

转义符还可以用来转义很多字符,如\n表示换行。\t表示制表符。 \本身也需要转义用 双斜杠 来代替。

如果一个字符串中很多字符都要转义就会很麻烦,所以Python又提供一种简便的写法,r''表示两个引号之间的内容是不需要转义的。 对于多行的字符串,为了避免加入\n的不方便,可以使用'''something'''的格式,即用三个引号来括起字符串,换行会被保留

# 没使用三引号
>>> a = '123
  File "<stdin>", line 1
a = '123
   ^
SyntaxError: EOL while scanning string literal

# 使用了三引号
>>> a = '''123
... 456
... 789'''
>>> a
'123\n456\n789'
布尔值

只有True和False两种,首字母大写。如果输入一个判断式,则Python会给出布尔值的结果。比方说输入3>2,运行后会得到True。 对于布尔值有三个逻辑运算符,分别是and,or和not。 本质上True用1存储,False用0存储

>>> 1 == 1
True
>>> 1 == True
True
>>> 0 != 0
False
>>> 0 == False
True
空值

Python中用None表示,不同于数值0。数值0是有意义的,而None是一个特殊的空值。可以将None赋值给变量,但无法创建None以外的其他NoneType对象。

>>> type(None)
<class 'NoneType'>
>>> a = None
>>> type(a)
<class 'NoneType'>
>>> None == a
True
>>> None == ''
False
>>> None == 0
False

变量

Python是一门动态类型语言,这意味着在Python中,可以反复把任意数据类型的对象赋值给同一个变量,相比起静态语言更加地灵活。

在Python中变量名不可以以数字开头,构成可以包括大小写英文,数字及下划线。

当我们写:a=’ABC’ 时,Python解释器干了两件事情:

  1. 在内存中创建了一个值为'ABC'的字符串对象;
  2. 在内存中创建了一个名为a的变量,并把它指向对象'ABC'

常量

Python中没有常量,因为变量都可以重复赋值。但是一般约定用全部字母大写的单词来表示一个常量,如:PI=3.14。



使用list和tuple

list

list是一种Python内置的数据类型,表示有序集合,可动态删除和插入。通过索引可以访问列表元素,索引从0开始,即访问第一个列表元素。并且列表是循环的,可以通过索引-1访问最尾的元素,索引-2访问倒数第二个元素。例如:

>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
>>> classmates[0]
'Michael'
>>> classmates[-1]
'Tracy'
>>> classmates[-2]
'Bob'

另外,还可以用 len() 函数获取列表的元素个数。

list 是一个可变的有序表,所以,可以往 list 中追加元素到末尾:

>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

也可以把元素插入到指定的位置,比如索引号为 1 的位置:

>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

要删除 list 末尾的元素,用 pop() 方法:

>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

要删除指定位置的元素,用 pop(i) 方法,其中 i 是索引位置:

>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']

要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:

>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list 里面的元素的数据类型可以不同,比如:

>>> L = ['Apple', 123, True]

list 里面的元素也可以是另一个 list,比如:

>>> s = ['python', 'java', ['asp', 'php'], 'scheme'] # 四个元素,其中第三个元素是一个列表
>>> len(s)
4

>>> s[0]
'python'
>>> s[2]
['asp', 'php']
>>> s[2][0]
'asp'
>>> s[2][1]
'php'

要注意s只有4个元素,s[2]作为一个list类型的元素。要拿到'php'可以用 s[2][1],即把s看作一个二维数组,这样的嵌套可以有很多层。

如果一个 list 中一个元素也没有,就是一个空的 list,它的长度为 0:

>>> L = []
>>> len(L)
0

tuple

tuple也是一种有序列表,但tuple一旦初始化就无法再修改,也因为这个特性,所以代码更安全。和list不同,tuple用小括号来括起。例如:

>>> classmates = ('Michael', 'Bob', 'Tracy')

定义空的tuple如下如下:

>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的 tuple,就要注意一下,如果使用t = (1)来定义,则得到的不是一个tuple,而是整数1,因为括号既可以表示tuple又可以表示数学公式中的小括号。这种情况下默认为后者。要定义1个元素的tuple格式如下,使用一个逗号进行区分:

>>> t = (1)
>>> t
1

>>> t = (1,)
>>> t
(1,)

tuple也有 "可变" 的例子,如果tuple的其中一个元素是list,则这个list元素的内容是可以修改的,如下:

>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个例子实际修改的是list而不是tuple,tuple指向的位置不会变,而list指向的位置可变,上面的例子实际上是创建了字符串X和Y,然后让tuple的第三个元素也即list元素指向这两个新的字符串。



使用dict和set

dict

dict即字典,用于存储键值对,查找速度极快。如果使用list来存键值对就需要两个list,要先从key list找出key,再从value list找到对应项的值,因此list越长,耗时越长。用dict实现则可以直接根据key来找value。格式如下:

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

dict速度快是因为Python内部像字典一样建立了索引,字典有部首表,Python内部也会根据不同key算出一个存放的「页码」(哈希算法),所以速度非常快。除了初始化赋值还可以对同一个key进行多次赋值,会覆盖原来的value,如果key不存在就会对dict插入一个新的键值对:

>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d
{'Tracy': 85, 'Michael': 95, 'Bob': 75}

>>> d['Michael'] = 20
>>> d
{'Tracy': 85, 'Michael': 20, 'Bob': 75}

>>> d['Lincoln'] = 100
>>> d
{'Tracy': 85, 'Michael': 20, 'Bob': 75, 'Lincoln': 100}

要判断key是否在dict里面有两种方法:

1.使用in关键字,有则返回True,无则返回False

>>> 'Thomas' in d
False

2.使用dict提供的get方法,有则返回key对应的value,无则返回空值None或者自己指定的值。

>>> d.get('Thomas')
None
>>> d.get('Thomas', -1)
-1

删除一个key则对应的value也会从dict中删除,使用pop方法来实现:

>>> d.pop('Bob')
75
>>> d
{'Tracy': 85, 'Michael': 20, 'Lincoln': 100}

dict的插入和查找速度极快,不会随着key的增加而增加,但需要占用大量的内存,内存浪费多。list则相反,插入和查找时间随元素增加而增加,但占用空间少。所以dict是一种用空间换时间的方法,注意dict的key必须是不可变对象,无法修改key,不然dict就混乱了。字符串和整数等都可以作为key,list无法作为key


###set

set和dict的原理是一样的,同样不可以放入可变对象做key,唯一的区别是set只有key没有value。显然set里面是没有重复的元素的,不然哈希时会出错。set是有序的,需要使用列表/元组做初始化,定义方式如下:

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

>>> s = set((1,2,3))
>>> s
{1, 2, 3}

列表中有重复元素时set会自动被过滤,添加可以使用add方法,如s.add(4)。删除则用remove方法,如s.remove(4)。

集合可以看成数学意义上无序和无重复的集合,可以做交集、并集、差集等操作

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
>>> s1-s2
{1}

再议不可变对象

Python中一切皆对象,而对象又分可变对象和不可变两类对象两类,具体来说,它们的差别就是对象的内容是否可变。不可变对象包括int, float, string, tuple,空值等,可变对象包括list, dict, set等。要注意对象和变量的关系,在Python里,变量都是对对象的引用。举个例子:

>>> a = 'abc'
>>> a[0]
'a'
>>> a[0]='b'  # 字符串对象本身是不可变的
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

# 变量可以指向另一个字符串对象
# 但字符串对象'abc'并没有改变,它依然存在于内存中
>>> a = 'bbc'
>>> a
'bbc'

关于参数传递,可以简单总结为以下两点:

  • 当一个可变对象作为函数参数时,函数内对参数修改会生效。
  • 当一个不可变对象作为函数参数时,函数内对参数修改无效,因为实际上函数是创建一个新的对象并返回而已,没有修改传入的对象。例如:
>>> a = ['c', 'b', 'a']
>>> a.sort()
>> a
['a', 'b', 'c']

list是可变对象,使用函数操作,内容会变化,注意区分一下变量对象,这里a是一个变量,它指向一个列表对象,这个列表对象的内容才是['c', 'b', 'a']。例如:

>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

字符串是不可变对象,所以replace函数并不会修改变量a指向的对象,实际上调用不可变对象的任意方法都不改变该对象自身的内容,只会创建新对象并返回,如果使用一个新的变量来接收返回的对象,就能得到操作结果。不接收则直接打印,原变量指向的对象不会变化

注意,虽然tuple是不可变对象,但是如果使用tuple作为dict或者set的key,还是有可能产生错误,因为tuple允许元素中包含列表,列表内容可变。如果使用了带有列表元素的tuple作为key就会报 TypeError: unhashable type: 'list' 的错误。



条件判断

条件判断

Python中代码块是以缩进作区分的,if-else条件判断注意要在判定条件后写冒号,并且代码块都正确对齐。多个判断条件可以使用多个elif来实现。例如:

age = 20
if age >= 6:
print('teenager')
elif age >= 18:
print('adult')
else:
print('kid')

判断条件并不一定要是一个判断式,可以简写为一个变量,当变量为非零数值,非空字符串,非空list等时,判断为True,否则为False


再议input

有时会采取 input('提示语句') 的方式读取用户输入,作为判定条件。要注意用户输入属于字符串类型,要进行数值比较必须先转换为对应的数据类型,否则会报错。

Python提供int(), float(), str()等方法进行数据类型的转换。

# 输入1表示Yes,输入0表示No
>>> choose = input('If you choose yes, please input 1. Otherwise, input 0: ')
If you choose yes, please input 1. Otherwise, input 0: 0

>>> if choose: # 没有进行转换
... print('Yes')
... else:
... print('No')
...
Yes

>>> if int(choose): # 进行了转换
... print('Yes')
... else:
... print('No')
...
No

循环

Python提供两种循环写法,一种是 for...in... 循环,一种是 while... 循环。for循环依次把list或tuple中的每个元素赋值给目标标识符,格式如下:

>>> names = ['Michael', 'Bob', 'Tracy']
>>> for name in names:
... print(name)
...
Michael
Bob
Tracy

当列表为连续数值时,可以用range方法生成,格式如下:

>>> sum = 0
>>> for x in range(5):
... sum = sum + x
... print(sum)
...
0
1
3
6
10

while循环的写法和if-else很相似,也是在判定条件后面加一个冒号。例如:

>>> sum = 0
>>> n = 11
>>> while n > 0:
... sum = sum + n
... n = n - 2
... print(sum)
...
11
20
27
32
35
36

另外,对于死循环的程序,可以通过Ctrl+C 强制终止。



字符串和编码

字符编码

  • ASCII

最早的编码,包含大小写英文,数字及一些符号。大写A编码为65,小写a编码为97。字符0编码为48。使用8位二进制组合(1个字节),可表示128个不同字符(最高位用于校验)。

  • GB2312

处理中文时一个字节显然不足,至少需要用两个字节,并且不与ASCII冲突,因此有了中国自己制定的GB2312编码,可用于编码中文。

  • Shift_JIS和Euc-kr

日本编码为Shift_JIS,韩文编码为Euc-kr。这样各个国家都用自己不同的标准和编码就很容易在多语言的环境产生冲突,变成乱码。因此又有了通用的一套编码。

  • Unicode

Unicode将所有语言统一到一套编码中,一般使用2个字节,部分非常偏僻的字符会用到4个字节。但是使用Unicode编码,如果在大量英文的环境又非常浪费空间,因为存储和传输时大小是使用ASCII编码的两倍,不划算,于是又有了新的方式。

  • UTF-8

UTF-8是可变长编码,对Unicode字符不同的数字大小编码成1-6个字节,英文字母为1个字节,汉字一般是3个字节。需要用到4-6字节的字符非常少,这样比较节省空间。

Example

字符 ASCII Unicode UTF-8
A 01000001 00000000 01000001 01000001
(超出范围) 01001110 00101101 11100100 10111000 10101101

插播一段课外知识

摘录整理自知乎提问为什么不少网站使用 UTF-8 编码?

为什么要分Unicode和UTF-8,它们两者有什么区别和关联呢?

其实最早的时候,由于各地区使用的编码方式不同,使用不同语言的人交流就成了一个大问题。国际标准组织ISO制订了统一的通用字符集(Universal Character Set,UCS),也简称为Unicode早期的Unicode版本对应于UCS-2编码方式,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示 2^16 也即 65536 个字符,基本满足各种语言的使用。根据维基的说法,当前最新版本的Unicode标准收录了128,000个字符,已经远远超出两字节所能表示的65536了。所以把Unicode视作UCS-2是一种过时的说法,Unicode标准存在多于两字节的字符

Unicode有多种字符编码实现方式。一个Unicode字符的code point是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,会使用不同的字符编码来实现Unicode这些字符编码统称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

例如,对于一个仅包含ASCII字符(只需要7位)的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的首位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以ASCII字符为主的西文文档就大大节省了编码长度。

但是,现在所说的Unicode一般都指的是字符集而非编码方式,廖雪峰老师在教程里提到的Unicode编码实际上指的是UCS-2这种编码方式。又因为以前UCS-2和UTF-16是等价的,所以微软的文档习惯把UTF16称为Unicode,这也导致很多开发者容易对此产生误会。

以上内容虽然有部分针对廖雪峰老师教程中描述不准确的地方进行了修改,但可能还是存在一些让人迷惑的说法,更详细的讲解可以看我的博文字符集与编码的恩怨情仇,以及Python3中的编码问题

其他内容

在计算机存储中,内存统一使用Unicode编码,而硬盘保存或者传输数据就用UTF-8编码。比方说打开记事本时,数据会从硬盘中UTF-8编码格式转换为Unicode格式,保存时则相反。

浏览网页时,服务器端动态生成的内容是Unicode编码,而传输时则会变为传输UTF-8编码格式的网页。所以常常在网页源码中看到 <meta charset="UTF-8" />的语句,表示该网页正是用的UTF-8编码。

按我的理解,可以这样总结:在传输/储存时,为了节省带宽/空间,会使用UTF-8这样的Unicode字符集的实现方式;而在计算机要处理的时候,应当使用原Unicode编码方式,也即每个字符都是固定两字节,这样字符长度统一更便于操作。

特别注意,在涉及到字节流和字符串转换的程序里,我们应始终坚持使用UTF-8编码进行转换,从而避免乱码问题。

另外,代码本身也是文本,所以也有编码相关的问题。在编写Python脚本时,一般会在文件头部加入以下两句注释:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序(这里说得不准确,具体看我第5章05模块的笔记),Windows系统则会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,如果源代码中有中文的话,就会出现乱码了。

注意,这里仅仅是声明了读取时采用UTF-8编码,但文件本身的编码不一定是UTF-8,要确定把代码文件编码为UTF-8格式,我们需要对写代码的IDE/文本编辑器进行相应的设置。廖老师提到,要确保使用UTF-8 without BOM编码,**那么BOM和without BOM有什么区别呢?**暂不在此进行讨论。

关于Python3中编码的信息,可以再看看官方文档


字符串

Python中,字符串类型中的每个字符采用的是两字节的Unicode编码,支持多语言。

ord函数

ord函数可以获取字符的十进制整数表示

>>> ord('')
20013
>>> ord('')
25991

注意只能传入单个字符

chr函数

chr函数可以将十进制整数转换为Unicode编码下对应的字符

>>> chr(20013)
''
>>> chr(25991)
''

此外,如果知道了字符的十进制编码,可以将其转换为十六进制,然后使用\u转义得到对应的Unicode字符

>>> hex(20013)
'0x4e2d'
>>> '\u4e2d'
''
>>> hex(25991)
'0x6587'
>>> '\u6587'
''
>>> '\u4e2d\u6587' # 多个字符
'中文'

补充一下,Unicode编码是多语言的,不仅限于中文和英文,还可以使用多种语言的字符及符号:

In [1]: ord('') # 韩语
Out[1]: 54620
In [2]: hex(54620)
Out[2]: '0xd55c'
In [3]: '\ud55c'
Out[3]: ''

In [4]: ord('') # 日语
Out[4]: 12395
In [5]: hex(12395)
Out[5]: '0x306b'
In [6]: '\u306b'
Out[6]: ''

In [7]: ord('a') # 英语
Out[7]: 97
In [8]: hex(97)
Out[8]: '0x61'
In [9]: '\u0061'
Out[9]: 'a'

In [10]: ord('1') # 数字
Out[10]: 49
In [11]: hex(49)
Out[11]: '0x31'
In [12]: '\u0031'
Out[12]: '1'

In [13]: ord(',') # 符号
Out[13]: 44
In [14]: hex(44)
Out[14]: '0x2c'
In [15]: '\u002c'
Out[15]: ','

从以上代码可以看到,这里的Unicode编码中每个字符都是固定用两字节表示(一个字符用4位16进制数表示,每个16进制数需要4bit,所以一共是4*4 = 16bits = 2bytes)和廖老师教程中所说的若干个字节不一致,是否真的有多于两字节的Unicode字符呢?有待考究。

bytes类型

因为Python程序执行时,字符串类型是用Unicode编码的,一个字符对应若干字节(若干字节还是固定两个字节呢?),要在网络中传输或存储到硬盘中就要把字符串变为bytes类型(因为传输和存储都是以字节为单位的,所以需要进行转换。又因为要节省资源,所以会使用别的编码方式如utf-8)。

Python显示bytes类型的数据会用b作前缀,要注意 b'ABC''ABC' 的差别,尽管内容一样,但前者的每个字符都只占1个字节,而后者在Python中以Unicode进行编码,每个字符占两个字节。也即:

>>> b'\x41\x42\x43'  # bytes类型,每个英文字符占1个字节
b'ABC'
>>> '\u0041\u0042\u0043' # Unicode类型,每个英文字符占2个字节,前8bit用0填充
'ABC'

Unicode字符串可以通过 encode() 方法编码为指定的bytes,如ASCII编码,utf-8编码etc,bytes类型的数据如果字符不属于ASCII码的范围,就用 '\x## 的格式表示。 相应地,如果读取字节流的数据,就要用 decode() 方法解码。例如:

>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'

>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

len函数

对字符串使用len函数会得到字符的数目,而对字节流使用len函数则会得到有多少字节。例如:

>>> len('中文') # 字符串'中文'包含两个字符
2
>>> len('\u4e2d\u6587') # 用4个字节表示字符串'中文'
2
>>> len('中文'.encode('utf-8')) # 字符串'中文'编码为utf-u格式后,占6个字节
6
>>> len(b'\xe4\xb8\xad\xe6\x96\x87') # 用6个字节表示字节流'中文'
6

>>> len('ABC') # 字符串'ABC'包含三个字符
3
>>> len('\u0041\u0042\u0043') # 用6个字节表示字符串'ABC'
3
>>> len('ABC'.encode('ascii')) # 字符串'ABC'编码为ASCII格式后,占3个字符
3
>>> len(b'\x41\x42\x43') # 用3个字节表示字节流'ABC'
3

格式化

和C语言类似,Python中使用百分号 占位符实现格式化的功能。%s表示用字符串替换,%d%f%x分别表示用整数,浮点数和十六进制数替换。其中%s可以将任意数据类型转换为字符串。而%d%f则可以指定整数和小数部分的位数。例如:

# 普通的格式化
>>> '%s %d %f %x' % ('a',1,1.2,0x2)
'a 1 1.200000 2'

# %s可以转换任意数据类型
>>> '%s %s %s %s' % ('a',1,1.2,0x2)
'a 1 1.2 2'

# %d可以控制整数部分的位数,不足的位数默认以空格填充
>>> '%4d' % 5
'   5'

# 除了用空格填充之外,也可以使用0填充不足的位数
>>> '%04d' % 5
'0005'

# %f也可以控制整数和小数部分的位数
>>> '%f - %.2f - %2.2f - %02.2f' % (1.2, 1.2, 1.2, 1.2)
'1.200000 - 1.20 - 1.20 - 1.20'

注意

  • 只有一个占位符时,后面变量不需要用括号括起。
  • 如果字符串本身包含,则需要转义,用%%来表示百分号。例如:
>>> 'growth rate: %d %' % 7 # 没有转义百分号
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: incomplete format

>>> 'growth rate: %d %%' % 7 # 转义了百分号
'growth rate: 7 %'