本文共 22890 字,大约阅读时间需要 76 分钟。
Python通过自动实现方便的dunder方法(例如__init__
,__str__
(字符串表示形式)或__eq__
(等于==
运算符)),使编写更好的变得非常容易。 还使创建冻结的(不可变的)实例,序列化实例和强制使用类型提示更加容易。
本文内容:
目录
的主要部分是:
@dataclass
装饰器,它返回相同的已定义类但已修改field
允许按字段自定义的功能。注意:在本文中,我们将使用此类的不同变体
Response
。此类旨在表示HTTP响应的简化形式。
要创建数据类,我们要做的就是@dataclass
在自定义类上使用装饰器,如下所示:
fom dataclasses import dataclass@dataclassclass Response: status: int body: str
前面的示例创建一个Response
具有status
和body
属性的类。@dataclass
默认情况下,装饰器为我们提供了以下好处:
__init__
__repr__
__eq__
__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方法的实现。我们将在本文后面看到。
有两种方法可以在数据类中定义字段。
from dataclasses import dstaclass@dataclassclass Response: body: str status: int = 200
以前的类可以是,只透过被实例化message
值或两者status
并message
>>> 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)
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
值应该是没有参数的函数。常用功能包括dict
或list
:
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
默认情况下,每个定义的字段使用__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
这两个对象相等,因为检查相等性时仅考虑status
和body
值,而不考虑headers
值。
注:当设置
compare
到False
上一个字段它不会被用于自动执行任何类似的方法(__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}
所述@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
基于body
和status
值对对象进行排序没有多大意义。将它们按身体的长度排序更有意义。我们可以使用以下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中,一个类可以具有,与实例属性的区别主要是以下两个:
__init__
我们可以使用伪字段在数据类中定义类属性 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
装饰将自动,如果参数frozen
和eq
是True
。frozen
是False
默认设置,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
并覆盖body
和content_type
属性的类。覆盖这些属性使我们可以为指定不同的默认内容类型和其他类型body
。还有一个Pager
类,用于保存响应中发送的与分页有关的所有数据。本Pager
类使用的描述符来验证page
始终为正数。我们还有一个JSONBody
可以通过传递一个message
和data
来初始化,或者可以通过传递一个APIException
实例来初始化。APIException
是我们创建的自定义异常,用于存储异常消息以及与该异常相关的一些数据。
以下是一些基本示例,说明如何使用这些类:
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)
通过一个更真实的示例,我们可以看到数据类的优点和缺点。
HTTPResponse
类中,我们首先拥有私有属性,然后是实例属性,仅初始化参数和类属性。__init__
的参数是按照定义字段的相同顺序实现的,因此我们必须先定义没有默认值的属性,然后再定义带有默认值的属性。__init__
方法中不包括类属性,因此我们必须始终创建仅init参数,然后使用中的描述符存储该值__post_init__
。您可以在Pager
类实现中看到一个示例。frozen=True
我们无法更新__post_init__
__slots__
。
转载地址:http://luhxf.baihongyu.com/