attrs学习

转载自:Python 面向对象编程OOP (四) 写类神器:attrs - 阿尔法的Python笔记 - SegmentFault 思否

使用attrs解放双手

大家好,这一期我想和大家分享一个OOP编程的高效神器:attrs库

首先我们来介绍下 attrs 这个库,其官方的介绍如下:

attrs 是这样的一个 Python 工具包,它能将你从繁综复杂的实现上解脱出来,享受编写 Python 类的快乐。它的目标就是在不减慢你编程速度的前提下,帮助你来编写简洁而又正确的代码。

因此用了它,定义和实现 Python 类变得更加简洁和高效

首先明确一点,我们现在是装了 attrs 和 cattrs 这两个库,但是实际导入的时候是使用 attr 和 cattr 这两个包,是不带 s 的。

在 attr 这个库里面有两个比较常用的组件叫做 attrs 和 attr,前者是主要用来修饰一个自定义类的,后者是定义类里面的一个字段的。下面是一个小例子

from attr import attrs,attrib

@attrs
class Person:
    name = attrib(type = str,default="")
    age = attrib(type = int,default=0)
    sex = attrib(type = str,default="")

if __name__ == '__main__':
    first_person = Person("John",18,"M")
    print(first_person)

Out:Person(name='John', age=18, sex='M')

主要作用

可以发现,Person这个类 三个属性都只写了一次,同时还指定了各个字段的类型和默认值,另外也不需要再定义 init 方法和 repr 方法了,非常简洁

实际上,主要是 attrs 这个修饰符起了作用,然后根据定义的 attrib 属性自动帮我们实现了 initrepreqneltlegtgehash 这几个方法

深入了解

现在来用实例看一下:

first_person = Person("John",18,"M")
second_person = Person("Nancy",16,"F")

print('Equal:', first_person == second_person)  #False
print('Not Equal(ne):', first_person != second_person) #True
print('Less Than(lt):', first_person.age < second_person.age) #False
print('Less or Equal(le):', first_person.age <= second_person.age) #False
print('Greater Than(gt):', first_person.age > second_person.age) #True
print('Greater or Equal(ge):', first_person.age >= second_person.age) #True

属性定义

对于 attrib 的定义,可以传入各种参数,不同的参数对于这个类的定义有非常大的影响。

下面来详细了解一下每个属性的具体参数和用法。

首先我们用 attrs 里面的 fields 方法可以查看一下

from attr import attrs, attrib,fields
print(fields(Person))

(Attribute(name='name', default='', validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=<class 'str'>, converter=None, kw_only=False), Attribute(name='age', default=0, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=<class 'int'>, converter=None, kw_only=False), Attribute(name='sex', default='', validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=<class 'str'>, converter=None, kw_only=False))
参数解释
name属性的名字,是一个字符串类型
default属性的默认值,如果没有传入初始化数据,那么就会使用默认值,如果没有默认值定义,那么就是 NOTHING,即没有默认值
validator验证器,检查传入的参数是否合法
init是否参与初始化,如果为 False,那么这个参数不能当做类的初始化参数,默认是 True。
type类型,比如 int、str 等各种类型,默认为 None
metadata元数据,只读性的附加数据
converter转换器,进行一些值的处理和转换器,增加容错性
kw_only是否为强制关键字参数,默认为 False

初始化

如果一个类的某些属性不想参与初始化,比如想直接设置一个初始值,一直固定不变,我们可以将属性的 init 参数设置为 False,看一个实例:

from attr import attrs,attrib

@attrs
class Person:
    name = attrib(type = str)
    age = attrib(init=False)
    sex = attrib(type = str)

first = Person("John","M") # Person(name='John', age=NOTHING, sex='M')

second = Person("Mike",89,"M")
#TypeError: __init__() takes 3 positional arguments but 4 were given

可以发现,first没有问题,但是second会报错,因为age没有参与初始化,只剩了self,age,sex

强制关键字

强制关键字是 Python 里面的一个特性,在传入的时候必须使用关键字的名字来传入

设置了强制关键字参数的属性必须要放在后面,其后面不能再有非强制关键字参数的属性

我们还是拿一样的例子,这回把sex的参数

from attr import attrs,attrib

