博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python基础知识:数据类型与函数大全【爆肝】
阅读量:2020 次
发布时间:2019-04-28

本文共 22890 字,大约阅读时间需要 76 分钟。

Python通过自动实现方便的dunder方法(例如__init____str__(字符串表示形式)或__eq__(等于==运算符)),使编写更好的变得非常容易。 还使创建冻结的(不可变的)实例,序列化实例和强制使用类型提示更加容易。

本文内容:

  • 目录


    •  

的主要部分是:

  • @dataclass 装饰器,它返回相同的已定义类但已修改
  • field 允许按字段自定义的功能。

注意:在本文中,我们将使用此类的不同变体Response。此类旨在表示HTTP响应的简化形式。

如何创建数据类

要创建数据类,我们要做的就是@dataclass在自定义类上使用装饰器,如下所示:

fom dataclasses import dataclass@dataclassclass Response:    status: int    body: str

前面的示例创建一个Response具有statusbody属性的类。@dataclass默认情况下,装饰器为我们提供了以下好处:

  • 自动创建以下dunder方法:
    1. __init__
    2. __repr__
    3. __eq__
    4. __str__
  • 强制类型提示用法。如果在没有类提示的NameError情况下定义了数据类中的字段,则会引发异常。
  • @dataclass不创建新类,它返回相同的已定义类。这使您可以在常规类中执行的任何操作在数据类中均有效。

通过查看先前定义的类,我们可以欣赏好处Response

实例初始化:

>>> resp = Response(status=200, body="OK")

正确表示一个类:

>>> import logging >>> logging.basicConfig(level=logging.INFO)>>> resp = Response(status=200, body="OK")>>> logging.info(resp)... INFO:root:Response(status=200, body='OK')

实例平等

>>> resp_ok = Response(status=200, body="OK")>>> resp_500 = Response(status=500, body="Error")>>> resp_200 = Response(status=200, body="OK")>>> resp_ok == resp_500... False>>> resp_ok == resp_200... True

注意:我们可以自定义每个dunder方法的实现。我们将在本文后面看到。

栏位定义

有两种方法可以在数据类中定义字段。

  1. 使用类型提示和可选的默认值
from dataclasses import dstaclass@dataclassclass Response:    body: str    status: int = 200

以前的类可以是,只透过被实例化message值或两者statusmessage

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp_ok = Response(body="OK")>>> logging.info(resp_ok)... INFO:root:Response(body='OK', status=200)>>> # Create 500 response>>> resp_error = Response(status=500, body="error")>>> logging.info(resp_error)... INFO:root:Response(body='error', status=500)
  1. 使用field方法。如果需要在现场进行更细粒度的配置,建议使用此方法。

通过使用该field方法,我们可以:

指定默认值

使用该field方法时,我们可以通过传递default参数来指定默认值:

from dataclasses import dataclass@dataclassclass Response:    body: str    status: int = field(default=200)

在Python中,建议不要将。这意味着定义这样的数据类不是一个好主意(以下示例无效)

from dataclasses import dataclass@dataclassclass Response:    status: int    body: str    headers: dict = {}

如果我们可以使用前面的代码,则每个实例都response将共享同一个headers对象,那就不好了。

幸运的是,当使用上述示例时,数据类通过引发错误来帮助我们防止这种情况。如果我们需要添加不可变对象作为默认值,则可以使用default_factory

default_factory值应该是没有参数的函数。常用功能包括dictlist

from dataclasses import dataclass, field@dataclassclass Response:    status: int    body: str    headers: dict = field(default_factory=dict)

然后,我们可以像这样使用此类:

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp = Response(status=200, body="OK")>>> logging.info(resp)... INFO:root:Response(status=200, body='OK', headers={})

注意:对于可变默认值,请使用default_factory

在自动实现的Dunder方法中包含或排除字段

默认情况下,每个定义的字段使用__init____str____repr__,和__eq__。该field方法允许指定在实现以下dunder方法时使用哪些字段:

__init__

from dataclasses import dataclass@dataclassclass Response:    body: str    headers: dict = field(init=False, default_factory=dict)    status: int = 200

该数据类将实现这样的__init___方法:

