全部版块 我的主页
论坛 数据科学与人工智能 数据分析与数据科学 数据分析与数据挖掘
800 0
2020-10-15
更灵活的装饰
传统上,装饰器必须是一个命名的,可调用的对象,通常是一个函数或一个类。PEP 614允许装饰器是任何可调用的表达式。
大多数人不认为旧的装饰器语法是限制性的。确实,放松装饰器的语法主要在一些利基用例中有所帮助。根据PEP,激励用例与GUI框架中的回调有关。
PyQT使用信号和插槽将小部件与回调连接。从概念上讲,您可以执行以下操作将clicked信号连接button到插槽say_hello():
button = QPushButton("Say hello")
@button.clicked.connect
def say_hello():
    message.setText("Hello
Hello
注意:这不是一个完整的示例,如果尝试运行它将引发错误。故意将其简短地集中在装饰器上,而不是沉迷于有关PyQT工作原理的细节。
有关PyQT入门和设置完整应用程序的更多信息,请参见Python和PyQt:构建GUI桌面计算器。
现在假设您有几个按钮,并且要跟踪它们,请将它们存储在字典中:
buttons = {
  "hello": QPushButton("Say hello")
  "leave": QPushButton("Goodbye")
  "calculate": QPushButton("3 + 9 = 12")
}
一切都很好。但是,如果您想使用装饰器将按钮连接到插槽,则会给您带来挑战。在早期版本的Python中,使用装饰器时无法使用方括号访问项目。您将需要执行以下操作:
hello_button = buttons["hello"]
@hello_button.clicked.connect
def say_hello():
    message.setText("Hello
在Python 3.9中,这些限制已解除,您现在可以使用任何表达式,包括在字典中访问一个项目:
@buttons["hello"].clicked.connect
def say_hello():
    message.setText("Hello
尽管这不是什么大的变化,但在少数情况下,它允许您编写更简洁的代码。扩展的语法还使在运行时动态选择装饰器变得更加容易。假设您有以下可用的装饰器:
# story.py
import functools
def normal(func):
    return func
def shout(func):
    @functools.wraps(func)
    def shout_decorator(*args
        return func(*args
    return shout_decorator
def whisper(func):
    @functools.wraps(func)
    def whisper_decorator(*args
        return func(*args
    return whisper_decorator
该@normal装饰完全不改变原有的功能,而@shout和@whisper提出的是从函数大写或小写返回任何文本。然后,您可以将对这些装饰器的引用存储在字典中,并使它们对用户可用:
# story.py (continued)
DECORATORS = {"normal": normal
voice = input(f"Choose your voice ({'
@DECORATORS[voice]
def get_story():
    return """
        Alice was beginning to get very tired of sitting by her sister on the
        bank
        the book her sister was reading
        conversations in it
        """
print(get_story())
运行此脚本时,系统会询问您要应用到该故事的装饰器。然后将结果文本打印到屏幕上:
$ python3.9 story.py
Choose your voice (normal
        ALICE WAS BEGINNING TO GET VERY TIRED OF SITTING BY HER SISTER ON THE
        BANK
        THE BOOK HER SISTER WAS READING
        CONVERSATIONS IN IT
此示例与@shout应用于相同get_story()。但是,此处已根据您的输入在运行时应用了它。与按钮示例一样,您可以通过使用临时变量在Python的早期版本中实现相同的效果。
有关装饰器的更多信息,请参阅Python Decorators上的Primer。有关松弛语法的更多详细信息,请参阅PEP 614。
注释类型提示
函数注释是在Python 3.0中引入的。该语法支持向Python函数添加任意元数据。这是将单位添加到公式的一个示例:
# calculator.py
def speed(distance: "feet"
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph
在此示例中,注释仅用作读者的文档。稍后您将看到如何在运行时访问批注。
PEP 484建议将注释用于类型提示。随着类型提示的流行,它们几乎已经排挤了Python中其他任何使用注释的地方。
由于静态类型之外的注释有多种使用情况,因此PEP 593引入了typing.Annotated,您可以将其与其他信息结合使用。您可以calculator.py像这样从上方重做示例:
# calculator.py
from typing import Annotated
def speed(
    distance: Annotated[float
) -> Annotated[float
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph
Annotated需要至少两个参数。第一个参数是常规类型提示,其余参数是任意元数据。类型检查器仅关心第一个参数,而元数据的解释则留给您和您的应用程序。类型检查器Annotated[float
您可以.__annotations__照常访问注释。speed()从导入calculator.py:
>>> from calculator import speed
>>> speed.__annotations__
{'distance': typing.Annotated[float
'time': typing.Annotated[float
'return': typing.Annotated[float
词典中提供了每个注释。您定义的元数据Annotated存储在.__metadata__:
>>> speed.__annotations__["distance"].__metadata__
('feet'
>>> {var: th.__metadata__[0] for var
{'distance': 'feet'
最后一个示例通过读取每个变量的第一个元数据项来挑选所有单位。在运行时访问类型提示的另一种方法是get_type_hints()在typing模块中使用。get_type_hints()默认情况下将忽略元数据:
>>> from typing import get_type_hints
>>> from calculator import speed
>>> get_type_hints(speed)
{'distance': <class 'float'>
'time': <class 'float'>
'return': <class 'float'>}
这应该允许大多数在运行时访问类型提示的程序无需更改即可继续运行。您可以使用新的可选参数include_extras要求包含元数据:
>>> get_type_hints(speed
{'distance': typing.Annotated[float
'time': typing.Annotated[float
'return': typing.Annotated[float
使用Annotated可能会导致冗长的代码。使代码简短易读的一种方法是使用类型别名。您可以定义表示带注释类型的新变量:
# calculator.py
from typing import Annotated
Feet = Annotated[float
Seconds = Annotated[float
MilesPerHour = Annotated[float
def speed(distance: Feet
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph
类型别名可能需要花一些时间来设置,但是它们可以使您的代码非常清晰易读。
如果您有一个广泛使用批注的应用程序,则还可以考虑实现批注工厂。将以下内容添加到calculator.py:
# calculator.py
from typing import Annotated
class AnnotationFactory:
    def __init__(self
        self.type_hint = type_hint
    def __getitem__(self
        if isinstance(key
            return Annotated[(self.type_hint
        else:
            return Annotated[self.type_hint
    def __repr__(self):
        return f"{self.__class__.__name__}({self.type_hint})"
AnnotationFactory可以创建Annotated具有不同元数据的对象。您可以使用注释工厂来创建更多动态别名。更新calculator.py使用AnnotationFactory:
# calculator.py (continued)
Float = AnnotationFactory(float)
def speed(
    distance: Float["feet"]
) -> Float["miles per hour"]:
    """Calculate speed as distance over time"""
    fps2mph = 3600 / 5280  # Feet per second to miles per hour
    return distance / time * fps2mph
Float[<metadata>]代表Annotated[float
移除广告
更强大的Python解析器
Python 3.9最酷的功能之一是您在日常编码生活中不会注意到的功能。解析器是Python解释器的基本组件。在最新版本中,解析器已重新实现。
自成立以来,Python使用基本的LL(1)解析器将源代码解析为解析树。您可以将LL(1)解析器视为一次读取一个字符并弄清楚如何解释源代码而无需回溯的解析器。
使用简单的解析器的一个优点是,它的实现和推理相当简单。缺点是在某些情况下您需要使用特殊的技巧来规避。
在Python的创建者Guido van Rossum的一系列博客文章中,他研究了PEG(解析表达式语法)解析器。PEG解析器比LL(1)解析器更强大,并且避免了特殊的破解。由于Guido的研究,在python 3.9中实现了PEG解析器。有关更多详细信息,请参见PEP 617。
目标是使新的PEG解析器产生与旧的LL(1)解析器相同的抽象语法树(AST)。这两个解析器实际上都附带了最新版本。虽然PEG解析器是默认解析器,但是您可以通过使用-X oldparser命令行标志,使用旧的解析器运行程序:
$ python -X oldparser script_name.py
或者,您可以设置PYTHONOLDPARSER环境变量。
旧的解析器将在Python 3.10中删除。这将允许新功能不受LL(1)语法的限制。如PEP 622中所述,当前正在考虑将其包含在Python 3.10中的此类功能之一是结构模式匹配。
同时使用两个解析器非常适合验证新的PEG解析器。您可以在两个解析器上运行任何代码,并在AST级别进行比较。在测试过程中,对整个标准库以及许多流行的第三方软件包进行了编译和比较。
您还可以比较两个解析器的性能。通常,PEG分析器和LL(1)的执行情况相似。在整个标准库中,PEG解析器速度稍快,但也使用了更多的内存。实际上,使用新的解析器时,您不会注意到性能的好坏。
其他很酷的功能
到目前为止,您已经看到了Python 3.9中最大的新功能。但是,Python的每个新发行版也包括许多小的更改。该官方文档包括所有这些更改的列表。在本部分中,您将了解可以开始使用的其他一些非常酷的新功能。
字符串前缀和后缀
如果您需要删除字符串的开头或结尾,那么.strip()似乎可以完成此工作:
>>> "three cool features in Python".strip(" Python")
'ree cool features i'
后缀" Python"已删除,但"th"字符串的开头也已删除。的实际行为.strip()有时令人惊讶-并触发了许多 错误 报告。这是很自然的假设,.strip(" Python")将删除子" Python",但它消除了单个字符" ","P","y","t","h","o",和"n"来代替。
要实际删除字符串后缀,可以执行以下操作:
>>> def remove_suffix(text
...     if text.endswith(suffix):
...         return text[:-len(suffix)]
...     else:
...         return text
...
>>> remove_suffix("three cool features in Python"
'three cool features in'
这样效果更好,但有点麻烦。此代码也有一个细微的错误:
>>> remove_suffix("three cool features in Python"
''
如果后缀恰好是空字符串,则说明整个字符串已被删除。这是因为空字符串的长度为0,所以text[:0]最终返回。您可以通过重新启用测试来解决此问题suffix and text.endswith(suffix)。
在Python 3.9中,有两种新的字符串方法可以解决这个确切的用例。您可以分别使用.removeprefix()和.removesuffix()删除字符串的开头或结尾:
>>> "three cool features in Python".removesuffix(" Python")
'three cool features in'
>>> "three cool features in Python".removeprefix("three ")
'cool features in Python'
>>> "three cool features in Python".removeprefix("Something else")
'three cool features in Python'
请注意,如果给定的前缀或后缀与字符串不匹配,则会使字符串保持原样。
.removeprefix()并.removesuffix()删除最多一份副本。如果要确保删除所有它们,则可以使用while循环:
>>> text = "Waikiki"
>>> text.removesuffix("ki")
'Waiki'
>>> while text.endswith("ki"):
...     text = text.removesuffix("ki")
...
>>> text
'Wai'
有关和的更多信息,请参见PEP 616。.removeprefix().removesuffix()
移除广告
直接键入提示列表和字典
为基本类型str(int,和)添加类型提示通常非常简单bool。您直接用类型注释。这种情况与您创建的自定义类型类似:
radius: float = 3.9
class NothingType:
    pass
nothing: NothingType = NothingType()
泛型是一个不同的故事。泛型类型通常是可以参数化的容器,例如数字列表。由于技术原因,在以前的Python版本中,您无法使用list[float]或list(float)作为类型提示。相反,您需要从typing模块导入其他列表对象:
from typing import List
numbers: List[float]
在Python 3.9中,不再需要此并行层次结构。现在,您终于可以使用list正确的类型提示了:
numbers: list[float]
这将使您的代码更易于编写,并消除了同时使用list和的困惑List。将来,不推荐使用typing.List和类似的泛型(如typing.Dict和),typing.Type而这些泛型最终将从中删除typing。
如果您需要编写与旧版本Python兼容的代码,则仍可以通过使用Python 3.7中提供的__future__导入功能来利用新语法。在Python 3.7中,您通常会看到以下内容:
>>> numbers: list[float]
Traceback (most recent call last):
  File "<stdin>"
TypeError: 'type' object is not subscriptable
但是,使用__future__导入使此示例起作用:
>>> from __future__ import annotations
>>> numbers: list[float]
>>> __annotations__
{'numbers': 'list[float]'}
这是可行的,因为注释不会在运行时评估。如果您尝试评估注释,那么您仍然会遇到TypeError。有关此新语法的更多信息,请参见PEP 585。
拓扑排序
由节点和边组成的图可用于表示不同种类的数据。例如,当您用来pip从PyPI安装软件包时,该软件包可能依赖于其他软件包,而这些软件包可能具有更多的依赖性。
这种结构可以用一个图表示,其中每个包都是一个节点,每个依赖项都由一条边表示:
该图显示了realpython-reader的依赖关系
该图显示了realpython-reader的依赖关系
此图显示了realpython-reader软件包的依赖关系。它直接取决于feedparser和html2text,而feedparser反过来取决于sgmllib3k。
假设您要按顺序安装这些软件包,以便始终满足所有依赖性。然后,您将执行所谓的拓扑排序,以找到依赖关系的总顺序。
Python 3.9graphlib在标准库中引入了一个新模块,以进行拓扑排序。您可以使用它来查找集合的总顺序,或在考虑可以并行化的任务的情况下进行更高级的调度。要查看示例,可以在字典中表示较早的依赖关系:
>>> dependencies = {
...     "realpython-reader": {"feedparser"
...     "feedparser": {"sgmllib3k"}
... }
...
这表示您在上图中看到的依赖性。例如,realpython-reader取决于feedparser和html2text。在这种情况下,的特定依赖项realpython-reader被写为set :{"feedparser"
注意:请记住,字符串在其字符上是可迭代的。因此,您通常希望将单个字符串包装在某种容器中:
>>> dependencies = {"feedparser": "sgmllib3k"}  # Will NOT work
这并不能说是feedparser要看sgmllib3k。相反,它说,feedparser取决于每一个s,g,m,l,l,i,b,3,和k。
为了计算图的总订单,你可以使用TopologicalSorter从graphlib:
>>> from graphlib import TopologicalSorter
>>> ts = TopologicalSorter(dependencies)
>>> list(ts.static_order())
['html2text'
在给定的顺序建议是,应先安装html2text,然后sgmllib3k,然后feedparser,最后realpython-reader。
注意:图形的总顺序不一定是唯一的。在此示例中,其他有效顺序为:
sgmllib3k,html2text,feedparser,realpython-reader
sgmllib3k,feedparser,html2text,realpython-reader
TopologicalSorter具有广泛的API,可让您使用增量添加节点和边.add()。您还可以迭代使用图,这在计划可以并行完成的任务时特别有用。有关完整示例,请参见文档。

关注 CDA人工智能学院 ,回复“录播”获取更多人工智能精选直播视频!


二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群