我正在嘗試從類中分離一個欄位和一個方法以使用 mongodb。
工人階級的例子:
@dataclass
class Article(Mongodata):
name: str
quantity: int
description: str
_id: Optional[int] = None
def __getdict__(self):
result = asdict(self)
result.pop("_id")
return result
如何將 _id 和getdict隔離到一個抽象類中,以便一切正常。
@dataclass
class Article(Mongodata):
name: str
quantity: int
description: str
@dataclass
class Mongodata(ABCMeta):
@property
@abstractmethod
def _id(self) -> Optional[int]:
return None
def __getdict__(self):
result = asdict(self)
result.pop("_id")
return result
你能解釋一下抽象類和元類有什么不同嗎,我來自java,讀完之后我什么都不明白?
uj5u.com熱心網友回復:
正如您所提到的,您使用的是 Python 3.9,您可以按照與上面相同的方式進行設定,但是,如果您如上所述宣告欄位Article并在超類中添加一個欄位定義,如下所示:
@dataclass
class Mongodata(ABC):
_id: Optional[int] = None
然后如果你真的嘗試運行代碼,你會遇到TypeError如下:
TypeError: non-default argument 'name' follows default argument
這樣做的原因是在dataclasses涉及繼承時決議資料類的欄位的順序。在這種情況下,它首先添加_id來自超類的欄位,然后是Article資料類中的所有欄位。由于它添加的第一個引數有一個默認值,但它后面的引數沒有默認值,它會TypeError像你期望的那樣引發 a 。
請注意,如果您決定以__init__相同的方式為 Article 類手動生成一個方法,您實際上會遇到相同的行為:
def __init__(self, _id: Optional[int] = None, name: str, quantity: int, description: str):
^
SyntaxError: non-default argument follows default argument
Python 3.9 中的最佳方法似乎是以這種方式宣告資料類,以便子類中的所有欄位都具有默認值:
from abc import ABC
from dataclasses import dataclass, asdict
from typing import Optional
@dataclass
class Mongodata(ABC):
_id: Optional[int] = None
def __getdict__(self):
result = asdict(self)
result.pop("_id")
return result
@dataclass
class Article(Mongodata):
name: str = None
quantity: int = None
description: str = None
但是隨后創建Article物件的位置引數將是一個問題,因為它會將傳入建構式的第一個引數分配給_id:
a = Article('123', 321, 'desc')
因此,您可以改為將其None作為第一個位置引數傳遞,然后將其分配給_id. 另一種有效的方法是將關鍵字引數傳遞給建構式:
a = Article(name='123', quantity=321, description='desc')
This actually feels more natural with the kw_only param that was introduced to dataclasses in Python 3.10 as a means to resolve this same issue, but more on that below.
A Metaclass Approach
Another option is to declare a function which can be used as a metaclass, as below:
from dataclasses import asdict
from typing import Optional
def add_id_and_get_dict(name: str, bases: tuple[type, ...], cls_dict: dict):
"""Metaclass to add an `_id` field and a `get_dict` method."""
# Get class annotations
cls_annotations = cls_dict['__annotations__']
# This assigns the `_id: Optional[int]` annotation
cls_annotations['_id'] = Optional[int]
# This assigns the `_id = None` assignment
cls_dict['_id'] = None
def get_dict(self):
result = asdict(self)
result.pop('_id')
return result
# add get_dict() method to the class
cls_dict['get_dict'] = get_dict
# create and return a new class
cls = type(name, bases, cls_dict)
return cls
Then you can simplify your dataclass definition a little. Also you technically don't need to define a get_dict method here, but it's useful so that an IDE knows that such a method exists on the class.
from dataclasses import dataclass
from typing import Any
@dataclass
class Article(metaclass=add_id_and_get_dict):
name: str
quantity: int
description: str
# Add for type hinting, so the IDE knows such a method exists.
def get_dict(self) -> dict[str, Any]:
...
And now it's a bit more intuitive when you want to create new Article objects:
a = Article('abc', 123, 'desc')
print(a) # Article(name='abc', quantity=123, description='desc', _id=None)
print(a._id) # None
print(a.get_dict()) # {'name': 'abc', 'quantity': 123, 'description': 'desc'}
a2 = Article('abc', 321, 'desc', _id=12345)
print(a2) # Article(name='abc', quantity=321, description='desc', _id=12345)
print(a2._id) # 12345
print(a2.get_dict()) # {'name': 'abc', 'quantity': 321, 'description': 'desc'}
Keyword-only Arguments
In Python 3.10, if you don't want to assign default values to all the fields in a subclass, another option is to decorate the superclass with @dataclass(kw_only=True), so that fields defined in that class are then required to be keyword-only arguments by default.
You can also use the KW_ONLY sentinel value as a type annotation which is provided in dataclasses in Python 3.10 as shown below, which should also make things much simpler and more intuitive to work with.
from abc import ABC
from dataclasses import dataclass, asdict, KW_ONLY
from typing import Optional
@dataclass
class Mongodata(ABC):
_: KW_ONLY
_id: Optional[int] = None
@property
def dict(self):
result = asdict(self)
result.pop("_id")
return result
# noinspection PyDataclass
@dataclass
class Article(Mongodata):
name: str
quantity: int
description: str
Essentially, any fields defined after the _: KW_ONLY then become keyword-only arguments to the constructor.
Now the usage should be exactly as desired. You can pass both keyword and positional arguments to the constructor, and it appears to work as intended:
a = Article(name='123', quantity=123, description='desc')
print(a) # Article(_id=None, name='123', quantity=123, description='desc')
print(a._id) # None
print(a.dict) # {'name': '123', 'quantity': 123, 'description': 'desc'}
a2 = Article('123', 321, 'desc', _id=112233)
print(a2) # Article(_id=112233, name='123', quantity=321, description='desc')
print(a2._id) # 112233
print(a2.dict) # {'name': '123', 'quantity': 321, 'description': 'desc'}
Also, just a quick explanation that I've been able to come up with, on why this appears to work as it does. Since you've only decorated the superclass as kw_only=True, all this accomplishes is in making _id as a keyword-only argument to the constructor. The fields in the subclass are allowed as either keyword or positional arguments, since we didn't specify kw_only for them.
An easier way to think about this, is to imagine that the signature of the __init__() method that dataclasses generates, actually looks like this:
def __init__(self, name: str, quantity: int, description: str, *, _id: Optional[int] = None):
In Python (not necessarily in 3.10 alone), the appearance of * in a function signifies that all the parameters that follow it are then declared as keyword-only arguments. Note that the _id argument, in this case is added as a keyword-argument after all the positional arguments from the subclass. This means that the method signature is valid, since it's certainly possible for keyword-only arguments to a method to have default values as we do here.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/357783.html