def __init__(self, body:str, status: int=200):    self.body = body    self.status = status    self.headers = dict()

该版本的Response类不允许headers在初始化时使用值。这是我们如何使用它:

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> >>> resp = Response(body="Success")>>> logging.info(resp)... INFO:root:Response(body='Success', headers={}, status=200)>>>>>> # passing a headers param on initialization will raise an srgument error.>>> resp = Response(body="Success", headers={})... TypeError: __init__() got an unexpected keyword argument 'headers'>>>>>> # 'headers' is an instance attribute and can be used after initialization.>>> resp.headers = {"Content-Type": "application/json"}>>> logging.info(resp)... INFO:root:Response(body='Success', headers={'Content-Type': 'application/json'}, status=200)

注意__init__方法中未使用的字段也可以在使用初始化后填充__post_init__

__repr__ 和 __str__

from dataclasses import dataclass@dataclassclass Response:    body: str    headers: dict = field(repr=False, init=False, default_factory=dict)    status: int = 200

现在,Response该类将不会打印headers实例打印时的值。

>>> resp = Response(body="Success")>>> logging.info(resp)... INFO:root:Response(body='Success', status=200)

__eq__

from dataclasses import dataclass, field@dataclassclass Response:    body: str    headers: dict = field(compare=False, init=False, repr=False, default_factory=dict)    status: int = 200

比较一个实例是否等于另一个实例时,此版本的Response类将不考虑该headers值。

>>> resp_json = Response(body="Success")>>> resp_json.headers = {"Content-Type": "application/json"}>>> resp_xml = Response(body="Success")>>> resp_xml.headers = {"Content-Type": "application/xml"}>>> resp_json == resp_xml... True

这两个对象相等,因为检查相等性时仅考虑statusbody值,而不考虑headers值。

:当设置compareFalse上一个字段它不会被用于自动执行任何类似的方法(__lt____gt__等...)。稍后将进行更多比较。

添加特定于字段的元数据

我们可以将元数据添加到字段中。元数据是一个映射,它应由第三方库使用。数据类实现根本不使用字段元数据。

注意:如果您决定使用特定于字段的元数据,请注意,其他第三方库可能会覆盖任何值。建议使用特定的键来避免冲突。

from dataclasses import dataclass, fieldfrom typing import Any@dataclassclass Response:    body: Any = field(metadata={"force_str": True})    headers: dict = field(init=False, repr=False, default_factory=dict)    status: int = 200

此类Response为键分配映射force_str作为元数据。元数据映射可以用作配置,以强制使用任何以as形式传递的字符串表示形式body

要访问字段的元数据,fields可以使用该方法。

>>> from dataclasses import fields>>> resp = Response(body="Success")>>> fields(resp)...(Field(name='body',type=typing.Any,default=
,default_factory=
,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({'force_str': True}),_field_type=_FIELD), Field(name='headers',type=
,default=
,default_factory=
,init=False,repr=False,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD), Field(name='status',type=
,default=200,default_factory=
,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),_field_type=_FIELD))

fields方法返回一个Fields对象元组。可以在实例或类上使用它。

要检索该body字段,我们可以使用理解和next

>>> body_field = next(    (field     for field in fields(resp)     if field.name == "body"),    None)>>> logging.info(body_field)... INFO:root:Field(name='body',type=typing.Any,default=
,default_factory=
,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({'force_str': True}),_field_type=_FIELD)>>> logging.info(body_field.metadata)... INFO:root:{'force_str': True}

很多人学习python,不知道从何学起。
很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识。
那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!
QQ群:553215015


自定义对象初始化

所述@dataclass装饰自动实现的__init__方法。通过使用,__post_init__我们可以在初始化时添加自定义逻辑,而不必重新实现__init__

from dataclasses import dataclass, field from typing import Any  from sys import getsizeof@dataclassclass Response:    body: str      headers: dict = field(init=False, compare=False, default_factory=dict)    status: int = 200    def __post_init__(self):        """Add a Content-Length header on init"""        self.headers["Content-Length"] = getsizeof(self.body)

