简体   繁体   English

如何在 python-attrs 类中忽略额外的 kwargs

[英]how to ignore extra kwargs in python-attrs class

For example:例如:

@attrs
class Foo:
  a = attrib()

f = Foo(a=1, b=2)

Code above will throw an error because class Foo doesn't have b attr.上面的代码会抛出错误,因为类Foo没有b属性。 But I want to discard passed b value as if I just called f = Foo(a=1) .但是我想丢弃传递的b值,就像我刚刚调用f = Foo(a=1)一样。 In my use case I have dynamic dict (which I want to transform into attr-class) and I simply do not need some of the keys.在我的用例中,我有动态 dict(我想将其转换为 attr-class),我根本不需要一些键。

I think I figured out a more elegant solution which allows you to take advantage of the features of attrs while also tweaking the __init__ logic.我想我想出了一个更优雅的解决方案,它允许您利用attrs的功能同时调整__init__逻辑。 See attrs documentation for more info.有关详细信息,请参阅attrs 文档

@attr.s(auto_attribs=True, auto_detect=True)
class Foo():
    a: int
    optional: int = 3

    def __init__(self,**kwargs):
      filtered = {
        attribute.name: kwargs[attribute.name]
        for attribute in self.__attrs_attrs__
        if attribute.name in kwargs
      }
      self.__attrs_init__(**filtered)

The code above allows you to specify extraneous keyword args.上面的代码允许您指定无关的关键字参数。 It also allows for optional args.它还允许使用可选参数。

>>> Foo(a = 1, b = 2)
Foo(a=1, optional=3)

attrs detects the explicit init method (due to auto_detect=True ) and still creates the init function, but calls it __attrs_init__ . attrs检测到显式 init 方法(由于auto_detect=True )并仍然创建 init 函数,但将其__attrs_init__ This allows you do define your own init function to do preprocessing and then call __attrs_init__ when you are done.这允许您定义自己的 init 函数来进行预处理,然后在完成后调用__attrs_init__

>>> import inspect
>>> print(inspect.getsource(Foo.__attrs_init__))
def __attrs_init__(self, a, optional=attr_dict['optional'].default):
    self.a = a
    self.optional = optional
class FromDictMixin:
    @classmethod
    def from_dict(cls, data: dict):
        return cls(**{
            a.name: data[a.name]
            for a in cls.__attrs_attrs__
        })

@attrs
class Foo(FromDictMixin):
    a = attrib()

It works, but it looks kinda ugly.它有效,但它看起来有点难看。 I was hopping that attrs lib had out of the box solution.我希望attrs lib 有开箱即用的解决方案。

This seems to be more of a question of serialization/deserialization/validation and attrs is quite strict on its argument for multiple reasons.这似乎更像是一个序列化/反序列化/验证的问题,并且由于多种原因, attrs 对其论点非常严格。 One of them is typing (as in types, not pressing keys :)) and the other is robustness/debugabiity.其中一个是打字(如类型,而不是按键:)),另一个是健壮性/可调试性。 Ignoring arguments that you might have just misspelt can lead to very frustrating moments.忽略您可能拼错的论点可能会导致非常令人沮丧的时刻。 It's better to move this kind of stuff into a separate layer.最好将这种东西移到单独的层中。

You can find some possible tools for that in https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs .您可以在https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs找到一些可能的工具。

I had to do something similar but I didn't want to write a custom __init__ method for every class.我必须做类似的事情,但我不想为每个类编写一个自定义的__init__方法。 So I created a decorator where it would attach an __init__ method to the class before instantiation then wrap in attrs.define decorator.所以我创建了一个装饰器,它会在实例化之前__init__方法附加到类,然后包装在attrs.define装饰器中。

This is just an example but does what you want.这只是一个示例,但可以执行您想要的操作。

import attrs


def define(cls):
    def __init__(cls, **kwargs):
        filtered = {}
        for attr in cls.__attrs_attrs__:
            if attr.name in kwargs:
                filtered[attr.name] = kwargs[attr.name]
        cls.__attrs_init__(**filtered)

    def wrapper(*args, **kwargs):
        nonlocal cls
        cls.__init__ = __init__
        cls = attrs.define(cls)
        return cls(*args, **kwargs)

    return wrapper


@define
class Booking:
    id: int
    id_hash: str


booking = {"id": 1, "id_hash": "a3H33lk", "foo": "bar"}
b = Booking(**booking)
print(b)
# Booking(id=1, id_hash='a3H33lk')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM