欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python学习笔记之——列表, 元组

程序员文章站 2022-05-01 22:41:01
...

目录

列表

 

列表推导

生成器表达式

元组

不可变的元组如果含有可变对象呢?


 

列表

 

列表推导

 

列表推导,创建列表的快捷方式, list comprehension , listcomps

生成器表达式( generator expression) 则称为 genexps

 

for 循环

>>> symbols = '$¢£¥€¤'
>>> codes = []
>>> for symbol in symbols:
   ...     codes.append(ord(symbol))
   ...     
>>> codes
[36, 194, 162, 194, 163, 194, 165, 226, 130, 172, 194, 164]
>>> symbol
'\xa4'

 

列表推导

>>> codes = [ord(symbol) for symbol in symbols]
>>> codes 
[36, 194, 162, 194, 163, 194, 165, 226, 130, 172, 194, 164]
>>>  symbol
'\xa4'

 

列表推导滥用

links = [
            {
                "url": url,
                "method": "GET",
                "postdata": '',
                "header": {
                    "Cookie": cookies
                }
            }
            for url in urls if url not in filtered_urls
        ]

 

通常原则: 只用列表推导建立新的列表,并且尽量保持简短。

 

变量泄漏

for循环进行的时候,如果有变量名与for中临时变量名相同,for循环执行的时候会覆盖原有值。

python2 存在, python3 已解决

# python2
>>> word = "hello"
>>> words = [word for word in "Hello World!"]
>>> words
['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!']
>>> word
'!'


# python3
>>> word = "hello"
>>> words = [word for word in "Hello World!"]
>>> words
['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!']
>>> word
'hello'

 

列表推导快于for循环 https://github.com/fluentpython/example-code/blob/master/02-array-seq/listcomp_speed.py

 

列表推导与笛卡尔积

背景:假如需要一个列表,列表里是3种不同尺寸的T恤,每个尺寸有2个颜色(列表推导+循环嵌套):

>>> colors = ["black", "white"]
>>> sizes = ["S", "M", "L"]
# 先以颜色排序,后以尺寸排序
>>> tshirts = [(color, size) for color in colors for size in sizes]
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]

# 先以尺寸排序,后以颜色排序
>>> tshirts = [(size, color) for size in sizes for color in colors]
>>> tshirts
[('S', 'black'), ('S', 'white'), ('M', 'black'), ('M', 'white'), ('L', 'black'), ('L', 'white')]

 

生成器表达式

与列表推导一次性生成整个列表不同,生成器表达式可以逐个地产出元素,相对节省内存;

>>> symbols = '$¢£¥€¤'

# 如果生成器表达式是一个函数调用过程中为唯一参数,则不需要额外括号
>>> tuple(ord(symbol) for symbol in symbols)
(36, 162, 163, 165, 8364, 164)

>>> import array
# 两个参数,需要括号
>>> array.array("I", (ord(symbol) for symbol in symbols))
array('I', [36, 162, 163, 165, 8364, 164])

使用生成器表达式计算笛卡尔积

生成器表达式只有在迭代时才会生成元素, 如在for循环中,只有for循环执行时才会生成元素;

未迭代时,只是一个生成器对象,不会生成元素;

>>> colors = ["black", "white"]
>>> sizes = ["S", "M", "L"]

# 生成器表达式会在每次for循环运行时才生成组合
>>> for tshirt in ((color, size) for color in colors for size in sizes):
...     print(tshirt)
... 
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')

# 如果不迭代,就是一个生成器对象
>>> ((color, size) for color in colors for size in sizes)
<generator object <genexpr> at 0x10fbdab40>



 

生成器表达式 VS 生成器函数

顾名思义,一个是表达式,一个是函数,殊途同归,都是产生了一个生成器对象。

>>> def gen():
...     for size in sizes:
...         for color in colors:
...             yield (size, color)
... 

# 生成器函数本身就是一个函数
>>> gen
<function gen at 0x10fbd1c08>

# 生成器函数返回一个生成器
>>> gen()
<generator object gen at 0x10fbdab40>


# 迭代生成器产出元素
>>> for item in gen():
...     print(item)
... 
('S', 'black')
('S', 'white')
('M', 'black')
('M', 'white')
('L', 'black')
('L', 'white')

 

元组

元组不仅仅是不可变列表

除了用作不可变列表之外,还可以用于没有字段名的记录。  有字段名的记录——collections.nametuple。

元组其实是对数据的记录:元组中的每个元素都包含了位置数据共两个信息。

排序后的元组也就丢失了位置信息,失去了数据的意义。

>>> request = ("GET", "https://www.baidu.com", headers, data)
>>> request2 = ("POST", "http://example.com", headers, data)

# 位置和值是元组的主要信息
>>> method, url, header, data = request 
>>> print("method: %s, url: %s" % (method, url))
method: GET, url: https://www.baidu.com

# 排序后失去原有记录意义
>>> sorted(request)
['', '', 'GET', 'https://www.baidu.com']

 

元组拆包

元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是被可迭代对象中的元素个数必须要跟接受这些元素的元组的空档数一致。 除非用星号* 标识多余元素(python3)。

示例

# 字符串格式化, 元组拆包
>>> print("name: %s, value: %s" % ("country", "china"))
name: country, value: china

# 平行赋值
>>> a, b = "a", "b"
>>> a
'a'
>>> b
'b'
>>> a, b
('a', 'b')

# 交换值
>>> a, b = b, a
>>> a, b
('b', 'a')


# 用占位符 _ 填充位置
>>> import os
>>> _, filename = os.path.split("/usr/bin/passwd")
>>> filename
'passwd'