实例化上一类时,将自动计算内容长度。

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp = Response("Success")>>> logging.info(resp)... INFO:root:Response(body='Success', headers={'Content-Length': 56}, status=200)

我们还可以在 __post_init__

from dataclasses import dataclass, is_dstaclass, asdict@dataclassclass Response:    body: Any = field(metadata={"force_str": True})    headers: dict = field(init=False, compare=False, default_factory=dict)    status: int = 200    def stringify_body(self):        """Returns a string representation of the value in body"""        body = self.body        if is_dataclass(body):            body = asdict(body)        if isinstance(body, dict):            return json.dumps(body)        if not isinstance(body, str):            return str(body)        return body    def __post_init__(self):        """Custom int logic.        - Check if body is configured to force value as string          - Calculate body's length and add corresponding header.         """        body_field = self.__dataclass_fields__["body"]        if body_field.metadata["force_str"]:            self.body = self.stringify_body()        self.headers["Content-Length"] = getsizeof(self.body)

我们可以像这样使用此类:

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp = Response(body={"message": "Success"})>>> logging.info(resp)... INFO:root:Response(body='{"message": "Success"}', headers={'Content-Length': 71}, status=200)

body值将自动序列化为字符串,并在初始化时存储在calss中。

前面的示例主要是为了展示自定义初始化逻辑。实际上,您可能不希望存储响应主体的字符串表示形式,而是使类可序列化更好。

注意:可以用于asdict将数据类转换为字典,这对于字符串序列化很有用。

我们还可以指定不是实例属性的字段,而是__post_init__通过使用以下字段将其传递给钩子dataclasses.InitVar

from dataclasses import dataclass, is_dstaclass, asdict, InitVar@dataclassclass Response:    body: Any    headers: dict = field(init=False, compare=False, default_factory=dict)    status: int = 200    force_body_str: InitVar[bool] = True    def stringify_body(self):        """Returns a string representation of the value in body"""        body = self.body        if is_dataclass(body):            body = asdict(body)        if isinstance(body, dict):            return json.dumps(body)        if not isinstance(body, str):            return str(body)        return body    def __post_init__(self, force_body_str):        """Custom int logic.        - Check if body is configured to force value as string          - Calculate body's length and add corresponding header.         """        if force_body_str:            self.body = self.stringify_body()        self.headers["Content-Length"] = getsizeof(self.body)

我们可以轻松地配置是否将值body存储为字符串:

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response where 'body' will be stored as a dict.>>> resp = Response(body={"message": "Success"}, force_body_str=False)>>> logging.info(resp)... INFO:root:Response(body={'message': 'Success'}, headers={'Content-Length': 232}, status=200)>>> # Create 200 response where 'body' will be stored as a string.>>> resp_str = Response(body={"message": "Success"})>>> logging.info(resp)... INFO:root:Response(body='{"message": "Success"}', headers={'Content-Length': 71}, status=200)

我们可以比较和排序的数据类

默认情况下,数据类实现__eq__。我们可以将order布尔参数传递给@dataclass装饰器,以实现__lt__(小于),__le__(小于或等于),__gt__(大于)和__ge__(大于或等于)。

这些丰富的的实现方式采用每个定义的字段,并按定义的顺序对它们进行比较,直到存在不相等的值为止。

from dataclasses import dataclass@dataclass(order=True)class Response:    body: str    status: int = 200

以前的数据类现在可以使用相比>=<=><操作数。最好的用例是在排序时:

>>> resp_ok = Response(body="Success")>>> resp_error = Response(body="Error", status=500)>>> sorted([resp_ok, resp_error])... [Response(body='Error', status=500), Response(body='Success', status=200)]

在此示例resp_error中,resp_ok因为的unicode值E小于的unicode值,所以在此示例之前S

注意:实现丰富的比较方法使我们可以轻松地对对象进行排序。

实施的比较方法将检查的值body,如果两者相等,则将继续进行status。如果该类具有更多字段,则将依次检查其余字段,直到找到不相等的值。

前面的示例是有效的,但是Response基于bodystatus值对对象进行排序没有多大意义。将它们按身体的长度排序更有意义。我们可以使用以下field方法指定要比较的字段:

