13 个 python3 才能用的特性

发布时间:2019-08-26 07:17:47编辑:auto阅读(1638)

    分享来源13 个 python3 才能用的特性

    python3 于 2008 年发布,从最初的大割裂到现在,绝大多数的开源库已经使用 python3 来编写,并且已经迭代了五个大版本,最新的 python3.7 计划于 2018 年 6 月 15 发布正式版。而 python2.7 作为 python2 的最后一个版本,将于 2020 年 1 月停止维护。

    更多Python视频、源码、资料加群683380553免费获取

    python3 的使用率在很久的一段时间里增长非常缓慢,是的,大多数人只觉得 python3 只是改了输出语句 print(),而并没有意识到实际上 python3 所具有的大量新特性。虽然说可以用 import __future__ 来在 python2 使用部分特性,但是以下 13 点非常好用的特性是你在 python2 中完全无法体验到的。

     

    我们从 https://www.asmeurer.com/python3-presentation/slides.html 中整理并翻译了 python3 的特性,剔除了部分老旧的代码,整理了相关例子,并提供了 jupyter notebook 版本以及 html 版,获取方法在本文末。

     

    特性 1: 高级解包

    交换两个变量的值在 python 中非常简单,你也许已经在 python2 中大量使用以下方法:

    a, b = 1, 2
    a, b = b, a
    print(a, b)
    ##> 2, 1
    

     

    使用解包交换变量非常方便,在 python3 中,这个特性得到了加强,现在你可以这样做:

    a, b, *rest = range(10)
    print('a:', a)
    print('b:', b)
    print('rest:', rest)
    ##> a: 0
    ##> b: 1
    ##> rest: [2, 3, 4, 5, 6, 7, 8, 9]
    

     

    rest 可以在任何位置,比如这样:

    a, *rest, b = range(10)
    print('rest', rest)
    ##> rest [1, 2, 3, 4, 5, 6, 7, 8]
    
    *rest, b = range(10)
    print('rest', rest)
    ##> rest [0, 1, 2, 3, 4, 5, 6, 7, 8]
    

     

    使用 python 获得文件的第一行和最后一行内容。

    with open('use_python_to_profit.txt') as f:
        first, *_, last = f.readlines() # 注意,这会读取所有内容到内存中
    
    print('first:', first)
    print('last:', last)
    ##> first: step 1: use python
    ##> last: step 10: profit
    

     

    特性 2: 强制关键词参数

    def f(a, b, *args, option=True):
        pass
    

     

    如果你用以上写法来写一个函数,那么你限定了调用参数时,必须要这样写 f(a, b, option=True)

    如果你不想收集其他参数,你可以用 * 代替 *args,比如这样:

    def f(a, b, *, option=True):
        pass
    

     

    当你碰上这种事情:哎呀,我不小心传递太多参数给函数,其中之一会被关键字参数接收,然后程序原地爆炸了。

    def sum(a, b, biteme=False):
        if biteme:
            print('一键删库')
        else:
            return a + b
    
    sum(1, 2)
    ##> 3
    sum(1, 2, 3)
    ##> 一键删库.
    

    .. .所以,以后千万别这样写,为了你的下半生能够过上平静的日子,你应该这样:

    def sum(a, b, *, biteme=False):
        if biteme:
            print('一键删库')
        else:
            return a + b
    

     

    试一下不合法的调用:

    sum(1, 2, 3)
    ##> TypeError: sum() takes 2 positional arguments but 3 were given
    

     

    有时你会想写这样一个方法

    def maxall(iterable, key=None):
        """
        返回一个列表中的最大值
        """
        key = key or (lambda x: x)
        m = max(iterable, key=key)
        return [i for i in iterable if key(i) == key(m)]
    
    maxall(['a', 'ab', 'bc'], len)
    ##> ['ab', 'bc']
    

     

    但是你又想像内置的max()函数那样允许 max(a, b, c) 的写法,但是这两种传参方法似乎不能和平相处:

    def maxall(*args, key=None):
       """
       A list of all max items from the iterable
       """
       if len(args) == 1:
           iterable = args[0]
       else:
           iterable = args
       key = key or (lambda x: x)
       m = max(iterable, key=key)
       return [i for i in iterable if key(i) == key(m)]
    
    maxall(['a', 'bc', 'cd'], len)
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-22-e8f4c154d310> in <module>()
         11     return [i for i in iterable if key(i) == key(m)]
         12 
    ---> 13 maxall(['a', 'bc', 'cd'], len)
    
    <ipython-input-22-e8f4c154d310> in maxall(key, *args)
          8         iterable = args
          9     key = key or (lambda x: x)
    ---> 10     m = max(iterable, key=key)
         11     return [i for i in iterable if key(i) == key(m)]
         12 
    
    TypeError: unorderable types: builtin_function_or_method() > list()
    

     

    显然,我们应该用max(iterable, *, key=None)来写这个函数。你在写代码时,也可以用关键词参数使你的 api 具有更好的扩展性。

    # 蠢蠢的写法
    def extendto(value, shorter, longer):
        """
        使短的 list 填充到和长的 list 一样长,填充为 value
        """
        if len(shorter) > len(longer):
            raise ValueError('The `shorter` list is longer than the `longer` list')
        shorter.extend([value]*(len(longer) - len(shorter)))
    
    a = [1, 2]
    b = [1, 2, 3, 4, 5]
    
    extendto(10, a, b)
    
    print('a', a)
    ##> a [1, 2, 10, 10, 10]
    

     

    当你碰上这种事情:哎呀,我不小心传递太多参数给函数,其中之一会被关键字参数接收,然后程序原地爆炸了。

    # 更好的写法
    def extendto(value, *, shorter=None, longer=None):
        """
        Extend list `shorter` to the length of list `longer` with `value`
        """
        if shorter is None or longer is None:
            raise TypeError('`shorter` and `longer` must be specified')
        if len(shorter) > len(longer):
            raise ValueError('The `shorter` list is longer than the `longer` list')
        shorter.extend([value]*(len(longer) - len(shorter)))
    

     

    我们可以用 extendto(10, shorter=a, longer=b) 的方式调用这个方法,以后我们要修改这个接口的传参方式时,也不用修改已有代码啦。

     

    特性 3:链式异常

    现在你在写一个函数,由于可能会出现错误,你打算 catch 可能出现的异常,做一些额外的工作,然后再抛出另一种异常。

    import shutil
    
    def mycopy(source, dest):
        try:
            shutil.copy2(source, dest)
        except OSError: # We don't have permissions. More on this later
            raise NotImplementedError("automatic sudo injection")
    

     

    如果你用 python2 的话得到的是,你把第一个异常信息丢了,只能一脸懵逼。

    >>> mycopy(1, 2)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 5, in mycopy
    NotImplementedError: automatic sudo injection
    

     

    python3 中会依次把异常记录下来

    mycopy(1, 2)
    
    ---------------------------------------------------------------------------
    SameFileError                             Traceback (most recent call last)
    <ipython-input-26-7970d14296a0> in mycopy(source, dest)
          4     try:
    ----> 5         shutil.copy2(source, dest)
          6     except OSError: # We don't have permissions. More on this later
    
    /usr/lib/python3.5/shutil.py in copy2(src, dst, follow_symlinks)
        250         dst = os.path.join(dst, os.path.basename(src))
    --> 251     copyfile(src, dst, follow_symlinks=follow_symlinks)
        252     copystat(src, dst, follow_symlinks=follow_symlinks)
    
    /usr/lib/python3.5/shutil.py in copyfile(src, dst, follow_symlinks)
         97     if _samefile(src, dst):
    ---> 98         raise SameFileError("{!r} and {!r} are the same file".format(src, dst))
         99 
    
    SameFileError: 1 and 2 are the same file
    
    During handling of the above exception, another exception occurred:
    
    NotImplementedError                       Traceback (most recent call last)
    <ipython-input-27-ddb6bcd98254> in <module>()
          1 # python3 中会依次把异常记录下来
    ----> 2 mycopy(1, 2)
    
    <ipython-input-26-7970d14296a0> in mycopy(source, dest)
          5         shutil.copy2(source, dest)
          6     except OSError: # We don't have permissions. More on this later
    ----> 7         raise NotImplementedError("automatic sudo injection")
    
    NotImplementedError: automatic sudo injection
    

     

    特性 4: 更好用的 OSError 子类

    刚刚给你的代码其实不正确,OSError 实际上包含了很多类异常,比如权限不够,文件没找到,不是一个目录等,而我们默认是权限不够。你在 python2 中可能是这样来区分 OSError 的:

    import errno
    def mycopy(source, dest):
        try:
            shutil.copy2(source, dest)
        except OSError as e:
            if e.errno in [errno.EPERM, errno.EACCES]:
                raise NotImplementedError("automatic sudo injection")
            else:
                raise
    

     

    python3 添加了大量的新 Exception 类型 https://docs.python.org/3.4/library/exceptions.html#os-exceptions ,所以现在你可以这样做:

    def mycopy(source, dest):
        try:
            shutil.copy2(source, dest)
        except PermissionError:
            raise NotImplementedError("automatic sudo injection")
    

     

    特性 5: 一切皆迭代器

    python2 中已经有迭代器了,然而 emmmm

    def naivesum(N):
        A = 0
        for i in range(N + 1):
            A += i
        return A
    
    naivesum(100000000) # 内存爆炸
    

    当然,python2 中可以用 xrange 来解决这个问题,你还可以使用 itertools.izip, dict.itervalues 替代 zip 和 dict.values…… 在 python3 中,range,zip,dict.values 以及其它,都是返回迭代器,所以这对内存很友好。

    如果你希望得到一个列表,要做的仅仅是在外层加一个 list,显示的声明永远比隐式地更好,你很难再写出一个吃内存的代码了。

     

    特性 6: 不是一切都能比较

    在 python2 中,你可以这么写

    >>> max(['one', 2])
    'one'
    
    >>> "abc" > 123
    True
    
    >>> None > all
    False
    

    在 python3 中,这个非常 buggy 的特性被取消啦:

    'one' > 2
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-33-55b5025c2335> in <module>()
    ----> 1 'one' > 2
    
    TypeError: unorderable types: str() > int()
    

     

    特性 7: yield from

    如果你用 generator 的话,这个是一个非常好的特性。在以前,你是这么写代码的:

    for i in gen():
        yield i
    

    现在是这样

    yield from gen()
    

     

    没有看懂?来一个例子,比如这样,我们希望得到 [0, 0, 1, 1, 2, 2, ...] 的列表用于迭代,我们有以下写法:

     

    # 蠢蠢的方法,直接生成对应的列表
    def dup(n):
        A = []
        for i in range(n):
            A.extend([i, i])
        return A
    
    # 不错的方法,使用 yield
    def dup(n):
        for i in range(n):
            yield i
            yield i
    
    # 更好的方法,使用 yield from
    def dup(n):
        for i in range(n):
            yield from [i, i]
    

     

    我们知道,迭代器的方式非常好,首先在内存上它很有优势,并且可以按需计算,每次只计算要用的值。如果你需要一个列表的时候,只需要在外层加一个 list,如果你需要切片 slicing,可以用 itertools.islice()

     

    特性 8: asyncio

    现在你可以用更方便的协程调用了

    async def fetch(host, port):
        r, w = await open_connection(host, port)
        w,write(b'GET /HTTP/1.0\r\n\r\n')
        while (await r.readline()).decode('latin-1').strip():
            pass
        body = await r.read()
        return body
    
    async def start():
        data = await fetch('Welcome to Python.org', 80)
        print(data.deode('utf-8'))
    

     

    特性 9: 新的标准库

    ipaddress 库

    import ipaddress
    
    print(ipaddress.ip_address('192.168.0.1'))
    print(ipaddress.ip_address('2001:db8::'))
    ##> 192.168.0.1
    ##> 2001:db8::
    

     

    functools.lrc_cache 装饰器

    from functools import lru_cache
    from urllib.error import HTTPError
    import urllib.request
    
    @lru_cache(maxsize=32)
    def get_pep(num):
        'Retrieve text of a Python Enhancement Proposal'
        resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
        try:
            with urllib.request.urlopen(resource) as s:
                return s.read()
        except HTTPError:
            return 'Not Found'
    
    for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
        pep = get_pep(n)
        print(n, len(pep))
    
    get_pep.cache_info()
    ##> CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
    

     

    enum 类

    from enum import Enum
    
    class Color(Enum):
        red = 1
        green = 2
        blue = 3
    

     

    特性 10: Fun

    听说你会中文编程?

    简历 = "knows python"
    π = 2.1415936
    

     

    类型标注

    def f(a: int, b: int = 2) -> int:
        return 10
    
    print(f.__annotations__)
    ##> {'return': <class 'int'>, 'a': <class 'int'>, 'b': <class 'int'>}
    

     

    特性 11: Unicode 编码

    这是新手遇到的最多的问题,为什么我的命令行输出是乱码?

    python2 中的 str 是字节数组

    python3 中的 str 是 unicode 字符串,只有 unicode 才能表示中文。

     

    特性 12: 矩阵相乘

    python3 中 @ 可以被重载了,所以用 numpy 中的矩阵乘法时可以这么来(我在 tensorflow 中也经常这样写)

    import numpy as np
    
    a = np.array([[1, 0], [0, 1]])
    b = np.array([[4, 1], [2, 2]])
    
    # 旧写法
    print(np.dot(a, b))
    # 新写法
    print(a @ b)
    

     

    特性 13: pathlib

    这是一个特别好用的面向对象路径处理库,以下是旧写法

    import os
    
    directory = "/etc"
    filepath = os.path.join(directory, "hosts")
    
    if os.path.exists(filepath):
        print('hosts exist')
    

     

    更好的写法

    from pathlib import Path
    
    directory = Path("/etc")
    filepath = directory / "hosts"
    
    if filepath.exists():
        print('hosts exist')
    

     

关键字