Python基础之容易忘记的地方
(1)编译型与解释型语言区别:
编译型:一次性,把所有代码编译成机器能识别的二进制码,再运行
代表语言:c,c++
优点: 执行速度块
缺点: 开发速度慢,调试周期长
解释型:代码从上到下一行一行解释并运行
代表语言:python,php
优点: 开发效率快,调试周期短
缺点: 执行速度相对慢
注释: 就是对代码的解释 方便大家阅读python代码
(2)python六大标准数据类型:
#### 一、数据类型分类:
(1)Number 数字类型 (int float bool complex)
(2)str 字符串类型
(3)list 列表类型
(4)tuple 元组类型
(5)set 集合类型
(6)dict 字典类型
#### 二、Number类型分类:
int : 整数类型 ( 正整数 0 负整数 )
float: 浮点数类型 ( 1普通小数 2科学计数法表示的小数 例:a = 3e-5 #3e-05 )
bool: 布尔值类型 ( 真True 和 假False )
complex: 复数类型 ( 声明复数的2种方法 ) (复数用作于科学计算中,表示高精度的数据,科学家会使用)
(3)容器类型分类:五个
str "nihao"
list [1,2,3]
tuple (6,7,8)
set {'a',1,2}
dict {'a':1,'b':2}
(4)字典和集合的注意点
可哈希的数据(不可变的数据):Number(int float bool complex) str tuple
不可哈希的数据(可变的数据): list set dict
字典的KEY只能用 可哈希的数据
集合中数据只能 可哈希的数据
(5)数据的在内存中的缓存机制(Python 3.8.5)
无论是变量缓存机制还是小数据池的驻留机制,都是为了节省内存空间,提升代码效率
# 在同一文件(模块)里,变量存储的缓存机制
#Number 部分
值相同下 内存地址指向同一个位置
# 容器类型部分
1.字符串 和 空元组 相同的情况下,地址相同
2.列表,元组,字典,集合无论什么情况 id标识都不同 [空元组例外]
# 不同文件(模块)里,部分数据驻留小数据池中
小数据池只针对:int,str,bool,空元组(),None关键字这些数据类型有效
在值相等下,指向同一块地址空间
(6)Number 类型的强制转换 (int float complex bool)
# int强制把数据变成整型
# 这里需要注意的是 '12.0' 浮点数的字符串不能转换成 int
int float bool 纯数字字符串 -> int
# float 强制把数据变成小数
int float bool 纯数字字符串和浮点数字符串 ->float
#complex 强制把数据变成复数
int float bool 纯数字字符串和浮点数字符串 complex -> complex
# bool 强制把数据变成布尔型 (布尔型为假的十中情况)
布尔型可以强转一切数据类型
而其中""" 0 , 0.0 , False , 0j '' [] () set() {} None """ -> False
# 初始化变量时,不清楚用什么值,无脑写上None
None 代表空的,代表什么也没有,一般用于初始化变量
# 默认转换成当前数据类型的一个值
int()->0; float()->0.0; complex()->0j; bool()->False
(7)Number 自动类型转换 (int float complex bool)
# 低精度默认向高精度进行转换
bool -> int -> float -> complex
res = 5.66 + 9.1 -90j => 5.66 + 0j + 9.1 -90j => 14.76-90j
# 为什么浮点计算不准确?
1.2 - 1.0 -> 0.19999999999999996
这与底层平台如何处理浮点数字关系更大。
CPython 中的 float 类型使用C语言的 double 类型进行存储。
float 对象的值是以固定的精度(通常为 53 位)存储的二进制浮点数,
由于 Python 使用 C 操作,而后者依赖于处理器中的硬件实现来执行浮点运算。
典型的 53 位精度为 Python 浮点数提供了 15-16 位小数的精度。
Python浮点算数的详细解释浮点算术
(8)容器类型的强制转换(str list tuple set dict)
# str 强制转换成字符串
所有的数据类型都可以转换, 在当前的数据类型两边套上引号
repr() 原型化输出字符串
# list 强制转换成列表
如果是字符串:把字符串中的每个元素单独拿出来,作为列表中的新元素
如果是字典: 字典中的键作为列表中的新元素
如果是其他容器数据:就是单纯的在原数据类型的两边换上[]括号
# tuple 强制转换成元组
如果是字符串:把字符串中的每个元素单独拿出来,作为元组中的新元素
如果是字典: 字典中的键作为元组中的新元素
如果是其他容器数据:就是单纯的在原数据类型得两边换上()括号
# set 强制转换成集合
如果是字符串:把字符串中的每个元素单独拿出来,作为集合中的新元素
如果是字典: 字典中的键作为集合中的新元素
如果是其他容器数据:就是单纯的在原数据类型得两边换上{}括号
# dict 字典类型的强制转换
要求: 必须是等长的二级容器,并且里面的元素个数是2个;
外层是列表,元组,集合, 里层是列表或者元组的等长二级容器 => 字典;
注意:集合是不可以嵌套集合的,见(4)。 里层如果放集合,那么字典的建值会不稳定。
{ ("a",1) , ("b",2) } -> {'b': 2, 'a': 1}
# 注意:字符串也算是一个容器
['a1','b2']-> {'a': 1, 'b': 2}
# 默认不加任何值,转换成该数据类型的空值
str()-> ''; list()->[];
tuple()->(); set()->set(); dict()->{}
(9)Python运算符
成员运算符: in 和 not in (针对容器)
身份运算符: is 和 is not (检测两个数据在内存当中是否是同一个值)
逻辑运算符: not、and、or (后两个为短路运算符)
# 短路运算
False or print('打印了111')
True and print('打印了222')
res = 5 and 6 # 6
res = 5 or 6 # 5
# 注意这里容易出错的地方 %
>>> 11 % 4
3
>>> -11 % -4
-3
>>> -11 % 4 -> -3 + 4
1
>>> 11 % -4 -> 3 - 4
-1
# 注意 ~ 按位取反运算符(针对于补码进行操作)
~1 = -2
----
原码: 0001
反码: 0001
补码: 0001
对补码进行 按位取反
补码: 0001
按位取反: 1110
补码转原码
补码: 1110
反码: 1001
原码: 1010
---
---
~-1 = 0
原码: 1001
反码: 1110
补码: 1111
对补码进行 按位取反
补码: 1111
按位取反:0000
补码转原码
补码: 0000
反码: 0000
原码: 0000
----
位运算符 | 解释说明 |
---|---|
& | 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 |
| | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1 |
~ | 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1 |
^ | 按位异或运算符:当两对应的二进位相异时,结果为1 |
<< | 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0 |
>> | 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数 |
(10) 字符串的格式化format
1. 索引传参
>>> '{1}和{0}去看星星了'.format('张三', '李四')
'李四和张三去看星星了'
2.关键字传参
'{name1}和{name2}去看星星了'.format(name1='李四', name2='张三')
'李四和张三去看星星了'
3.容器类型数据(列表或元祖)传参
>>> '{0[1]}和{1[0]}去看星星了'.format(['张三', '李四'],('王二麻子', '李雷雷'))
'李四和王二麻子去看星星了'
# 这里的索引不能使用 负数索引 取值
>>> '{group1[1]}和{group2[0]}去看星星了'.format(group1=['张三', '李四'], group2=('王二麻子', '李雷雷'))
'李四和王二麻子去看星星了'
>>> '{group1[zs]}和{group1[ls]}去看星星了'.format(group1={'zs':'张三', 'ls':'李四'})
'张三和李四去看星星了'
4.format的填充符号的使用( ^ > < )
"""
^ 原字符居中显示
> 原字符居右显示
< 原字符居左显示
"""
# *填充的符号
>>> '{group:*^10}三儿'.format(group='张')
'****张*****三儿'
# :f默认保留6位小数,存在四舍五入
# :, 金钱占位符,就是每隔3位加一个 ,
5.进制转换等特殊符号的使用( :d :f :s :, )
转义字符 | 说明 |
---|---|
\n | 换行符,将光标位置移到下一行开头。 |
\r | 回车符,将光标位置移到本行开头。如果接着输出的话,本行以前的内容会被逐一覆盖 |
\t | 水平制表符,也即 Tab 键,一般相当于四个空格。 |
\a | 蜂鸣器响铃。注意不是喇叭发声,现在的计算机很多都不带蜂鸣器了,所以响铃不一定有效。 |
\b | 退格(Backspace),将光标位置移到前一列。 |
\ | 反斜线 |
' | 单引号 |
" | 双引号 |
\ | 在字符串行尾的续行符,即一行未完,转到下一行继续写。 |
(11) 函数的帮助文档
doc 与 help()
>>> def my_sum(start:int, end:int) -> int:
... """
... :description: 此函数用于求一段连续数字的和
... :param start: 开始数
... :param end: 最后一个数
... :return: 它们的和
... """
... res = 0
... for i in range(start, end+1):
... res += i
... return res
...
>>> my_sum.__doc__
'\n :description: 此函数用于求一段连续数字的和\n :param start: 开始数\n :param end: 最后一个数\n :return: 它们的和\n '
>>>help(my_sum)
Help on function my_sum in module __main__:
my_sum(start: int, end: int) -> int
:description: 此函数用于求一段连续数字的和
:param start: 开始数
:param end: 最后一个数
:return: 它们的和
(12)函数命名空间
命名空间 创建顺序:(了解)
python解释器启动->创建内建命名空间->创建全局命名空间->创建局部命名空间
命名空间 销毁顺序:(了解)
函数调用结束后->销毁函数对应的局部命名空间数据->销毁全局命名空间数据->销毁内建命名空间数据
-- global :关键字:声明全局变量获修改全局变量
-- nonlocal :关键字:修改局部变量(当前函数上一层的局部变量)
LEGB原则(即就近原则找变量)
#找寻变量的调用顺序采用LEGB原则(即就近原则)
B —— Builtin(Python);Python内置模块的命名空间 (内建作用域)
G —— Global(module); 函数外部所在的命名空间 (全局作用域)
E —— Enclosing function locals;外部嵌套函数的作用域(嵌套作用域)
L —— Local(function);当前函数内的作用域 (局部作用域)
依据就近原则,从下往上 从里向外 依次寻找
(13)闭包函数
闭包的定义:
内函数使用外函数的局部变量,并且外函数将内函数返回出来的方法叫闭包,
返回的内函数叫 闭包函数.
>>> def x(n1):
... def y(n2):
... return n1 * n2
... return y
...
>>> f = x(3)
>>> res = f(2)
>>> print(res)
6
这里的函数y就叫闭包函数
闭包的特点:
内函数使用了外函数的局部变量,外函数的局部变量与内函数发生绑定,延长该变量的生命周期
(实际内存给它存储了这个值,暂时不释放)
闭包的意义:
闭包可以优先使用外函数中的变量,并对闭包中的值起到了封装保护的作用.外部无法访问
获取闭包函数使用的变量 __closure__ , cell_contents
def x():
n = 100
def y():
nonlocal n
n -= 50
return n
return y
func = x()
# __closure__ 属性定义的是一个包含 cell 对象的元组,
# 其中元组中的每一个 cell 对象用来保存作用域中变量的值
print(func.__closure__) # (<cell at 0x7ff1077df640: int object at 0x955a80>,)
# cell 对象的cell_contents 属性就是闭包中的自由变量。
# 局部变量脱离函数之后,还可以在函数之外被访问的原因的就是它已经存储在了闭包的cell_contents中了
print(func.__closure__[0].cell_contents) # 100
print(func()) # 50
(14)lambda表达式
在Python中,lambda的语法是唯一的。其形式如下:
lambda argument_list: expression
# 1.无参的lambda表达式
func = lambda : 'Eden'
print(func())
# 2.有参的lambda表达式
a = list(range(1,15))
func = lambda args: (i for i in args if i%2==0)
print(list(func(a)))
# 3.带有条件判断参的lambda表达式
func = lambda x,y: x if x>y else y
print(func(2, 8))
map(lambda x: x ** 2, [1, 2, 3, 4, 5]) # 使用 lambda 匿名函数
# [1, 4, 9, 16, 25]
# 提供了两个列表,对相同位置的列表数据进行相加
map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
# [3, 7, 11, 15, 19]
(15) globals与locals函数
-- globals() :返回字典,存放着全局作用域所有内容
#------------------------------
def func():
dic = globals()
for i in range(4):
dic['a{}'.format(i)] = i
func()
print(a0,a1,a2,a3)
#------------------------------
-- locals() :返回字典,当前作用域所有内容(locals调用之前的变量)
(16)迭代器
迭代器与可迭代对象的区别:
1) 可迭代对象包含迭代器。
2)如果一个对象拥有__iter__方法,其是可迭代对象;如果一个对象拥有next方法,其是迭代器。
3)定义可迭代对象,必须实现__iter__方法;定义迭代器,必须实现__iter__和next方法。
Python 支持在容器中进行迭代的概念。
这是通过使用两个单独方法来实现的;它们被用于允许用户自定义类对迭代的支持。
# 容器对象要提供迭代支持,必须定义一个方法:
container.__iter__()
# 返回一个迭代器对象。 该对象需要支持下文所述的迭代器协议。
# 如果容器支持不同的迭代类型,则可以提供额外的方法来专门地请求不同迭代类型的迭代器。
迭代器对象自身需要支持以下两个方法,它们共同组成了 迭代器协议:
iterator.__iter__()
返回迭代器对象本身。 这是同时允许容器和迭代器配合 for 和 in 语句使用所必须的
iterator.__next__()
从容器中返回下一项。 如果已经没有项可返回,则会引发 StopIteration 异常
Python 定义了几种迭代器对象以支持对一般和特定序列类型、字典和其他更特别的形式进行迭代。
除了迭代器协议的实现,特定类型的其他性质对迭代操作来说都不重要
一旦迭代器的 __next__() 方法引发了 StopIteration,它必须一直对后续调用引发同样的异常。
不遵循此行为特性的实现将无法正常使用
# 创建一个迭代器
int_vars = [1, 2, 3, 4]
int_vars_iter = iter(int_vars)
# 使用迭代器
while True:
try:
print(next(int_vars_iter))
except StopIteration:
break
#--------------------
# 创建一个迭代的对象
class My_Itera:
def __init__(self, start, stop):
self.start = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
while self.start<self.stop:
self.start += 1
return self.start
else:
raise StopIteration
# 使用 collections Iterable判断是否为可迭代对象
# Iterator判断是否为可迭代器
from collections import Iterable, Iterator
a = My_Itera(1, 5)
print(isinstance(a, Iterable))
(17)生成器
yield 表达式在定义 generator 函数或是 asynchronous generator(异步生成器) 的时候才会用到。 因此只能在函数定义的内部使用yield表达式。
在一个函数体内使用 yield 表达式会使这个函数变成一个生成器,并且在一个 async def 定义的函数体内使用 yield 表达式会让协程函数变成异步的生成
def gen(): # defines a generator function
yield 123
async def agen(): # defines an asynchronous generator function
yield 123
由于它们会对外层作用域造成附带影响,yield 表达式不被允许作为用于实现推导式和生成器表达式的隐式定义作用域的一部分。
在 3.8 版更改: 禁止在实现推导式和生成器表达式的隐式嵌套作用域中使用 yield 表达式。
生成器表达式:
生成器表达式是用圆括号括起来的紧凑形式生成器标注
生成器函数:
当一个生成器函数被调用的时候,它返回一个迭代器,称为生成器。然后这个生成器来控制生成器函数的执行。
当这个生成器的某一个方法被调用的时候,生成器函数开始执行。
这时会一直执行到第一个 yield 表达式,在此执行再次被挂起,给生成器的调用者返回 expression_list 的值。
挂起后,我们说所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。
通过调用生成器的某一个方法,生成器函数继续执行。此时函数的运行就和 yield 表达式只是一个外部函数调用的情况完全一致。
恢复后 yield 表达式的值取决于调用的哪个方法来恢复执行。
如果用的是 __next__() (通常通过语言内置的 for 或是 next() 来调用) 那么结果就是 None.
否则,如果用 send(), 那么结果就是传递给send方法的值。
所有这些使生成器函数与协程非常相似;它们 yield 多次,它们具有多个入口点,并且它们的执行可以被挂起。
唯一的区别是生成器函数不能控制在它在 yield 后交给哪里继续执行;控制权总是转移到生成器的调用者。
在 try 结构中的任何位置都允许yield表达式。
如果生成器在(因为引用计数到零或是因为被垃圾回收)销毁之前没有恢复执行,将调用生成器-迭代器的 close() 方法. close 方法允许任何挂起的 finally 子句执行。
当使用 yield from 时,它会将所提供的表达式视为一个子迭代器。这个子迭代器产生的所有值都直接被传递给当前生成器方法的调用者。
通过 send() 传入的任何值以及通过 throw() 传入的任何异常如果有适当的方法则会被传给下层迭代器。
如果不是这种情况,那么 send() 将引发 AttributeError 或 TypeError,而 throw() 将立即引发所传入的异常
当下层迭代器完成时,被引发的 StopIteration 实例的 value 属性会成为 yield 表达式的值。
它可以在引发 StopIteration 时被显式地设置,也可以在子迭代器是一个生成器时自动地设置(通过从子生成器返回一个值)
生成器-迭代器的方法
请注意在生成器已经在执行时调用以下任何方法都会引发 ValueError 异常
generator.__next__()
开始一个生成器函数的执行或是从上次执行的 yield 表达式位置恢复执行。
当一个生成器函数通过 __next__() 方法恢复执行时,当前的 yield 表达式总是取值为 None。
随后会继续执行到下一个 yield 表达式,其 expression_list 的值会返回给 __next__() 的调用者。
如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。
此方法通常是隐式地调用,例如通过 for 循环或是内置的 next() 函数。
generator.send(value)
恢复执行并向生成器函数“发送”一个值。 value 参数将成为当前 yield 表达式的结果。
send() 方法会返回生成器所产生的下一个值,或者如果生成器没有产生下一个值就退出则会引发 StopIteration。
当调用 send() 来启动生成器时,它必须以 None 作为调用参数,因为这时没有可以接收值的 yield 表达式。
generator.throw(type[, value[, traceback]])
在生成器暂停的位置引发 type 类型的异常,并返回该生成器函数所产生的下一个值。
如果生成器没有产生下一个值就退出,则将引发 StopIteration 异常。
如果生成器函数没有捕获传入的异常,或引发了另一个异常,则该异常会被传播给调用者。
generator.close()
在生成器函数暂停的位置引发 GeneratorExit。
如果之后生成器函数正常退出、关闭或引发 GeneratorExit (由于未捕获该异常) 则关闭并返回其调用者。
如果生成器产生了一个值,关闭会引发 RuntimeError。 如果生成器引发任何其他异常,它会被传播给调用者。 如果生成器已经由于异常或正常退出则 close() 不会做任何事
>>> def echo(value=None):
... print("Execution starts when 'next()' is called for the first time.")
... try:
... while True:
... try:
... value = (yield value)
... except Exception as e:
... value = e
... finally:
... print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
Don't forget to clean up when 'close()' is called.
>>>
>>> next(generator)
Execution starts when 'next()' is called for the first time.
1
>>> next(generator) # return None
>>>
>>> generator.send(2)
2
>>> generator.throw(TypeError, "spam")
TypeError('spam')
>>> generator.close()
Don't forget to clean up when 'close()' is called.
Python基础之容易忘记的地方的更多相关文章
- 进击的Python【第三章】:Python基础(三)
Python基础(三) 本章内容 集合的概念与操作 文件的操作 函数的特点与用法 参数与局部变量 return返回值的概念 递归的基本含义 函数式编程介绍 高阶函数的概念 一.集合的概念与操作 集合( ...
- 『Python基础-9』元祖 (tuple)
『Python基础-9』元祖 (tuple) 目录: 元祖的基本概念 创建元祖 将列表转化为元组 查询元组 更新元组 删除元组 1. 元祖的基本概念 元祖可以理解为,不可变的列表 元祖使用小括号括起所 ...
- 『Python基础-4』字符串
# 『Python基础-4』字符串 目录 1.什么是字符串 2.修改字符串 2.1 修改字符串大小 2.2 合并(拼接)字符串 2.3 使用乘号'*'来实现字符串的叠加效果. 2.4 在字符串中添加空 ...
- 开发技术--浅谈python基础知识
开发|浅谈python基础知识 最近复习一些基础内容,故将Python的基础进行了总结.注意:这篇文章只列出来我觉得重点,并且需要记忆的知识. 前言 目前所有的文章思想格式都是:知识+情感. 知识:对 ...
- python基础全部知识点整理,超级全(20万字+)
目录 Python编程语言简介 https://www.cnblogs.com/hany-postq473111315/p/12256134.html Python环境搭建及中文编码 https:// ...
- Python基础(下篇)
本篇文章主要内容:异常处理,函数,模块和包. 在开始正篇之前我们先来看看上一篇可乐留下的题目. 题目: 变量 a= {"name": "可乐", "a ...
- Python之路3【第一篇】Python基础
本节内容 Python简介 Python安装 第一个Python程序 编程语言的分类 Python简介 1.Python的由来 python的创始人为吉多·范罗苏姆(Guido van Rossum) ...
- python基础之文件读写
python基础之文件读写 本节内容 os模块中文件以及目录的一些方法 文件的操作 目录的操作 1.os模块中文件以及目录的一些方法 python操作文件以及目录可以使用os模块的一些方法如下: 得到 ...
- python基础知识理解
一.概述 看了一天的python基础语法,基本对python语法有了一个大概的了解(其实之前断断续续也看过python),学习网址:Python 基础教程.因为之前我学过C++,因此在学习python ...
随机推荐
- HTTPS:网络安全攻坚战
本文为<三万长文50+趣图带你领悟web编程的内功心法>第五个章节. 5.HTTPS 我们知道,明文传输和不安全是HTTP的其中一个特点,但是随着越来越多机密的业务交易转移到线上,如银行转 ...
- Go的switch
目录 go的switch 一.语法 二.默认情况 三.多表达式判断 四.无表达式 五.Fallthrough go的switch switch 是一个条件语句,用于多条件匹配,可以替换多个if els ...
- vue关于导航守卫的几种应用场景
beforeEach 该钩子函数主要用来做权限的管理认证 router.beforeEach((to, from, next) => { if (to.matched.some(record = ...
- SSRF攻击原理
目录 什么是SSRF 原理 防护 什么是SSRF 一个对外的Web接口,改接口能让用户控制curl命令,去访问别的web服务. 简图如下 想象一下当用户请求的baidu.com/x.php?image ...
- SpringBoot利用spring.profiles.active=@spring.active@不同环境下灵活切换配置文件
一.创建配置文件 配置文件结构:这里建三个配置文件,application.yml作为主配置文件配置所有共同的配置:-dev和-local分别配置两种环境下的不同配置内容,如数据库地址等. appli ...
- 《Asp.Net Core3 + Vue3入坑教程》 - Vue 1.使用vue-cli创建vue项目
简介 <Asp.Net Core3 + Vue3入坑教程> 此教程适合新手入门或者前后端分离尝试者.可以根据图文一步一步进操作编码也可以选择直接查看源码.每一篇文章都有对应的源码 目录 & ...
- java将数据生成csv文件
1,httpRequest接口触发进程[或者可以换成其他方式触发] /** * 出入库生成CSV文件 * @param req * @param params * @return */@Request ...
- HTML基础速览
HTML概述 HTML ,CSS , JavaScript, JQuery, Vue 的关系 HTML可以写一个简单的前端,但是很丑,所以需要CSS对HTML进行美化 HTML是静态的.JavaScr ...
- flutter兼论
Flutter是Google开发的一套全新的跨平台.开源UI框架,支持iOS.Android系统开发,并且是未来新操作系统Fuchsia的默认开发套件.自从2017年5月发布 第一个版本以来,目前Fl ...
- Java 操作PPT数字签名(一):添加、检测、删除签名
本文简要概述如何通过Java程序来处理PPT中的数字签名,文章主要分三个部分来介绍,即数字签名的添加.验证以及删除. 基本操作思路: 1. 添加签名:[加载PPT文档]→[添加签名]→[保存文档] 2 ...