from dataclasses import dataclass, field from sys import getsizeof@dataclass(order=True)class Response:    body: str = field(compare=False)    status: int = field(compare=False, default=200)    _content_length: int = field(compare=True, init=False)    def __post_init__(self):        """Calculate and store content length on init"""        self._content_length = getsizeof(self.body)

在前面的示例中,我们通过将布尔compare参数传递给方法来指定在实现比较方法时使用的字段field

现在,该类将根据的值大小进行排序body。我们还可以根据的大小判断一个实例是否大于另一个实例body

>>> resp_ok = Response(body="Success")>>> resp_error = Response(body="Error", status=500)>>> sorted([resp_ok, re sp_error])...[Response(body='Error', status=500, _content_length=54), Response(body='Success', status=200, _content_length=56)]>>> # resp_error is smaller than resp_ok because>>> # "Error" is smaller than "Success"

此实现的一个缺点是,只要body属性的大小相同,两个给定的实例将相等。

>>> resp_ok = Response(body="Success")>>> resp_error = Response(body="Failure")>>> resp_ok == resp_error... True>>> # both instances are equal because Success and Failure have the same amounts of chars>>> # and getsizeof() returns the same size for both strings.

为了相等,最好还检查body属性值是否相同:

from dataclasses import dataclass, field from sys import getsizeof@dataclass(order=True)class Response:    _content_length: int = field(compare=True, init=False)    body: str = field(compare=True)    status: int = field(compare=False, default=200)    def __post_init__(self):        """Calculate and store content length on init"""        self._content_length = getsizeof(self.body)

通过将_content_length字段定义移到body内容的长度上方,将首先将其用于任何比较。我们还将body字段设置为compare字段。在检查相等性时,如果内容长度相同,body将检查的实际值,这是检查相等性的更好方法。

 
>>> resp_ok = Response(body="Success")>>> resp_error = Response(body="Failure")>>> resp_ok == resp_error... False

这也适用于排序,因为具有相同内容长度的响应实例将通过字符的权重进行排序。排序将始终产生相同的顺序。

>>> sorted([resp_ok, resp_error])... [Response(_content_length=56, body='Failure', status=200), Response(_content_length=56, body='Success', status=200)]

注意:定义字段的顺序对于比较很重要。

冻结或不可变的实例

我们可以通过传递frozen=True@dataclass装饰器来创建冻结的实例。

from dataclasses import dataclass, field@dataclass(frozen=True)class Response:    body: str    status: int = 200

当您要确保代码或第三方库未错误地修改只读数据时,这很有用。如果尝试修改值,FrozenInstanceError则会引发异常:

>>> resp_ok = Response(body="Success")>>> resp_ok.body = "Done!"... dataclasses.FrozenInstanceError cannot assign to field 'body'

在Python中,我们不能真正拥有。如果您不遗余力,仍然可以修改冻结的数据类实例:

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Check values of 'resp_ok'>>> logging.info(resp_ok)... INFO:root:Response(body='Success', status=200)>>>>>> object.__setattr__(resp_ok, "body", "Done!")>>> # We have modified a "frozen" instance>>> logging.info(resp_ok)... INFO:root:Response(body='Done!', status=200)

这不太可能发生,但是值得知道。

注意:在使用只读数据时,使用冻结的数据类,以避免不必要的副作用。

注意:您不能__post_init__在冻结的数据类中实现挂钩。

替换或更新对象实例

数据类模块还提供了replace一种使用相同类创建新实例的方法。任何更新都作为参数传递:

from dataclasses import dataclass, replace@dataclass(frozen=True)class Response:    body: str    status: int = 200>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp_ok = Response(body="Success")>>> logging.info(resp_ok)... INFO:root:Response(body='Success', status=200)>>> # Replace instance>>> resp_ok = replace(resp_ok, body="OK")>>> logging.info(resp_ok)... INFO:root:Response(body='OK', status=200)

价值body被更新,价值status被复制。resp_ok现在,任何引用都指向新的更新对象。

注意:使用replace确保_init___post_init__将使用更新的值运行。

添加类属性

