Python
3.7是在不久前发布的,我想测试一些新奇的dataclass
+键入功能。使用本机类型和typing
模块中的本机类型,使提示正确工作非常容易。
>>> import dataclasses
>>> import typing as ty
>>>
... @dataclasses.dataclass
... class Structure:
... a_str: str
... a_str_list: ty.List[str]
...
>>> my_struct = Structure(a_str='test', a_str_list=['t', 'e', 's', 't'])
>>> my_struct.a_str_list[0]. # IDE suggests all the string methods :)
但是我想尝试的另一件事是在运行时强制将类型提示作为条件,即dataclass
类型不正确的a不可能存在。它可以很好地实现__post_init__
:
>>> @dataclasses.dataclass
... class Structure:
... a_str: str
... a_str_list: ty.List[str]
...
... def validate(self):
... ret = True
... for field_name, field_def in self.__dataclass_fields__.items():
... actual_type = type(getattr(self, field_name))
... if actual_type != field_def.type:
... print(f"\t{field_name}: '{actual_type}' instead of '{field_def.type}'")
... ret = False
... return ret
...
... def __post_init__(self):
... if not self.validate():
... raise ValueError('Wrong types')
这种validate
功能适用于本机类型和自定义类,但不适用于typing
模块指定的功能:
>>> my_struct = Structure(a_str='test', a_str_list=['t', 'e', 's', 't'])
Traceback (most recent call last):
a_str_list: '<class 'list'>' instead of 'typing.List[str]'
ValueError: Wrong types
有没有更好的方法来验证带有typing
-typed列表的无类型列表?优选地,一个不包括检查类型的所有元素的任何list
,dict
,tuple
,或set
这是一个dataclass
“属性。
而不是检查类型是否相等,应使用isinstance
。但是您不能使用参数化的泛型类型(typing.List[int]
),而必须使用“泛型”版本(typing.List
)。因此,您将能够检查容器类型,而不是所包含的类型。参数化的泛型类型定义了__origin__
可用于该属性的属性。
与Python 3.6相反,在Python 3.7中,大多数类型提示都具有有用的__origin__
属性。比较:
# Python 3.6
>>> import typing
>>> typing.List.__origin__
>>> typing.List[int].__origin__
typing.List
和
# Python 3.7
>>> import typing
>>> typing.List.__origin__
<class 'list'>
>>> typing.List[int].__origin__
<class 'list'>
Python
3.8通过typing.get_origin()
自省功能引入了更好的支持:
# Python 3.8
>>> import typing
>>> typing.get_origin(typing.List)
<class 'list'>
>>> typing.get_origin(typing.List[int])
<class 'list'>
值得注意的例外是typing.Any
,typing.Union
和typing.ClassVar
…嗯,任何一个typing._SpecialForm
没有定义__origin__
。幸好:
>>> isinstance(typing.Union, typing._SpecialForm)
True
>>> isinstance(typing.Union[int, str], typing._SpecialForm)
False
>>> typing.get_origin(typing.Union[int, str])
typing.Union
但是参数化类型定义了一个__args__
属性,该属性将其参数存储为元组。Python
3.8引入了typing.get_args()
检索它们的功能:
# Python 3.7
>>> typing.Union[int, str].__args__
(<class 'int'>, <class 'str'>)
# Python 3.8
>>> typing.get_args(typing.Union[int, str])
(<class 'int'>, <class 'str'>)
因此,我们可以改进类型检查:
for field_name, field_def in self.__dataclass_fields__.items():
if isinstance(field_def.type, typing._SpecialForm):
# No check for typing.Any, typing.Union, typing.ClassVar (without parameters)
continue
try:
actual_type = field_def.type.__origin__
except AttributeError:
# In case of non-typing types (such as <class 'int'>, for instance)
actual_type = field_def.type
# In Python 3.8 one would replace the try/except with
# actual_type = typing.get_origin(field_def.type) or field_def.type
if isinstance(actual_type, typing._SpecialForm):
# case of typing.Union[…] or typing.ClassVar[…]
actual_type = field_def.type.__args__
actual_value = getattr(self, field_name)
if not isinstance(actual_value, actual_type):
print(f"\t{field_name}: '{type(actual_value)}' instead of '{field_def.type}'")
ret = False
这不是完美的,因为它不会考虑typing.ClassVar[typing.Union[int, str]]
或typing.Optional[typing.List[int]]
为实例,但是它应该上手的东西。
接下来是应用此检查的方法。
除了使用之外__post_init__
,我还可以使用装饰器路线:这可以用于具有类型提示的任何东西,不仅限于dataclasses
:
import inspect
import typing
from contextlib import suppress
from functools import wraps
def enforce_types(callable):
spec = inspect.getfullargspec(callable)
def check_types(*args, **kwargs):
parameters = dict(zip(spec.args, args))
parameters.update(kwargs)
for name, value in parameters.items():
with suppress(KeyError): # Assume un-annotated parameters can be any type
type_hint = spec.annotations[name]
if isinstance(type_hint, typing._SpecialForm):
# No check for typing.Any, typing.Union, typing.ClassVar (without parameters)
continue
try:
actual_type = type_hint.__origin__
except AttributeError:
# In case of non-typing types (such as <class 'int'>, for instance)
actual_type = type_hint
# In Python 3.8 one would replace the try/except with
# actual_type = typing.get_origin(type_hint) or type_hint
if isinstance(actual_type, typing._SpecialForm):
# case of typing.Union[…] or typing.ClassVar[…]
actual_type = type_hint.__args__
if not isinstance(value, actual_type):
raise TypeError('Unexpected type for \'{}\' (expected {} but found {})'.format(name, type_hint, type(value)))
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
check_types(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
if inspect.isclass(callable):
callable.__init__ = decorate(callable.__init__)
return callable
return decorate(callable)
用法是:
@enforce_types
@dataclasses.dataclass
class Point:
x: float
y: float
@enforce_types
def foo(bar: typing.Union[int, str]):
pass
Appart通过验证上一节中建议的某些类型提示,此方法仍存在一些缺点:
class Foo: def __init__(self: 'Foo'): pass
)不被考虑在内inspect.getfullargspec
:您可能希望使用typing.get_type_hints
和inspect.signature
替代;不验证不是适当类型的默认值:
@enforce_type
def foo(bar: int = None):
pass
foo()
没有筹集任何款项TypeError
。如果您想对此加以考虑inspect.Signature.bind
,inspect.BoundArguments.apply_defaults
则可能需要结合使用(并因此迫使您定义def foo(bar: typing.Optional[int] = None)
);
def foo(*args: typing.Sequence, **kwargs: typing.Mapping)
,如开头所述,我们只能验证容器,而不能验证包含的对象。在这个答案开始流行之后,一个受它启发很大的图书馆发布了,消除上述缺点的需求已成为现实。因此,我对该typing
模块进行了更多介绍,并将在此处提出一些发现和一种新方法。
对于初学者来说,typing
在寻找参数何时可选方面做得很好:
>>> def foo(a: int, b: str, c: typing.List[str] = None):
... pass
...
>>> typing.get_type_hints(foo)
{'a': <class 'int'>, 'b': <class 'str'>, 'c': typing.Union[typing.List[str], NoneType]}
这非常好,绝对是对的改进inspect.getfullargspec
,因此最好使用它,因为它还可以正确地将字符串作为类型提示来处理。但是typing.get_type_hints
会为其他类型的默认值提供援助:
>>> def foo(a: int, b: str, c: typing.List[str] = 3):
... pass
...
>>> typing.get_type_hints(foo)
{'a': <class 'int'>, 'b': <class 'str'>, 'c': typing.List[str]}
因此,即使您觉得这种情况非常麻烦,您仍可能需要进行更严格的检查。
接下来是用作或typing
参数的提示的情况。由于这些中的始终是一个元组,因此可以递归地找到该元组中包含的提示的。结合以上检查,我们将需要过滤所有剩余的内容。typing._SpecialForm``typing.Optional[typing.List[str]]``typing.Final[typing.Union[typing.Sequence, typing.Mapping]]``__args__``typing._SpecialForm``__origin__``typing._SpecialForm
拟议的改进:
import inspect
import typing
from functools import wraps
def _find_type_origin(type_hint):
if isinstance(type_hint, typing._SpecialForm):
# case of typing.Any, typing.ClassVar, typing.Final, typing.Literal,
# typing.NoReturn, typing.Optional, or typing.Union without parameters
yield typing.Any
return
actual_type = typing.get_origin(type_hint) or type_hint # requires Python 3.8
if isinstance(actual_type, typing._SpecialForm):
# case of typing.Union[…] or typing.ClassVar[…] or …
for origins in map(_find_type_origin, typing.get_args(type_hint)):
yield from origins
else:
yield actual_type
def _check_types(parameters, hints):
for name, value in parameters.items():
type_hint = hints.get(name, typing.Any)
actual_types = tuple(
origin
for origin in _find_type_origin(type_hint)
if origin is not typing.Any
)
if actual_types and not isinstance(value, actual_types):
raise TypeError(
f"Expected type '{type_hint}' for argument '{name}'"
f" but received type '{type(value)}' instead"
)
def enforce_types(callable):
def decorate(func):
hints = typing.get_type_hints(func)
signature = inspect.signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
parameters = dict(zip(signature.parameters, args))
parameters.update(kwargs)
_check_types(parameters, hints)
return func(*args, **kwargs)
return wrapper
if inspect.isclass(callable):
callable.__init__ = decorate(callable.__init__)
return callable
return decorate(callable)
def enforce_strict_types(callable):
def decorate(func):
hints = typing.get_type_hints(func)
signature = inspect.signature(func)
@wraps(func)
def wrapper(*args, **kwargs):
bound = signature.bind(*args, **kwargs)
bound.apply_defaults()
parameters = dict(zip(signature.parameters, bound.args))
parameters.update(bound.kwargs)
_check_types(parameters, hints)
return func(*args, **kwargs)
return wrapper
if inspect.isclass(callable):
callable.__init__ = decorate(callable.__init__)
return callable
return decorate(callable)
Python 的数值可以表示三种类型的数据: 整数 :可以表示正数,例如 123;可以表示负数,例如 123;使用 0 表示零。 浮点数:浮点数由整数部分与小数部分组成,例如 123.456。 复数:复数由实数部分和虚数部分构成,例如 1 + 2j,实数部分是 1,虚数部分是 2。 1. 基本运算 1.1 加法 整数相加 >>> 1 + 1 2 浮点数相加 >>> 1.2 + 2.3 3.5
本文向大家介绍详细解析Python中的变量的数据类型,包括了详细解析Python中的变量的数据类型的使用技巧和注意事项,需要的朋友参考一下 变量是只不过保留的内存位置用来存储值。这意味着,当创建一个变量,那么它在内存中保留一些空间。 根据一个变量的数据类型,解释器分配内存,并决定如何可以被存储在所保留的内存中。因此,通过分配不同的数据类型的变量,你可以存储整数,小数或字符在这些变量中。 变量赋值
这节课是数据类型篇最后一节了,这节课我们来讲下集合数据类型,集合这个数据类型很特殊,到底是个怎么特殊法,下面我们一起来看下: 1. 简介 1.1 定义 集合是一个无序、不重复的序列,集合中所有的元素放在 {} 中间,并用逗号分开,例如: {1, 2, 3},一个包含 3 个整数的列表 {‘a’, ‘b’, ‘c’},一个包含 3 个字符串的列表 1.2 集合与列表的区别 在 Python 中,集合
前面的几个小节我们分别学习了字符串、列表、和元组等等几种 Python 中的基础数据类型,这节课我们来学习 Python 中另一个比较重要的数据类型–字典,字典和其他我们已经学习过的数据类型都有些不一样,具体不一样在哪里我们一起来看一下: 1. 字典简介 字典由键和对应值成对组成,字典中所有的键值对放在 {} 中间,每一对键值之间用逗号分开,例如: {‘a’:‘A’, ‘b’: ‘B’, ‘c’:
元组是一个和列表和相似的数据类型,两者拥有着基本相同的特性,但是也有很多不同的地方,这节课我们来详细的介绍下元组这个数据类型 1. 简介 1.1 定义 元组是一个有序的只读序列,元组中所有的元素放在 () 中间,并用逗号分开,例如: (1, 2, 3),一个包含 3 个整数的元组 (‘a’, ‘b’, ‘c’),一个包含 3 个字符串的元组 1.2 元组与列表的区别 元组与列表很相似,都是有序的只
这节课我们来学习下 Python 中一个非常重要的数据类型:列表。为什么说它非常重要呢?因为在我们的实际开发过程中,列表是一个经常会用到的数据结构,它以占用空间小,浪费内存空间少这一特性而被广泛应用。这一小节我们会学习: 列表的常见运算操作 列表的常见函数 列表的常见方法 1. 什么是列表? 列表是一个有序的序列,列表中所有的元素放在 [] 中间,并用逗号分开,例如: [1, 2, 3],一个包含