@attrs
class Person:
    name = attrib(type = str)
    age = attrib(type = str)
    sex = attrib(kw_only=True)
    
first = Person("John",18,sex="M")
#Person(name='John', age=18, sex='M')

如果初始化first时使用Person("John",18,"M")则会报错

验证器

有时候在设置一个属性的时候必须要满足某个条件,比如性别必须要是男或者女,否则就不合法。对于这种情况,我们就需要有条件来控制某些属性不能为非法值。

from attr import attrs, attrib

def is_valid_gender(instance, attribute, value):
    if value not in ('M', 'F'):
        raise ValueError(f'gender {value} is not valid')
        
@attrs
class Person:
    name = attrib(type = str)
    age = attrib(type = str)
    sex = attrib(validator=is_valid_gender)

在这里我们定义了一个验证器 Validator 方法,叫做 is_valid_gender,其中 gender 定义的时候传入了一个参数 validator,其值就是我们定义的 Validator 方法:

  • instance:类对象
  • attribute:属性名
  • value:属性值

下面做了两个实验,一个就是正常传入 "M",另一个写错了,写的是 "X":

first = Person("John",18,"M")  
# Person(name='John', age=18, sex='M')

second = Person("Ann",29,"X")  
ValueError: gender X is not valid

second报错了,因为其值不是正常的性别,所以程序直接报错终止
注意在 Validator 里面返回 True 或 False 是没用的,错误的值还会被照常复制。所以,一定要在 Validator 里面 raise 某个错误。

另外 attrs 库里面还给我们内置了好多 Validator,比如判断类型,这里如果规定age必须为 int 类型:

age  =attrib(validator=validators.instance_of(int))

另外 validator 参数还支持多个 Validator,比如我们要设置既要是数字,又要小于 100,那么可以把几个 Validator 放到一个列表里面并传入:

from attr import attrs, attrib,validators

def is_valid_gender(instance, attribute, value):
    if value not in ('M', 'F'):
        raise ValueError(f'gender {value} is not valid')
        
def is_less_than_100(instance, attribute, value):
    if value > 100:
        raise ValueError(f'age {value} must less than 100')
        
@attrs
class Person:
    name = attrib(type = str)
    age = attrib(validator=[validators.instance_of(int), is_less_than_100])
    sex = attrib(validator=[validators.instance_of(str),is_valid_gender])

转换器

很多时候我们会不小心传入一些形式不太标准的结果,比如本来是 int 类型的 100,我们传入了字符串类型的 100,那这时候直接抛错应该不好吧,所以我们可以设置一些转换器来增强容错机制,比如将字符串自动转为数字:

from attr import attrs, attrib,validators

def to_int(value):
    try:
        return int(value)
    except:
        return None
@attrs
class Person:
    name = attrib(type = str)
    age = attrib(converter=to_int)
    sex = attrib(validator=validators.instance_of(str))

last_person = Person("xiaobai","35","M")    
print(last_person)

