python类元编程示例-使用类型注解来检查转换属性值的类框架
zhezhongyun 2025-05-14 18:24 11 浏览
参考《流程的python》第24章,用三种方式实现使用类型注解来检查转换属性值的类框架
1 __init_subclass__方式
1.1 代码实现
from collections.abc import Callable # <1>
from typing import Any, NoReturn, get_type_hints
from typing import Dict, Type
class Field:
def __init__(self, name: str, constructor: Callable) -> None: # <2>
if not callable(constructor) or constructor is type(None): # <3>
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.constructor = constructor
def __set__(self, instance: Any, value: Any) -> None:
if value is ...: # <4>
value = self.constructor()
else:
try:
value = self.constructor(value) # <5>
except (TypeError, ValueError) as e: # <6>
type_name = self.constructor.__name__
msg = f'{value!r} is not compatible with {self.name}:{type_name}'
raise TypeError(msg) from e
instance.__dict__[self.name] = value # <7>
class Checked:
@classmethod
def _fields(cls) -> Dict[str, type]: # <1>
return get_type_hints(cls)
def __init_subclass__(subclass) -> None: # <2>
super().__init_subclass__() # <3>
for name, constructor in subclass._fields().items(): # <4>
setattr(subclass, name, Field(name, constructor)) # <5>
def __init__(self, **kwargs: Any) -> None:
for name in self._fields(): # <6>
value = kwargs.pop(name, ...) # <7>
setattr(self, name, value) # <8>
if kwargs: # <9>
self.__flag_unknown_attrs(*kwargs) # <10>
def __setattr__(self, name: str, value: Any) -> None: # <1>
if name in self._fields(): # <2>
cls = self.__class__
descriptor = getattr(cls, name)
descriptor.__set__(self, value) # <3>
else: # <4>
self.__flag_unknown_attrs(name)
def __flag_unknown_attrs(self, *names: str) -> NoReturn: # <5>
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}')
def _asdict(self) -> Dict[str, Any]: # <6>
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
def __repr__(self) -> str: # <7>
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)
return f'{self.__class__.__name__}({kwargs})'
#!/usr/bin/env python3
from checkedlib import Checked
class Movie(Checked):
title: str
year: int
box_office: float
if __name__ == '__main__':
movie = Movie(title='The Godfather', year=1972, box_office=137)
print(movie.title)
print(movie)
try:
# remove the "type: ignore" comment to see Mypy error
movie.year = 'MCMLXXII' # type: ignore
except TypeError as e:
print(e)
try:
blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
except TypeError as e:
print(e)
checked_demo.py执行结果
The Godfather
Movie(title='The Godfather', year=1972, box_office=137.0)
'MCMLXXII' is not compatible with year:int
'billions' is not compatible with box_office:float
A Checked subclass definition requires that keyword arguments are
used to create an instance, and provides a nice __repr__::
>>> class Movie(Checked): # <1>
... title: str # <2>
... year: int
... box_office: float
...
>>> movie = Movie(title='The Godfather', year=1972, box_office=137) # <3>
>>> movie.title
'The Godfather'
>>> movie # <4>
Movie(title='The Godfather', year=1972, box_office=137.0)
The type of arguments is runtime checked during instantiation and when an attribute is set::
>>> blockbuster = Movie(title='Avatar', year=2009, box_office='billions')
Traceback (most recent call last):
...
TypeError: 'billions' is not compatible with box_office:float
>>> movie.year = 'MCMLXXII'
Traceback (most recent call last):
...
TypeError: 'MCMLXXII' is not compatible with year:int
Attributes not passed as arguments to the constructor are initialized with default values::
>>> Movie(title='Life of Brian')
Movie(title='Life of Brian', year=0, box_office=0.0)
Providing extra arguments to the constructor is not allowed::
>>> blockbuster = Movie(title='Avatar', year=2009, box_office=2000,
... director='James Cameron')
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'director'
Creating new attributes at runtime is restricted as well::
>>> movie.director = 'Francis Ford Coppola'
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'director'
The _asdict instance method creates a dict from the attributes
of a Movie object::
>>> movie._asdict()
{'title': 'The Godfather', 'year': 1972, 'box_office': 137.0}
2 代码解释
在这个例子中,__init_subclass__ 和 __init__ 的调用顺序和作用域不同,它们各自处理类和实例级别的逻辑。
调用顺序
- __init_subclass__ 是在子类定义时自动调用的。这发生在任何实例被创建之前,仅在类定义期间触发一次。因此,当定义一个继承了 Checked 类的新类时,__init_subclass__ 将被调用来为这个新类设置 Field 描述符。
- __init__ 是在创建类的一个实例时调用的。每次通过类(例如 Checked 的子类)创建一个新对象时,都会调用 __init__ 方法。
不同的作用
- __init_subclass__ 在 Checked 类中用来自动为每个子类基于类型注解创建并设置 Field 实例。这些 Field 实例被设置为类属性,用来管理对应的实例属性。
- __init__ 的作用是初始化具体的实例。尽管 __init_subclass__ 已经设置了 Field 描述符,但是描述符需要在实例化时对具体的属性值进行管理和转换。__init__ 中的 setattr(self, name, value) 调用是用来初始化这些实例属性的值,并且触发 Field 描述符的 __set__ 方法,该方法负责使用提供的构造函数验证和转换这些值。尽管 __init_subclass__ 已经把注解自动创建并分配为 Field 实例,但这些 Field 实例实际上是作为类属性存在的。它们不直接持有数据,而是定义了如何通过其 __set__ 和 __get__ 方法处理数据。当你在 __init__ 方法中使用 setattr 时,实际上是触发了这些 Field 描述符的 __set__ 方法,它们负责根据构造函数规则来验证和转换赋予实例属性的值。这样,每个实例都可以持有独立的、已经经过验证和转换的数据。
- __init_subclass__ 设置了如何管理和验证属性(即通过 Field 描述符),但并未处理具体的属性值。
- __init__ 负责实际设置这些属性的初始值,并触发描述符来处理验证和转换的逻辑。
在调用__init_subclass__之后类的结构会发生一些变化
在这个例子中,使用了 __init_subclass__ 方法和 Checked 类的框架,当 Movie 类被定义继承自 Checked 类时,__init_subclass__ 会自动被调用。这是在类级别发生的,即在任何 Movie 实例被创建之前。
当 Movie 类定义完成并触发 __init_subclass__ 方法时,这里发生的主要变化如下:
- 自动属性创建:
- 对于 Movie 类中定义的每个类型注解(title: str, year: int, box_office: float),__init_subclass__ 会从这些注解中获取信息,并为每个属性创建一个 Field 实例。
- Field 实例作为类属性:
- 这些 Field 实例不直接存储任何数据,而是作为描述符存在。描述符主要负责拦截对这些属性的访问和修改操作。
- 对于每个属性,Field 描述符会设置如何创建(调用相应的类型构造器,如 str(), int(), float()),验证和存储其值。
__init_subclass__ 方法中的核心操作包括:
- 遍历从 get_type_hints 获取到的类型注解。
- 为每个属性(如 title, year, box_office)创建一个 Field 描述符,并将其作为类属性设置到 Movie 类上。
这样设置后的 Movie 类的结构将包含以下元素:
- title 属性关联到一个 Field 描述符,该描述符使用 str 作为构造函数。
- year 属性关联到一个 Field 描述符,该描述符使用 int 作为构造函数。
- box_office 属性关联到一个 Field 描述符,该描述符使用 float 作为构造函数。
类的行为
当创建 Movie 的实例并尝试设置这些属性时:
- 访问或修改这些属性将触发对应的 Field 描述符的 __set__ 或 __get__ 方法。
- 如果尝试设置的值类型不正确,Field 的 __set__ 方法将尝试转换值到声明的类型(如将字符串转换为整数)。如果转换失败,将抛出 TypeError。
2 类装饰器方式
2.1 代码实现
from collections.abc import Callable # <1>
from typing import Any, NoReturn, get_type_hints
from typing import Dict, Type
class Field:
def __init__(self, name: str, constructor: Callable) -> None: # <2>
if not callable(constructor) or constructor is type(None):
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.constructor = constructor
def __set__(self, instance: Any, value: Any) -> None: # <3>
if value is ...: # <4>
value = self.constructor()
else:
try:
value = self.constructor(value) # <5>
except (TypeError, ValueError) as e:
type_name = self.constructor.__name__
msg = (
f'{value!r} is not compatible with {self.name}:{type_name}'
)
raise TypeError(msg) from e
instance.__dict__[self.name] = value # <6>
# tag::CHECKED_DECORATOR[]
def checked(cls: type) -> type: # <1>
for name, constructor in _fields(cls).items(): # <2>
setattr(cls, name, Field(name, constructor)) # <3>
cls._fields = classmethod(_fields) # type: ignore # <4>
instance_methods = ( # <5>
__init__,
__repr__,
__setattr__,
_asdict,
__flag_unknown_attrs,
)
for method in instance_methods: # <6>
setattr(cls, method.__name__, method)
return cls # <7>
def _fields(cls: type) -> Dict[str, type]:
return get_type_hints(cls)
def __init__(self: Any, **kwargs: Any) -> None:
for name in self._fields():
value = kwargs.pop(name, ...)
setattr(self, name, value)
if kwargs:
self.__flag_unknown_attrs(*kwargs)
def __setattr__(self: Any, name: str, value: Any) -> None:
if name in self._fields():
cls = self.__class__
descriptor = getattr(cls, name)
descriptor.__set__(self, value)
else:
self.__flag_unknown_attrs(name)
def __flag_unknown_attrs(self: Any, *names: str) -> NoReturn:
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} has no attribute{plural} {extra}')
def _asdict(self: Any) -> Dict[str, Any]:
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
def __repr__(self: Any) -> str:
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)
return f'{self.__class__.__name__}({kwargs})'
2.2 代码解释
这段代码实现了一个装饰器 checked,它为 Python 类添加强类型检查和自动属性管理的功能。该装饰器使用了 Python 的描述符协议和类型注解来自动验证和初始化类属性。下面逐部分解析代码的功能和工作方式:
Field 类
- 目的:Field 类用作属性描述符,主要负责设置和检索类实例的属性值,并确保值符合指定的类型。
- 构造方法 (__init__): 接收一个属性名和一个构造函数(类型)。如果构造函数不是可调用的或者是 None 类型,则抛出 TypeError。
- 设置方法 (__set__): 负责接收和处理对属性的赋值操作。如果值是 ...(省略符号),则使用构造函数(无参数)创建默认值。如果提供了值,则尝试使用构造函数将值转换为适当的类型。如果转换失败,会抛出 TypeError。
checked 装饰器
- 功能:通过自动应用类型注解和属性管理,使类成为"检查型"类。
- 实现:自动属性管理:遍历类的类型注解,并为每个注解创建一个 Field 描述符,负责管理相应的属性。添加方法:将一系列方法(如 __init__, __repr__, __setattr__, _asdict, __flag_unknown_attrs)添加到被装饰的类中,以支持初始化、属性设置、字典转换和错误管理等功能。
实现的方法
- _fields:返回一个字典,包含类的所有类型注解。用于在类中创建 Field 描述符。
- __init__:负责初始化新创建的实例。从 kwargs 中提取每个字段的值,如果未提供,则使用省略符号触发默认构造。
- __setattr__:当尝试设置属性时,首先检查该属性是否由 Field 描述符管理。如果是,则使用描述符的 __set__ 方法来设置值,否则抛出属性错误。
- __flag_unknown_attrs:在尝试设置未知属性或当 __init__ 方法中存在额外的 kwargs 时被调用,抛出 AttributeError。
- _asdict:返回一个字典,包含由 Field 描述符管理的所有属性及其值。
- __repr__:生成类实例的字符串表示,展示所有通过 Field 管理的属性和它们的值。
使用 checked 装饰器处理子类后类的结构会发生的变化
属性管理
checked 装饰器会遍历子类中通过类型注解定义的属性,并为每个属性创建并分配一个 Field 描述符实例。这些 Field 描述符被设置为类属性,用于管理相应的实例属性。这意味着:
对于每个定义的属性,都会有一个 Field 实例与之关联,负责接收和处理对该属性的所有访问和修改请求。
这些 Field 描述符将确保属性值在设置时被正确地转换和验证,按照定义的类型进行。
方法增强
checked 装饰器还会将几个关键的方法添加到子类中:
init:用于正确地初始化新对象的实例属性,接受关键字参数,每个参数对应一个属性。如果属性未在初始化时提供,将使用其类型的默认构造函数来生成值。
setattr:重写此方法以确保对属性的设置通过 Field 描述符进行,从而利用 Field 的类型检查和转换功能。
repr:提供了一种格式化的方式来描述类实例,显示所有通过 Field 管理的属性和它们的值。
_asdict:允许将对象的属性转换成字典形式,便于查看和使用。
__flag_unknown_attrs:当尝试访问或设置不存在的属性时,此方法将被调用,抛出 AttributeError。
3 元类方式
3.1 代码实现
from collections.abc import Callable
from typing import Any, NoReturn, get_type_hints
from typing import Dict, Type
class Field:
def __init__(self, name: str, constructor: Callable) -> None:
if not callable(constructor) or constructor is type(None):
raise TypeError(f'{name!r} type hint must be callable')
self.name = name
self.storage_name = '_' + name # <1>
self.constructor = constructor
def __get__(self, instance, owner=None):
if instance is None: # <2>
return self
return getattr(instance, self.storage_name) # <3>
def __set__(self, instance: Any, value: Any) -> None:
if value is ...:
value = self.constructor()
else:
try:
value = self.constructor(value)
except (TypeError, ValueError) as e:
type_name = self.constructor.__name__
msg = f'{value!r} is not compatible with {self.name}:{type_name}'
raise TypeError(msg) from e
setattr(instance, self.storage_name, value) # <4>
class CheckedMeta(type):
def __new__(meta_cls, cls_name, bases, cls_dict): # <1>
if '__slots__' not in cls_dict: # <2>
slots = []
type_hints = cls_dict.get('__annotations__', {}) # <3>
for name, constructor in type_hints.items(): # <4>
field = Field(name, constructor) # <5>
cls_dict[name] = field # <6>
slots.append(field.storage_name) # <7>
cls_dict['__slots__'] = slots # <8>
return super().__new__(
meta_cls, cls_name, bases, cls_dict) # <9>
class Checked(metaclass=CheckedMeta):
__slots__ = () # skip CheckedMeta.__new__ processing
@classmethod
def _fields(cls) -> Dict[str, type]:
return get_type_hints(cls)
def __init__(self, **kwargs: Any) -> None:
for name in self._fields():
value = kwargs.pop(name, ...)
setattr(self, name, value)
if kwargs:
self.__flag_unknown_attrs(*kwargs)
def __flag_unknown_attrs(self, *names: str) -> NoReturn:
plural = 's' if len(names) > 1 else ''
extra = ', '.join(f'{name!r}' for name in names)
cls_name = repr(self.__class__.__name__)
raise AttributeError(f'{cls_name} object has no attribute{plural} {extra}')
def _asdict(self) -> Dict[str, Any]:
return {
name: getattr(self, name)
for name, attr in self.__class__.__dict__.items()
if isinstance(attr, Field)
}
def __repr__(self) -> str:
kwargs = ', '.join(
f'{key}={value!r}' for key, value in self._asdict().items()
)
return f'{self.__class__.__name__}({kwargs})'
3.2 代码解释
这段代码定义了一个复杂的类装饰和类元编程机制,通过自定义描述符 Field 和元类 CheckedMeta 来自动化处理类属性的类型检查和初始化。这种机制可以用来创建严格类型化的数据结构,确保对象属性在设置时自动进行类型转换和验证。
Field 类
Field 用作属性描述符,负责管理属性的访问和设置。
- 初始化: 接收一个属性名称和一个构造函数。如果构造函数不是可调用的,则抛出异常。此外,它生成一个内部使用的存储属性名 (storage_name),这通常是私有属性名。
- __get__ 方法: 用于获取属性值。如果 instance 为 None(通常在直接通过类访问属性时发生),返回描述符自身;否则从实例字典中返回相应的值。
- __set__ 方法: 设置属性值。如果值为特殊标记 ...,使用无参数的构造函数创建默认值;否则尝试使用构造函数转换提供的值。如果转换失败,抛出 TypeError。
CheckedMeta 元类
CheckedMeta 是一个元类,用于在创建类时自动处理属性的类型注解。
- __new__ 方法: 在创建类对象时被调用。检查是否已定义 __slots__,用于优化内存使用。从类定义中获取类型注解,并为每个注解创建一个 Field 描述符,将其添加到类字典中。为每个 Field 描述符生成的 storage_name 创建一个对应的槽。最后,使用修改后的类字典调用基类的 __new__ 方法创建类。
Checked 类
作为使用 CheckedMeta 元类的示例,这个类提供了以下功能:
- 初始化: 使用关键字参数初始化类的实例。通过 _fields 方法获取所有字段,并尝试从 kwargs 中提取对应的值进行设置。
- 属性设置 (__setattr__): 拦截所有属性赋值操作,确保通过 Field 描述符处理。
- 错误处理: 当尝试访问或设置未知属性时,__flag_unknown_attrs 方法会抛出 AttributeError。
- 字典转换 (_asdict): 提供一种将对象属性转换为字典的方法,便于外部访问和操作。
- 字符串表示 (__repr__): 生成对象的友好字符串表示,展示其所有属性和值。
使用 CheckedMeta 作为元类的子类结构
属性描述符 (Field 实例):
title 关联到一个 Field 实例,此实例用 str 作为构造函数进行类型检查和转换。
year 关联到一个 Field 实例,此实例用 int 作为构造函数进行类型检查和转换。
box_office 关联到一个 Field 实例,此实例用 float 作为构造函数进行类型检查和转换。
槽 (slots):
slots 包含 [‘_title’, ‘_year’, ‘_box_office’],这些是实际存储属性值的内部属性名,避免了使用实例字典。
方法:
类继承自 Checked,因此拥有 init、setattr、_asdict、repr 和 __flag_unknown_attrs 等方法,这些方法提供了初始化、属性设置、错误处理、对象转字典和字符串表示的功能。
4 元类的调用顺序
元类在 Python 中用于创建类,它们本身是类的类。
元类的关键方法
- __prepare__:
- 调用时机: 在类体被执行前调用。
- 功能: 返回一个映射对象,用于在类定义时存储类属性和方法的定义。通常返回的是一个字典,但可以覆盖为其他类型的映射对象。
- 参数: 接受将要创建的类的名称和它的基类。
- 返回值: 用于类体执行的命名空间。
- __new__:
- 调用时机: 在类的实例(即类对象本身)被创建时调用。
- 功能: 负责实际创建类对象。它可以修改类的定义,添加、修改或删除属性,或者根据需要完全改变类的创建过程。
- 参数: 元类、类名、基类元组、类字典(由 __prepare__ 返回的映射对象),还可以接收额外的关键字参数(如果在类定义中提供)。
- 返回值: 返回一个新的类对象。
- __init__:
- 调用时机: 在类对象创建后被调用,用于初始化新创建的类对象。
- 功能: 初始化新创建的类对象。与 __new__ 不同,__init__ 不返回任何值,它仅用于初始化操作。
- 参数: 类对象、类名、基类元组、字典。
- __init_subclass__:
- 调用时机: 这是一个类方法,当类被继承时自动调用。
- 功能: 用于在创建子类时执行初始化或其他配置工作。
- 参数: 子类和任何在子类定义时传递的关键字参数。
- 返回值: 通常无返回值,用于设置子类或进行检查。
调用顺序示例
假设我们有一个元类 Meta 和使用这个元类的类 MyClass:
class Meta(type):
def __prepare__(name, bases, **kwargs):
print("Meta.__prepare__ called")
return super().__prepare__(name, bases, **kwargs)
def __new__(cls, name, bases, namespace, **kwargs):
print("Meta.__new__ called")
return super().__new__(cls, name, bases, namespace, **kwargs)
def __init__(cls, name, bases, namespace, **kwargs):
print("Meta.__init__ called")
super().__init__(name, bases, namespace, **kwargs)
def __init_subclass__(cls, **kwargs):
print("Meta.__init_subclass__ called")
super().__init_subclass__(**kwargs)
class MyClass(metaclass=Meta):
pass
class MySubClass(MyClass):
pass
调用顺序为:
- Meta.__prepare__ 被调用来准备 MyClass 的命名空间。
- 类体 MyClass 执行。
- Meta.__new__ 被调用来创建 MyClass 类对象。
- Meta.__init__ 被调用来初始化 MyClass 类对象。
- Meta.__init_subclass__ 被调用(如果在 MyClass 中定义)。
- 当 MySubClass 被定义时,上述步骤(1-4)重复执行,此外,MyClass.__init_subclass__ 也会被调用来处理子类的创建。
相关推荐
- 饿了么面试官:实现一下 Element-UI 官网的主题切换动画!
-
最近看到ElementPlus官网上的切换主题方式非常有趣,这是一个过渡的动画效果所以在网上查了一番,找到基本的实现方法实现基本效果首先我们起一个html文件,写一个按钮,以及简单的背景颜色切...
- 强大而好用的选择器:focus-within
-
伪类和伪元素在开发网页样式中,选择器必不可少,而且选择器也是在开发css中非常重要的内容,包括常用的类选择器,id选择,同时还有伪类,伪类选择器最大的特点就是冒号开头。平时也经常会有小伙伴问到,在使用...
- 令程序员惊叹的一些CSS3效果库
-
还在寻找那些CSS3的效果库吗?如果你的答案是肯定的,并且目前没有找到,那么你一定不能错过小编为大家收集的这些CSS3效果库,这是一个令你兴奋的集合!最新的CSS3都配备了新的特性,来设计创建动画和互...
- 伪元素黑魔法:一个替代onerror解决图片加载失败的方案
-
问题的引出是这样的,在一个项目中有大量的页面主体是table做数据展示,所以就封装了一个table的组件,提供动态渲染的方案。有个问题是数据类型中有图片,对于图片的加载失败我们需要做容错。一般我们的思...
- 前端 - 如何通过CSS修改图片透明度
-
如果在图片上显示文字,经常会遇到这个情况,就是当文字和背景颜色差不多时,文字会看不清楚,我们一般通过给文字加textshadow或者修改图片的透明度来让文字显示更加突出。我们今天说一下透明度的问题,...
- CSS元素居中方法完全指南
-
这里是工作狂的聚集地职场学术新媒体设计极客专门治愈处女座强迫症。本文为CSS入门翻译redman9原载CSS-Trick人们经常抱怨在CSS中居中元素的问题,其实这个问题并不复杂,只是因为方法众...
- CSS图像 hover 动画效果
-
点击页底“阅读原文”下载原码CSSHover在网页设计中是极为常用的一个CSS效果,只要你有创造力,都可以让Hover变得更多姿多彩,今天我们主要分享40多款使用CSSHOVER完成...
- 前端能限制用户截图吗?
-
摘要:在某些业务场景下,保护屏幕信息的私密性,防止用户随意截图分享,成为了前端开发者的一个棘手需求。但浏览器和操作系统的设计,真的允许网页开发者完全掌控用户的截图行为吗?本文将深入探讨前端限制截图的...
- 每天一个CSS小技巧 - 不规则投影
-
当我们想给一个矩形或者其他能用border-radius生成的形状加投影时,box-shadow的表现都很棒的。但是,当元素添加可一些伪元素或半透明的装饰之后,border-radius会无视这些。这...
- Web开发中10个有用的免费CSS代码
-
在本文中主要展示了在Web开发中一些免费但是非常有用的代码,开发人员可以下载它们来简化工作流程。在这个集合中的所有代码都是经过精挑细选的,对于开发人员来说非常有用。在开发一个网站时,这些代码将节省大量...
- 什么是伪类和伪元素?两者有什么区别?单一冒号和双冒号有何不同
-
https://juejin.im/post/5df1e312f265da33d039d06d?utm_source=bigezhang.com#comment伪类伪类存在的意义是为了通过选择器找到那...
- CSS2与CSS3中常用的伪类汇总大全
-
CSS2与CSS3中有非常多的伪类,可以用于实现各种强大的、酷炫的功能。有用于选择标签状态的,如:a:linka:hoverinput:checkedinput:focus等;也有用于根据结构选...
- 实用!这8个CSS工具可以提升编程速度
-
作为网页设计师,为了在预期的时间内能完成项目,前期肯定是要进行大量练习的。但是如果你花了大量的时间在编写CSS代码上,那无疑是浪费时间。工欲善其事必先利其器,聪明的设计师善于利用工具提升他们的编码效率...
- 《丝路传说怀旧版》宠物融合丹:属性加成与技能继承要点
-
在《丝路传说怀旧版》中,宠物融合丹是优化宠物属性与技能的核心道具,其使用需结合技能继承规则、品质提升机制及资源规划策略。以下是关键要点分析一、属性加成机制品质提升与属性增长品质阶梯:宠物分为白、绿、蓝...
- Python 3.14 t-string 要来了,它与 f-string 有何不同?
-
Python最近出了个大新闻:PEP-750t-string语法被正式采纳了!这意味着Python将在今年10月发布的3.14版本中引入一种新的字符串前缀t,称为模板字符串(Tem...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- CSS 水平对齐 (Horizontal Align) (30)