Skip to content

Latest commit

 

History

History
293 lines (207 loc) · 9.78 KB

File metadata and controls

293 lines (207 loc) · 9.78 KB

1. 一个关于+=的谜题

>>>t=(1, 2, [30 ,40])
>>>t[2] += [50, 60]

到底会发生下面4种情况中的那一种

​ a. t变成(1, 2, [30, 40, 50 ,60])

​ b. 因为tuple不支持对它的元素赋值, 所以会抛出TypeError异常

​ c. 以上俩个都不对

​ d. a和b都是对的

答案是d. a和b都是对的。

tPython 2.7.17 (default, Nov  7 2019, 10:07:09) 
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> t = (1, 2, [30, 40])
>>> t[2] +=[50, 60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])

由此可知

  1. 增量赋值不是一个原子操作。
  2. 不要把可变对象放到元组里面。

2. 数组

  1. 如果只需要一个只包含数字的列表,那么array.array比list更高效。

3. 字典和集合

  1. python 里面所有的不可变类型都是可散列的, 这个说法是不准确的,比如虽然元组本身是不可散列的,但是他里面的元素可能是其他可变类型。
  2. 散列值就是他们id()返回的值。
  3. 字典在内存上的开销特别大。
  4. 字典使用的是散列表,而散列表又必须是稀疏的,这导致在空间上的效率低下。
  5. 用元组取代字典就能节约空间的原因有俩个:其一是避免了散列表所耗费的空间,其二是无需把记录中字段的名字在每个元素李都存一遍。
  6. 字典是实现是典型的空间换取时间:字典类型有着巨大的内存开销,但是他们无视数据量的大小的快速访问---只要字典能被装载到内存中。

4. s[0] == s[:1] 只对str这个序列类型成立

>>> str1 = "swift"
>>> str1[0]==str1[:1]
True
>>> list1 = [1,2,3,4]
>>> list1[1]==list1[:1]
False

5. 字符编码

  1. 需要在多台设备中或多种场合下运行的代码,一定不能依赖默认编码。打开文件时始终应该明确传入encoding=参数,因为不同的设备使用的默认编码可能不同,有时隔一天也会发生变化。
  2. 如果使用字节序列构建正则表达式,\d和\w等模式只能匹配ASCII字符;相比之下,如果是字符串模式,就能匹配ASCII之外的Unicode数字或字母。

6. 函数

  1. 所有的函数都是对象
>>> def factorial(n):
...     '''return n!'''
...     return 1 if n<2 else n*factorial(n-1)
... 
>>> factorial(23)
25852016738884976640000
>>> factorial.__doc__
'return n!'
>>> type(factorial)
<class 'function'>
>>> fact = factorial
>>> fact
<function factorial at 0x7f9267333c80>
>>> factorial
<function factorial at 0x7f9267333c80>
>>> fact(5)
120
>>> map(factorial, range(11))
<map object at 0x7f9266c961d0>
>>> list(map(fact, range(11)))
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
  1. 别的名称使用函数,再把函数作为参数传递

  2. python 也可叫做函数式编程风格

  3. 接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-order function)。map函数就是一例

  4. 在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce和apply。apply函数在Python 2.3中标记为过时,在Python 3中移除了,因为不再需要它了。如果想使用不定量的参数调用函数,可以编写fn(*args, **keywords),不用再编写apply(fn, args, kwargs)。

  5. 函数式语言通常会提供map、filter和reduce三个高阶函数(有时使用不同的名称)。在Python3中,map和filter还是内置函数,但是由于引入了列表推导和生成器表达式,它们变得没那么重要了。列表推导或生成器表达式具有map和filter两个函数的功能,而且更易于阅读。