Out: Person(name='xiaobai', age=35, sex='M')
<think>嗯,用户问的是关于Vue的$attrs,我需要先回忆一下这个知识点的内容。首先,$attrs是Vue的一个实例属性,主要用在组件通信中,特别是在处理父组件传递的属性和事件时。可能用户在使用Vue组件时遇到了属性传递的问题,或者想了解如何更高效地处理非prop属性。 我记得$attrs在Vue 2和Vue 3中的行为可能有些不同。比如,在Vue 2中,$attrs包含的是父组件传递下来的非prop属性,而class和style不在其中。但在Vue 3中,$attrs会包括所有传递的属性,包括class和style。这一点需要特别指出,避免用户混淆。 用户可能的情况是,他们创建了一个组件,但发现有些属性没有正确传递到子组件,或者希望将多个属性批量传递,而不用一个个声明props。这时候$attrs就派上用场了,特别是结合v-bind="$attrs"可以一次性绑定所有属性。 另外,用户可能对$attrs和props的区别不太清楚。需要解释清楚,props是显式声明的,而$attrs是未被声明为props的属性。同时,$listeners在Vue 2中用于事件监听器,但在Vue 3中已经合并到$attrs里,这也是需要注意的版本差异。 可能用户的实际应用场景是封装高阶组件,比如自定义表单控件,需要将原生属性如placeholder、disabled等自动传递到内部的input元素上。这时候使用$attrs可以避免在每个组件中重复声明这些属性,提高组件的可复用性。 还要提到inheritAttrs选项,默认情况下父作用域的非prop属性会作为普通HTML属性应用在子组件的根元素上,通过设置inheritAttrs为false,可以关闭这一行为,然后通过v-bind="$attrs"手动控制属性的应用位置,这在需要将属性传递给特定子元素时非常有用。 可能需要举例说明,比如一个自定义输入组件,将$attrs绑定到内部的input标签,这样父组件传递的所有属性(如type、placeholder)都会应用到input上,而不是根元素div上。这样可以避免属性被错误地应用到根元素导致样式或功能问题。 另外,用户可能对如何监听事件有疑问,特别是在Vue 3中,由于$listeners被移除,事件监听器也包含在$attrs里,所以需要使用v-on来绑定,或者通过context.attrs访问事件。这里需要给出示例代码,帮助用户理解。 还要注意,如果用户使用的是Vue 2,需要提醒他们关于$listeners的存在,而Vue 3中不再需要,避免混淆。可能用户是在学习过程中遇到问题,所以明确版本差异很重要。 总结回答的结构应该是先定义$attrs,然后说明其作用,比较不同版本的区别,使用场景,如何与inheritAttrs配合使用,以及提供示例代码。这样用户能全面理解概念和应用方法,解决他们可能遇到的实际问题。</think>在 Vue 中,`$attrs` 是一个组件实例属性,用于处理 **非 Prop 的父组件传递属性**(即父组件传递给子组件,但子组件未在 `props` 中显式声明的属性)。它常用于高阶组件开发或需要透传属性的场景。 --- ### 核心概念 1. **非 Prop 属性** 父组件传递给子组件,但子组件未通过 `props` 接收的属性(如 `class`、`style`、原生 HTML 属性等)。 2. **自动继承与透传** - 默认情况下,父组件的非 Prop 属性会直接绑定到子组件的根元素上(通过 HTML 属性继承)。 - 如果子组件的根元素已存在同名属性,Vue 会智能合并(如 `class`)或覆盖(如 `value`)。 -- ### `$attrs` 的作用 - **透传属性** 通过 `v-bind="$attrs"` 可以将所有父组件传递的非 Prop 属性批量绑定到子组件的内部元素,而非根元素。 - **精细化控制** 结合 `inheritAttrs: false` 选项,可以阻止默认的属性继承行为,手动决定属性的传递位置。 --- ### 使用场景 #### 1. 基础透传 ```vue <!-- 父组件 --> <ChildComponent type="text" placeholder="请输入" disabled /> <!-- 子组件 --> <template> <div> <!-- 手动将 $attrs 绑定到 input 元素 --> <input v-bind="$attrs" /> </div> </template> <script> export default { inheritAttrs: false // 禁止默认继承到根元素 div } </script> ``` #### 2. 高阶组件封装 封装一个增强的输入框组件,透传所有原生属性和事件: ```vue <template> <div class="custom-input"> <label>{{ label }}</label> <input v-bind="$attrs" :value="value" @input="$emit('input', $event.target.value)" /> </div> </template> <script> export default { inheritAttrs: false, props: ['label', 'value'] } </script> ``` --- ### Vue 2 vs Vue 3 差异 | 特性 | Vue 2 | Vue 3 | |--| | `$attrs` 包含内容 | 不包含 `class` 和 `style` | 包含所有传递的属性,包括 `class` 和 `style` | | 事件监听器 | 通过 `$listeners` 访问 | 合并到 `$attrs` 中,直接透传 | --- ### 关键选项:`inheritAttrs` - **默认值**:`true` 父组件的非 Prop 属性会自动绑定到子组件根元素。 - **设置为 `false`** 禁用自动继承,通过 `$attrs` 手动控制属性传递位置。 -- ### 总结 `$attrs` 是 Vue 中实现 **属性透传** 的核心工具,尤其适用于: 1. 封装与原生 HTML 元素交互的组件(如表单控件) 2. 构建高阶组件时透传未知属性 3. 避免逐一声明 `props` 的冗余代码 通过结合 `v-bind="$attrs"` 和 `inheritAttrs: false`,可以更灵活地控制组件行为,提升代码复用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值