python - 在 __init__ 中为用户类设置默认/空属性

标签 python class instance instance-variables python-attrs

我的编程水平不错,并且从这里的社区中获得了很多值(value)。然而,我从来没有接受过太多编程方面的学术教学,也没有在真正有经验的程序员旁边工作过。因此,我有时会与“最佳实践”作斗争。

问题:

当我创建一个新类时,我是否应该在 __init__ 中设置所有实例属性,即使它们是 None 并且实际上后来在类方法中赋值?

MyClassresults 属性见下面的例子:

class MyClass:
    def __init__(self,df):
          self.df = df
          self.results = None

    def results(df_results):
         #Imagine some calculations here or something
         self.results = df_results

我在其他项目中发现,当类属性仅出现在类方法中时,它们可能会被掩埋,并且有很多事情要做。

那么对于经验丰富的专业程序员来说,标准做法是什么?为了可读性,您会在 __init__ 中定义所有实例属性吗?

如果有人有关于我在哪里可以找到这些原则的 Material 的任何链接,那么请把它们放在一个答案中,我们将不胜感激。我知道 PEP-8 并且已经多次搜索我的问题,但找不到任何人涉及这个。

谢谢

安迪

最佳答案

我认为您应该避免这两种解决方案。仅仅是因为您应该避免创建未初始化或部分初始化的对象,我稍后将概述的一种情况除外。

看看你的类的两个稍微修改的版本,有一个 setter 和一个 getter:

class MyClass1:
    def __init__(self, df):
          self.df = df
          self.results = None

    def set_results(self, df_results):
         self.results = df_results

    def get_results(self):
         return self.results

class MyClass2:
    def __init__(self, df):
          self.df = df

    def set_results(self, df_results):
         self.results = df_results

    def get_results(self):
         return self.results

MyClass1MyClass2 之间的唯一区别是第一个在构造函数中初始化 results 而第二个在 中初始化设置结果。你类(class)的用户来了(通常是你,但不总是)。每个人都知道您不能信任用户(即使是您):

MyClass1("df").get_results()
# returns None

或者

MyClass2("df").get_results()
# Traceback (most recent call last):
# ...
# AttributeError: 'MyClass2' object has no attribute 'results'

你可能会认为第一种情况更好,因为它不会失败,但我不同意。在这种情况下,我希望程序能够快速失败,而不是进行长时间的调试以找出发生了什么。因此,第一个答案的第一部分是:不要将未初始化的字段设置为None,因为您失去了快速失败提示

但这不是全部答案。无论您选择哪个版本,都会遇到一个问题:该对象未被使用,也不应该被使用,因为它没有完全初始化。您可以将文档字符串添加到 get_results:"""Always use set_results **BEFORE** this method"""。不幸的是,用户也不会阅读文档字符串。

您的对象中存在未初始化字段的主要原因有两个: 1. 您(暂时)不知道该字段的值; 2. 您想避免扩展操作(计算、文件访问、网络……),又名“惰性初始化”。这两种情况在现实世界中都会遇到,并且与仅使用完全初始化对象的需求相冲突。

幸运的是,这个问题有一个有据可查的解决方案:设计模式,更准确地说是 Creational patterns .在您的情况下,工厂模式或 builder 模式可能就是答案。例如:

class MyClassBuilder:
    def __init__(self, df):
          self._df = df # df is known immediately
          # GIVE A DEFAULT VALUE TO OTHER FIELDS to avoid the possibility of a partially uninitialized object.
          # The default value should be either:
          # * a value passed as a parameter of the constructor ;
          # * a sensible value (eg. an empty list, 0, etc.)

    def results(self, df_results):
         self._results = df_results
         return self # for fluent style
         
    ... other field initializers

    def build(self):
        return MyClass(self._df, self._results, ...)

class MyClass:
    def __init__(self, df, results, ...):
          self.df = df
          self.results = results
          ...
          
    def get_results(self):
         return self.results
    
    ... other getters
         

(您也可以使用 Factory,但我发现 Builder 更灵活)。让我们给用户第二次机会:

>>> b = MyClassBuilder("df").build()
Traceback (most recent call last):
...
AttributeError: 'MyClassBuilder' object has no attribute '_results'
>>> b = MyClassBuilder("df")
>>> b.results("r")
... other fields iniialization
>>> x = b.build()
>>> x
<__main__.MyClass object at ...>
>>> x.get_results()
'r'

优点很明显:

  1. 检测和修复创建失败比后期使用失败更容易;
  2. 您不会在野外发布您的对象的未初始化(因此可能具有破坏性)版本。

Builder 中存在未初始化的字段并不矛盾:这些字段在设计上是未初始化的,因为 Builder 的作用是初始化它们。 (实际上,这些字段是 Builder 的某种 forein 字段。)这就是我在介绍中谈到的情况。在我看来,它们应该设置为默认值(如果存在)或保持未初始化状态,以便在您尝试创建不完整的对象时引发异常。

我的回答的第二部分:使用创建模式来确保对象被正确初始化

旁注:当我看到一个包含 getters setters 的类时,我非常怀疑。我的经验法则是:始终尝试将它们分开,因为当它们相遇时,物体会变得不稳定。

关于python - 在 __init__ 中为用户类设置默认/空属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55800218/

相关文章:

ios - 从另一个类快速调用文本字段

javascript - 将当前 Controller 实例传递给模态服务

python - 在 Python 中从一个目录(包含大量文件)中选择一个随机文件

Python 解释器和脚本输出不同的结果

python - 给定范围内的数字如果循环python

ruby - 子类实例变量在 Ruby 中更改父类(super class)实例变量

jquery - 如何为 jQuery 小部件实例创建唯一的 id?

python - 使用Python将ini文件中的所有内容读入字典

c++ - 这是某种指针错误吗?

c++ - 缺少类型说明符,编译器将 Class* 更改为 int*