>>> list(map(fact, range(6)))
[1, 1, 2, 6, 24, 120]
>>> [fact(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> list(map(fact, filter(lambda n: n%2, range(6))))
[1, 6, 120]
>>> [fact(n) for n in range(6) if n%2]
[1, 6, 120]
  1. 在Python 3中,map和filter返回生成器(一种迭代器),因此现在它们的直接替代品是生成器表达式(在Python 2中,这两个函数返回列表,因此最接近的替代品是列表推导)。
  2. 在Python 2中,reduce是内置函数,但是在Python 3中放到functools模块里了。这个函数最常用于求和,自2003年发布的Python 2.3开始,最好使用内置的sum函数。在可读性和性能方面,这是一项重大改善。

7. 匿名函数

  1. lambda关键字在Python表达式内创建匿名函数。
  2. Python简单的句法限制了lambda函数的定义体只能使用纯表达式。换句话说,lambda函数的定义体中不能赋值,也不能使用while和try等Python语句。
  3. 在参数列表中最适合使用匿名函数。
>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'reaspberry', 'banana']
>>> sorted(fruits, key=lambda word: word[::-1])
['banana', 'apple', 'fig', 'reaspberry', 'strawberry', 'cherry']
  1. 除了作为参数传给高阶函数之外,Python很少使用匿名函数。由于句法上的限制,非平凡的lambda表达式要么难以阅读,要么无法写出。

    Lundh提出的lambda表达式重构秘笈如果使用lambda表达式导致一段代码难以理解,Fredrik Lundh建议像下面这样重构。

​ (1)编写注释,说明lambda表达式的作用。

​ (2)研究一会儿注释,并找出一个名称来概括注释。

​ (3)把lambda表达式转换成def语句,使用那个名称来定义函数。

​ (4)删除注释。

8. 可调用对象

​ 除了用户定义的函数,调用运算符(即( ))还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的callable( )函数。Python数据模型文档列出了7种可调用对象。

用户定义的函数

使用def语句或lambda表达式创建。

内置函数

使用C语言(CPython)实现的函数,如len或time.strftime。

内置方法

使用C语言实现的方法,如dict.get。

方法

在类的定义体中定义的函数。

调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。因为Python没有new运算符,所以调用类相当于调用函数。(通常,调用类会创建那个类的实例,不过覆盖__new__方法的话,也可能出现其他行为。)

类的实例

如果类定义了__call__方法,那么它的实例可以作为函数调用。

生成器函数

使用yield关键字的函数或方法。调用生成器函数返回的是生成器对象。

>>> abs, str ,13
(<built-in function abs>, <class 'str'>, 13)
>>> [callable(obj) for obj in (abs, str, 13)]
[True, True, False]

9. 不仅Python函数是真正的对象,任何Python对象都可以表现得像函数。为此,只需实现实例方法__call__

import random

class BingoCage:
    def __init__(self, items):
        self.__items = list(items)
        random.shuffle(self.__items) # 随机排序

    def pick(self):
        try:
            return self.__items.pop()
        except IndexError:
            raise LookupError("pick empty BingoCage")

    def __call__(self, *args, **kwargs):
        return self.pick()
>>> from bingocall import BingoCage
>>> bingo = BingoCage(range(5))
>>> bingo.pick()
3
>>> bingo()
1
>>> callable(bingo)
True

实现__call__方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用,例如BingoCage中的剩余元素。装饰器就是这样。装饰器必须是函数,而且有时要在多次调用之间“记住”某些事[例如备忘(memoization),即缓存消耗大的计算结果,供后面使用]。

10. 函数参数*args和**kwargs

def tag(name, *content, cls=None, **attrs):
    """生成一个或多个HTML标签"""
    if cls is not None:
        attrs['class'] = cls
    if attrs:
        attr_str = ''.join(' %s="%s"' % (attr, value) for attr, value in sorted(attrs.items()))
    else:
        attr_str = ""
    if content:
        return '\n'.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content)
    else:
        return '<%s%s />' % (name, attr_str)
>>> from tag import tag
>>> tag('br')
'<br />'
>>> tag('p', 'hello')
'<p>hello</p>'
>>> tag('p', 'hello', 'world')
'<p>hello</p>\n<p>world</p>'
>>> print(tag('p', 'hello', 'world'))
<p>hello</p>
<p>world</p>
>>> tag('p', 'hello', id=33)
'<p id="33">hello</p>'
>>> print(tag('p', 'hello', 'world', cls='sidebar'))
<p class="sidebar">hello</p>
<p class="sidebar">world</p>
>>> tag(content='testing', name="img")
'<img content="testing" />'
>>> my_tag = {'name':'img', 'title': 'Sunset Bulevard', 'src': 'sunset.jpg', 'cls': 'framed'}
>>> tag(**my_tag)
'<img class="framed" src="sunset.jpg" title="Sunset Bulevard" />'

❶ 传入单个定位参数,生成一个指定名称的空标签。

❷ 第一个参数后面的任意个参数会被*content捕获,存入一个元组。

❸ tag函数签名中没有明确指定名称的关键字参数会被attrs捕获,存入一个字典。

❹ cls参数只能作为关键字参数传入。

❺ 调用tag函数时,即便第一个定位参数也能作为关键字参数传入。

❻ 在my_tag前面加上,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被**attrs捕获。