And*_*ndy 19 python class instance-variables instance python-attrs
我有不错的编程水平,并从这里的社区中获得了很多价值。然而,我从来没有在编程方面进行过太多的学术教学,也没有与真正有经验的程序员一起工作。因此,我有时会为“最佳实践”而苦恼。
对于这个问题,我找不到更好的地方,尽管可能有讨厌这些问题的喷子,我还是发布了这个。如果这让你感到不安,很抱歉。我只是想学习,而不是惹你生气。
题:
当我创建一个新类时,我是否应该在init 中设置所有实例属性,即使它们是 None 并且实际上后来在类方法中分配了值?
MyClass 的属性结果见下例:
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
Run Code Online (Sandbox Code Playgroud)
我在其他项目中发现,当类属性只出现在类方法中时,它们可能会被埋没,而且还有很多事情要做。
那么对于经验丰富的专业程序员来说,什么是标准做法呢?为了可读性,您会在init 中定义所有实例属性吗?
如果有人有任何关于我可以在哪里找到这些原则的材料的链接,那么请将它们放在答案中,我们将不胜感激。我知道 PEP-8 并且已经在上面搜索了我的问题好几次,但找不到任何涉及此问题的人。
谢谢
安迪
jfe*_*ard 18
我认为你应该避免这两种解决方案。仅仅因为您应该避免创建未初始化或部分初始化的对象,除非我将在稍后概述一种情况。
看看你的类的两个稍微修改过的版本,一个 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
Run Code Online (Sandbox Code Playgroud)
和
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
Run Code Online (Sandbox Code Playgroud)
MyClass1和之间的唯一区别MyClass2是第一个results在构造函数中初始化,而第二个在set_results. 你的班级的用户来了(通常是你,但不总是)。每个人都知道你不能信任用户(即使是你):
MyClass1("df").get_results()
# returns None
Run Code Online (Sandbox Code Playgroud)
或者
MyClass2("df").get_results()
# Traceback (most recent call last):
# ...
# AttributeError: 'MyClass2' object has no attribute 'results'
Run Code Online (Sandbox Code Playgroud)
您可能认为第一种情况更好,因为它不会失败,但我不同意。在这种情况下,我希望程序快速失败,而不是进行长时间的调试会话来查找发生了什么。因此,第一个答案的第一部分是:不要将未初始化的字段设置为None,因为您会丢失快速失败提示。
但这不是全部的答案。无论您选择哪个版本,都会遇到一个问题:对象没有被使用,也不应该被使用,因为它没有完全初始化。您可以将文档字符串添加到get_results:"""Always use set_results **BEFORE** this method"""。不幸的是,用户也不阅读文档字符串。
您的对象中未初始化的字段有两个主要原因: 1. 您(目前)不知道该字段的值;2. 您想避免扩展操作(计算、文件访问、网络等),即“延迟初始化”。这两种情况在现实世界中都会遇到,并且与仅使用完全初始化的对象的需求相冲突。
令人高兴的是,这个问题有一个有据可查的解决方案:设计模式,更准确地说是创建模式。在您的情况下,Factory 模式或 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
Run Code Online (Sandbox Code Playgroud)
(您也可以使用 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'
Run Code Online (Sandbox Code Playgroud)
优点很明显:
Builder 中存在未初始化的字段并不矛盾:这些字段在设计上是未初始化的,因为 Builder 的作用是初始化它们。(实际上,这些字段对于 Builder 来说是某种外部字段。)这就是我在介绍中谈到的情况。在我看来,如果您尝试创建一个不完整的对象,它们应该被设置为默认值(如果存在)或保持未初始化以引发异常。
我回答的第二部分:使用创建模式来确保对象被正确初始化。
旁注:当我看到一个带有 getter和setter的类时,我非常怀疑。我的经验法则是:总是尝试将它们分开,因为当它们相遇时,物体会变得不稳定。
在与有经验的程序员进行大量研究和讨论之后,请参阅下面我认为对这个问题最 Pythonic 的解决方案。我首先包含了更新的代码,然后是一个叙述:
class MyClass:
def __init__(self,df):
self.df = df
self._results = None
@property
def results(self):
if self._results is None:
raise Exception('df_client is None')
return self._results
def generate_results(self, df_results):
#Imagine some calculations here or something
self._results = df_results
Run Code Online (Sandbox Code Playgroud)
描述我学到的、改变的以及为什么:
所有类属性都应包含在init(构造函数)方法中。这是为了确保可读性和帮助调试。
第一个问题是你不能在 Python 中创建私有属性。一切都是公开的,因此可以访问任何部分初始化的属性(例如将结果设置为 None)。指示私有属性的约定是在前面放置一个引导下划线,因此在这种情况下,我将其更改为 self.results 到 self.**_**results
请记住,这只是约定,self._results 仍然可以直接访问。然而,这是处理伪私有属性的 Pythonic 方式。
第二个问题是将部分初始化的属性设置为无。正如下面的@jferard 所解释的,由于这被设置为 None,我们现在已经失去了一个快速失败的提示,并为调试代码添加了一层混淆。
为了解决这个问题,我们添加了一个 getter 方法。这可以在上面看到为具有 @property 装饰器的函数results()。
这是一个在调用时检查 self._results 是否为 None 的函数。如果是这样,它将引发异常(故障安全提示),否则将返回该对象。@property 装饰器将调用样式从函数更改为属性,因此用户必须在 MyClass 实例上使用的所有.results就像任何其他属性一样。
(我将设置结果的方法的名称更改为 generate_results() 以避免混淆并为 getter 方法释放 .results)
如果类中有其他方法需要使用 self._results,但只有在正确分配的情况下,您才能使用 self.results,这样故障安全提示就如上所示。
我还建议阅读@jferard 对这个问题的回答。他深入探讨了问题和一些解决方案。我添加我的答案的原因是我认为在很多情况下,以上就是你所需要的(以及 Pythonic 的做法)。
| 归档时间: |
|
| 查看次数: |
20504 次 |
| 最近记录: |