# python3

>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)

 

用*处理剩下的元素

在Python中,函数用*args来获取不确定数量的参数算是一种经典写法了。

Python3中,这个概念被用于平行赋值中:

>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])

在平行赋值中, *前缀只能用在一个变量前面,但是可以放在赋值表达式中的任意位置:

# 只能出现一次
>>> a, *body, c *rest = range(5)
  File "<stdin>", line 1
SyntaxError: can't assign to operator

# 其余任意位置
>>> a, *body, c = range(5)
>>> a, body, c
(0, [1, 2, 3], 4)
>>> *head, c, d, e = range(5)
>>> head, c, d, e
([0, 1], 2, 3, 4)

嵌套元组拆包

接受表达式的元组可以是嵌套式的, 例如 (a, b, (c, d))。 只要这个接受元组的嵌套结构符合表达式本身的嵌套结构, Python 就可以作出正确的响应。 

>>> info = ("Xin yuan shuai", 34, "male", (1984, 1, 8))

>>> name, age, gender,  (year, month, date) = info
>>> name, age, gender, year, month, date
('Xin yuan shuai', 34, 'male', 1984, 1, 8)

# 表达式左右格式必须是对应的
>>> name, age, gender,  year, month, date = info
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 6, got 4)

 

具名元组

collections.namedtuple 是一个工厂函数, 它可以用来构建一个带字段名的元组和一个有名字的类——这个带名字的类对调试程序有很大帮助。
用 namedtuple 构建的类的实例所消耗的内存跟元组是一样的, 因为字段名都被存在对应的类里面。 这个实例跟普通的对象实例比起来也要小一些, 因为 Python 不会用 __dict__ 来存放这些实例的属性。

>>> import collections

# 创建一个具名元组需要两个参数, 一个是类名, 另一个是类的各个字段的名字。 
# 后者可以是由数个字符串组成的可迭代对象, 或者是由空格分隔开的字段名组成的字符串。
>>> Student = collections.namedtuple("Student", ["name", "age", "gender", "birth", "address"])


# 存放在对应字段里的数据要以一串参数的形式传入到构造函数中
# 注意, 元组的构造函数却只接受单一的可迭代对象
>>> jack = Student("Jack", 22, "male", 1996, ("Beijing", "chaoyang"))
>>> jack
Student(name='Jack', age=22, gender='male', birth=1996, address=('Beijing', 'chaoyang'))

# 可以通过字段名或者位置来获取一个字段的信息
>>> jack.name
'Jack'
>>> jack.address
('Beijing', 'chaoyang')
>>> jack[4][1]
'chaoyang'

 

除了从普通元组那里继承来的属性之外, 具名元组还有一些自己专有的属性。 如: _fields 类属性、 类方法_make(iterable) 和实例方法 _asdict()

>>> Address = collections.namedtuple("Address", ("city area"))
>>> lucy_addr = Address("Beijing", "Haidian")
>>> lucy_data = ("Lucy", 23, "female", 1998, lucy_addr)
>>> lucy = Student._make(lucy_data)
>>> lucy._asdict()
OrderedDict([('name', 'Lucy'), ('age', 23), ('gender', 'female'), ('birth', 1998), ('address', Address(city='Beijing', area='Haidian'))])
>>> lucy
student(name='Lucy', age=23, gender='female', birth=1998, address=Address(city='Beijing', area='Haidian'))
>>> for key, value in lucy._asdict().items():
...     print key + ":", value
... 
name: Lucy
age: 23
gender: female
birth: 1998
address: Address(city='Beijing', area='Haidian')

>>> lucy = Student(*lucy_data)
>>> lucy
student(name='Lucy', age=23, gender='female', birth=1998, address=Address(city='Beijing', area='Haidian'))

 

  • _fields 属性是一个包含这个类所有字段名称的元组。
  • 用 _make() 通过接受一个可迭代对象来生成这个类的一个实例, 它的作用跟 Student(*lucy_data) 是一样的。
  • _asdict() 把具名元组以 collections.OrderedDict 的形式返回, 我们可以利用它来把元组里的信息友好地呈现出来。

 

不可变的元组如果含有可变对象呢?

《流畅的Python》 里有个示例,如下

>>> t = (1, 2, [3, 4])
>>> id(t), id(t[2])
(4456817920, 4457204840)
>>> t[2] += [5, 6]

猜猜 t 会不会变化? 请自行尝试查看

# 会不会变化?
>>> t
>>> id(t), id(t[2])

为什么呢?

# 二者等价
>>> t[2] += [5, 6]
>>> t[2] = t[2] + [5, 6]

两个表达式是等价的,两个列表相加不是在前一个列表的基础之上原地相加而是生成了一个新的列表.

等式右边是两个列表相加,生成了一个新的列表,也就是新的列表的引用,赋值给了t[2], 而元组是不可变的,于是就既发出了异常,又增加成功。先计算增加,成功了,再计算赋值操作,失败。

如果元组里有可变对象,在更新变对象时建议使用自增运算

>>> t[2].append(7)

Python可视化  http://www.pythontutor.com/visualize.html

STEP 1

Python学习笔记之——列表, 元组

STEP2

 

Python学习笔记之——列表, 元组

 

Python学习笔记之——列表, 元组

 

不要把可变对象放在元组里面。
增量赋值不是一个原子操作。 我们刚才也看到了, 它虽然抛出了异常, 但还是完成了操作。

 

列表滥用——当列表不是首选时

tuple, nametuple, dqueue, set

 

 

 

相关标签: Python列表