【问题标题】:__pre_init__ functionalty in python?python中的__post_init__功能?
【发布时间】:2020-11-04 14:01:42
【问题描述】:

我想让字符串比较不区分大小写。 为此,我想创建一个只有一个字符串字段的不可变类。 在构造函数中,我想在将值分配给字段之前调用 lower()。

我想尽可能多地使用标准类,如namedtuple 或dataclass。 使用 post_init 函数(例如,参见 How to use the __post_init__ method in Dataclasses in Python)感觉就像是 hack。 这也让我想知道我在 post_init 函数中更改字段后是否仍然冻结。

但是,我找不到 pre_init 函数。 有没有更好的办法?

【问题讨论】:

  • "我想在将值分配给字段之前调用 lower()" -- 那么,有什么阻止你这样做吗?如果是这样,请详细说明。如果不是,还有什么问题?
  • 为什么你的常规初始化中的 .lower() 不够?只是这里的业余爱好者,所以也许我缺乏一些 SE 知识...
  • A pre_init - 在__init__ 之前调用的方法是__new__。在这里查看更多spyhce.com/blog/understanding-new-and-init
  • new是类的初始化,init是实例的初始化。所有实例都应转换为较低的。
  • 例如,将 NamedTuple 的 init 覆盖为 ```` class Name(NamedTuple): name: str def init__(self, name: str) - > None: def canonical_representation() -> str: return name.lower() self.name: str = canonical_representation() ``` 不允许:``` File "C:\Users\laarpjljvd\AppData\Local\Programs \Python\Python39\lib\typing.py",第 1775 行,在 __new 中引发 AttributeError("Cannot overwrite NamedTuple attribute " + key) ``

标签: python


【解决方案1】:

事实证明,数据类没有提供我正在寻找的功能。 但 Attrs 确实如此:

from attr import attrs, attrib


@attrs(frozen=True)
class Name:
    name: str = attrib(converter=str.lower)

【讨论】:

    【解决方案2】:

    澄清:如果我对问题的解释正确,那么您特别希望使用带有 stdlib dataclasses.dataclass(frozen=True) 注释的类。具体来说,对于您的情况,您可能会有这样的情况:

    from dataclasses import dataclass
    
    
    @dataclass(frozen=True)
    class HasStringField:
        some_string: str
    
    
    instance = HasStringField("SOME_STRING")
    instance.some_string  # value: "SOME_STRING"
    

    我的理解是你想要实现一些东西,使得上面instance.some_string 中的值实际上是"some_string"


    答案: 没有“内置”方式来做你想做的事,但我想到了两个选项:在 __post_init__ 中使用 object.__setattr__ 或使用替代方法装饰器为您提供了一种创建自定义构造函数的方法,该构造函数仍然调用由dataclass 创建的构造函数。

    选项 1:在 __post_init__ 中使用 object.__setattr__

    @dataclasses.dataclass(frozen=True)
    class HasStringField:
        some_string: str
    
        def __post_init__(self):
            object.__setattr__(self, "some_string", self.some_string.lower())
    
    
    inst = HasStringField("SOME_STRING")
    inst.some_string  # value: "some_string"
    

    选项 2:自定义装饰器

    an answer to a similar question 中的 dataclass_with_default_init 装饰器提供了一个这样的装饰器。

    使用 dataclass_with_default_init 装饰器,您可以:

    # Import / implement the decorator
    
    @dataclass_with_default_init(frozen=True)
    class HasStringField:
        some_string: str
    
        def __init__(self, some_string: str):
            self.__default_init__(some_string=some_string.lower())
    
    
    instance = HasStringField("SOME_STRING")
    instance.some_string  # value: "some_string"
    

    ...关于这个类的所有其他内容都将保持不变,就像您使用了 dataclass 装饰器一样。

    【讨论】:

      【解决方案3】:

      这应该是最简单的方法之一,你提到的字段是内部的data(继承自UserString类):

      from collections import UserString
      
      class LowerStr(UserString):
          def __init__(self, value):
              super().__init__(value)
              self.data = self.data.lower()
      
      s1 = LowerStr("ABC")
      print("s1: {}".format(s1))
      print("s1 class: {}".format(s1.__class__))
      print("s1 class mro: {}".format(s1.__class__.mro()))
      

      输出:

      s1: abc
      s1 class: <class '__main__.LowerStr'>
      s1 class mro: [<class '__main__.LowerStr'>, <class 'collections.UserString'>, <class 'collections.abc.Sequence'>, <class 'collections.abc.Reversible'>, <class 'collections.abc.Collection'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Iterable'>, <class 'collections.abc.Container'>, <class 'object'>]
      

      这为您提供了来自str 的所有方法,您可以随意自定义它们。

      如果你更喜欢子类,那么内部没有data属性:

      class InsensitiveStr(str):
          def __new__(cls, value):
              if isinstance(value, str):
                  return super().__new__(cls, value.lower())
              else:
                  raise ValueError
      
      s1 = InsensitiveStr("ABC")
      print(s1)
      print(type(s1))
      print(s1.__class__)
      print(s1.__class__.__mro__)
      

      注意 mro 的差异。

      输出

      abc
      <class '__main__.InsensitiveStr'>
      <class '__main__.InsensitiveStr'>
      (<class '__main__.InsensitiveStr'>, <class 'str'>, <class 'object'>)
      

      【讨论】:

      • 这个类不是不可变的。可以更改数据字段的值。
      • s1 就像一个不可变的字符串。当然除了s1.data。您也可以使用相同的想法进行子类化。查看我的编辑。
      • 类似讨论请参见here
      【解决方案4】:

      注意:我仍然认为最好的方法是使用上面提到的__post_init__ / object.__set_attribute__ 或使用像https://www.attrs.org/en/stable/ 这样的外部库)

      我在阅读数据类本身的 python 代码时看到的一种模式是使用同名但在蛇形情况下的函数来执行这些类型的更改。例如:

      @dataclass(frozen=True)
      class HasStringField:
        some_string: str
      
      
      def has_string_field(some_string: str) -> HasStringField:
        return HasStringField(some_string.lower())
      
      

      那么你总是导入和使用has_string_field() 而不是HasStringField()。 (这种模式的一个例子是数据类field vs Field):

      它的缺点是冗长并且使类不变量可以绕过。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-10
        • 2018-08-06
        • 2013-07-27
        相关资源
        最近更新 更多