在Python中,一个类可以具有,与实例属性的区别主要是以下两个:

  1. 类属性在外部定义 __init__
  2. 该类的每个实例将共享一个类属性的相同值。

我们可以使用伪字段在数据类中定义类属性 typing.ClassVar

from dataclasses import dataclassfrom typing import ClassVar, Anyfrom sys import getsizeof from collections.abc import Callable@dataclassclass Response:    body: str     _content_length: int = field(default=0, init=False)    status: int = 200    getsize_fun: ClassVar[Callable[[Any], int]] = getsizeof    def __post_init__(self):        """Calculate content length by using getsize_fun"""        self._content_length = self.getsize_fun(self.body)

在此版本中,Response我们可以指定一个用于计算内容大小的函数。默认情况下sys.getsizeof使用。

from functools import reducedef calc_str_unicode_weight(self, string: str):    """Calculates strn weight by adding each character's unicode value"""    return reduce(lambda weight, char: weight+ord(char), string, 0)@dataclassclass ResponseUnicode(Response):    getsize_fun: ClassVar[Callable[[Any], int]] = calc_str_unicode_weight>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response, using getsizeof to calculate content length>>> resp_ok = Response(body="Success")>>> logging.info(resp_ok)... INFO:root:Response(body='Success', _content_length=56, status=200)>>> # Override function to use when calculating content length>>> resp_ok_unicode = ResponseUnicode(body="Success")>>> logging.info(resp_ok_unicode)... INFO:root:ResponseUnicode(body='Success', _content_length=729, status=200)

要覆盖用于计算内容长度的functino,我们将其子类化Response并传递我们想要的函数为getsize_fun

注意:所使用的字段ClassVar未在__init__,相等或比较dunder方法之类的数据类机制中使用。

数据类的继承

将继承与数据类一起使用时,字段将合并,这意味着子类可以覆盖字段定义。由于@dataclass装饰器返回的是旧的常规Python类,因此其他所有功能都相同。

from dataclasses import dataclasses@dataclassclass Response:    body: str    status: int    headers: dict@dataclassclass JSONResponse(Response):    status: int = 200    headers: dict = field(default_factory=dict, init=False)    def __post_init__(self):        """automatically add Content-Type header"""        self.headers["Content-Type"] = "application/json"

在上一个示例中,父类Response定义了基本字段,而子类JSONResponse覆盖了该headers字段并为该字段设置了默认值status

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp_ok = JSONResponse(body=json.dumps({"message": "OK"}))>>> logging.info(resp_ok)... INFO:root:JSONResponse(body='{"message": "OK"}', status=200, headers={'Content-Type': 'application/json'})

对象

@dataclass装饰将自动,如果参数frozeneqTruefrozenFalse默认设置,eq也是True默认设置。

from dataclasses import dataclass@dataclass(frozen=True)class Response:    body: str    status: int = 200

现在,我们可以使用此类的任何实例作为dict或中的键set。就我而言,我们可以创建用户响应的映射

>>> import logging>>> logging.basicConfig(level=logging.INFO)>>> # Create 200 response>>> resp_ok = Response(body="Success")>>> # Create 500 response>>> resp_error = Response(body="Error", status=500)>>> # Create a mapping of response -> usernames>>> responses_to_users = {...     resp_ok: ["j_mccain", "a_perez"],...     resp_error: ["d_dane", "b_rodriguez"]... }>>> logging.info(responses_to_users[resp_ok])... INFO:root:['j_mccain', 'a_perez']

__hash__即使未设置frozen,也eq可以True通过传递force_hash=True@dataclass装饰器来强制执行函数。仅在您100%确定需要此功能时,才应使用此功能。

数据类的用例

在本文中,我们对Response代表简化的HTTP响应对象的类进行了不同的更新。让我们放在一起。

为简单起见,我们将在同一个文件中编写将要使用的每个类和函数,实际上,应将这些类和函数散布到明智的模块中。

注意:这仍然不是100%真实的HTTP响应类,但是它具有足够的信息来了解如何使用数据类

import loggingfrom sys import getsizeoffrom typing import ClassVar, Any, Optionalfrom collections.abc import Callablefrom dataclasses import dataclass, field, InitVar, asdictlogging.basicConfig(level=logging.DEBUG)class APIException(Exception):    """Custom API exception."""    def __init__(self, message: str, **kwargs):        self.message = message;        self.data: Dict[str: Any] = kwargsclass PositiveNumberValidator:    """Descriptor to make sure a value is a positive number"""    def __set_name__(self, owner, name):        self.name = f"_{name }"    def __get__(self, obj, objtype=None):        return getattr(obj, self.name)    def __set__(self, obj, value):        self.validate(value)        setattr(obj, self.name, value)    def validate(self, value):        if not isinstance(value, int):            raise AttributeError(f"value of '{self.name}' must be a number")        if value < 0:            raise AttributeError(f"value of '{self.name}'' must be a positive number")@dataclass(eq=False)class Pager:    """Pager class.    This class is to hold any pager related data sent in the response.    The prev and next paramteres are meant to be links sent in the 'Link' header.    """    page_num: InitVar[int]    prev: str     next: str    page: ClassVar[PositiveNumberValidator] = PositiveNumberValidator()    def __post_init__(self, page_num):        """Assign page value to descriptor."""        self.page = page_num@dataclass(order=True)class HTTPResponse:    """Parent HTTPResponse    This class can:      1. Ordered and compared by its content size and body using regular operators      2. Pass a 'content_type' string to be used as header value.      3. Update headers directly as a regular dictionary.      4. Customize the function to calculate content-length.    """    _content_length: int = field(init=False)    body: Any    pager: Optional[Pager] = field(default=None, compare=False)    headers: dict = field(default_factory=dict, init=False, compare=False)    status: int = field(default=200, compare=False)    content_type: InitVar[str] = "text/html"    getsize_fun: ClassVar[Callable[[Any], int]] = getsizeof    def __post_init__(self, content_type):        """automatically calculate header values."""        self._content_length = self.getsize_fun(self.body)        self.headers["Content-Length"] = self._content_length        self.headers["Content-Type"] = content_type@dataclass(frozen=True)class JSONBody:    """Class to hold data sent in a JSON Response.    This class is immutable to avoid any unwanted modification of data.    """    message: str    data: dict    @classmethod    def from_exc(cls: type, exc: APIException):        """Initializes a JSONBody object from an exception."""        return cls(message=str(exc), data=getattr(exc, "data", {}))@dataclassclass JSONResponse(HTTPResponse):    """Class to represent a JSON Response. Child of HTTPResponse."""    body: JSONBody    content_type: InitVar[str] = "application/json"

在这里,我们创建了一个父类HTTPResponse,该父类将保存发送HTTP响应所需的基本数据。然后,我们创建了一个JSONResponse继承HTTPResponse并覆盖bodycontent_type属性的类。覆盖这些属性使我们可以为指定不同的默认内容类型和其他类型body。还有一个Pager类,用于保存响应中发送的与分页有关的所有数据。本Pager类使用的描述符来验证page始终为正数。我们还有一个JSONBody可以通过传递一个messagedata来初始化,或者可以通过传递一个APIException实例来初始化。APIException 是我们创建的自定义异常,用于存储异常消息以及与该异常相关的一些数据。

以下是一些基本示例,说明如何使用这些类

  1. 我们可以JSONResponse通过传递JSONBody实例来创建一个:
>>> import logging >>> logging.basicConfig(level=logging.INFO) >>> body = JSONBody(message="Success", data={"values": ["value1", "value2"]}) >>> resp = JSONResponse(body) >>> logging.info("resp: %s", resp) ... INFO:root:resp: JSONResponse(_content_length=48, body=JSONBody(message='Success', data={'values': ['value1', 'value2']}), pager=None, headers={'Content-Length': 48, 'Content-Type': 'application/json'}, status=200)

这已经是一个功能强大的类,并且实现它所需的代码非常短

   2.我们还可以传递一个Pager实例以使其响应更加可靠:

>>> pager = Pager(1, prev="?prev=0", next="?next=2") >>> resp = JSONResponse(body, pager=pager) >>> logging.info("resp: %s", resp) ... INFO:root:resp: JSONResponse(_content_length=48, body=JSONBody(message='Success', data={'values': ['value1', 'value2']}), pager=Pager(prev='?prev=0', next='?next=2'), headers={'Content-Length': 48, 'Content-Type': 'application/json'}, status=200)

  3.即使使用嵌套数据类,我们也可以轻松地将数据类转换为字典或元组:

>>> from dataclasses import asdict, astuple >>> logging.info("serialized resp: %s", asdict(resp)) ... INFO:root:serialized resp: {'_content_length': 48, 'body': {'message': 'Success', 'data': {'values': ['value1', 'value2']}}, 'pager': {'prev': '?prev=0', 'next': '?next=2'}, 'headers': {'Content-Length': 48, 'Content-Type': 'application/json'}, 'status': 200} >>> logging.info("resp as tuple: %s", astuple(resp)) ... INFO:root:resp astuple: (48, ('Success', {'values': ['value1', 'value2']}), ('?prev=0', '?next=2'), {'Content-Length': 48, 'Content-Type': 'application/json'}, 200)

 

通过一个更真实的示例,我们可以看到数据类的优点和缺点。

使用数据类的好处

  1. 我们可以用更少的代码创建功能强大的类。
  2. 对于每个类和实例属性都强制执行类型提示。
  3. 我们可以定制如何实施特殊的Dunder方法。
  4. 我们可以像使用常规类一样使用数据类。在前面的示例中,我们使用描述符和类方法没有问题。
  5. 继承可用于简化数据类的使用。
  6. 将实例序列化为字典或元组比较麻烦。
  7. 我们可以混合使用常规类和数据类。

使用数据类的缺点

  1. 在创建可以比较和排序的数据类时,定义字段的顺序很重要。因此,可读性会受到影响。建议尝试按类型分隔字段。在HTTPResponse类中,我们首先拥有私有属性,然后是实例属性,仅初始化参数和类属性。
  2. 使用默认值时,字段定义顺序也很重要。由于__init__的参数是按照定义字段的相同顺序实现的,因此我们必须先定义没有默认值的属性,然后再定义带有默认值的属性。
  3. 使用描述符需要一些技巧。由于描述符仅适用于类属性,并且__init__方法中不包括类属性,因此我们必须始终创建仅init参数,然后使用中的描述符存储该值__post_init__。您可以在Pager类实现中看到一个示例。
  4. 使用时,frozen=True我们无法更新__post_init__
  5. 如果需要,我们必须手动优化属性访问。含义,添加__slots__

 

在这里还是要推荐下我自己建的Python学习群:553215015,群里都是学Python的,如果你想学或者正在学习Python ,欢迎你加入,大家都是软件开发党,不定期分享干货(只有Python软件开发相关的),包括我自己整理的一份2020最新的Python进阶资料和零基础教学,欢迎进阶中和对Python感兴趣的小伙伴加入!

 

转载地址:http://luhxf.baihongyu.com/

你可能感兴趣的文章
crm使用soap操作商机赢单
查看>>
jsp页面跳转
查看>>
NSURLConnection同步和异步连接
查看>>
ORACLE中创建表空间,创建表,改动表,授权
查看>>
展望由非易失性设备构成的未来存储
查看>>
开源并兼容Windows NT的操作系统ReactOS简单介绍
查看>>
Sybase查询表结构的方法(相似于Oracle的Desc)
查看>>
【Oracle】OGG error while loading shared libraries
查看>>
浅谈Flash Socket通信安全沙箱
查看>>
创建树 - #号法
查看>>
poj 3436 ACM Computer Factory 最大流拆点+输出路径
查看>>
java 泛型接口
查看>>
hdu1998 奇数阶魔法 (数组填数)
查看>>
hdu 2563 -统计问题 【递推关系】
查看>>
Android开发之使用BroadcastReceiver实时监听电量(源码分享)
查看>>
13_Android的生命周期
查看>>
GIT有时候.gitignore不起作用的问题
查看>>
twisted network programming essentials 读书体会
查看>>
nyoj 1078 汉诺塔(四)[二分图 || 规律 || 暴力 || 贪心]
查看>>
7 怎样检查一个数字是不是2的乘方
查看>>