我读过很多有关 Python 属性的内容,并且了解它们的好处,并且它们通常比 getter/setter 更 Pythonic。但是,我没有发现任何提及它们的用法更容易出现拼写错误,从而导致用户出现意外行为,我想了解如何缓解这一潜在问题。
考虑以下示例:
import datetime
import dateutil
class Person:
def __init__(self, date_of_birth):
self.date_of_birth = date_of_birth
@property
def date_of_birth(self):
return self._date_of_birth
@date_of_birth.setter
def date_of_birth(self, value):
# <Some checks about value>
self._date_of_birth = datetime.datetime.strptime(value, '%d-%m-%Y').date()
def compute_age(self):
# Some computations involving today's date and self._date_of_birth
return dateutil.relativedelta.relativedelta(datetime.date.today(), self.date_of_birth).years
# Compute age of someone
person = Person("15-06-1985")
person.compute_age() # Returns 38
# Update date of birth
person.date_of_birth = "17-08-2001"
person.compute_age() # Returns 22
# Update wrong attribute
person.date_of_brith = "25-11-1999" # No error or warning raised
person.compute_age() # Still returns 22
最后一段代码仍然完全有效,但是
person
现在有一个无用的date_of_brith
属性,person.compute_age()
不使用该属性,而开发人员的意图是更新person
的实际出生日期。由于它不会生成警告或错误,因此此类错误可能不会被发现,并会产生难以在更复杂的应用程序中调试的意外结果。使用 setter 方法 person.set_date_of_birth()
而不是 person.date_of_birth
属性可以防止此类拼写错误,因为方法名称中的拼写错误会在执行时产生异常。因此,我的问题是,这不是提倡使用 setter 方法而不是属性吗?否则,在使用属性时是否有一种干净的方法来减轻这种拼写错误?
可以向实例添加任意属性,这是 Python 的一个基本属性。 (也有例外,但我们不必在这里关心它们。)
为了防止此类意外创建,我们可以重写
__setattr__
来定义 person.XXX = ...
的含义。在此方法中,您可以检查分配给的属性是否有效。
class Person:
def __init__(self, date_of_birth):
self.date_of_birth = date_of_birth
def __setattr__(self, name, value):
if name not in ["_date_of_birth"]:
raise AttributeError(f"Cannot create attribute named '{name}'")
super().__setattr__(name, value)
...
注意,我们不需要将
date_of_birth
添加到白名单中,因为不存在名为date_of_birth
的实例属性。当尝试分配给
person.date_of_birth
时,默认行为是调用 Person.date_of_birth.__set__
(如果存在)而不是创建实例属性。
也就是说,我会考虑是否值得让你的代码进行这样的运行时检查,而不是让用户负责适当地测试他们的代码。
您可以使用
__slots__
类变量来预定义允许/期望的属性:
class Person:
__slots__ = ('date_of_birth',)
...
尝试设置插槽中未定义的属性会引发 AttributeError。您是否想对所有课程执行此操作只是为了捕获拼写错误取决于您。
编辑:如果您还使用 @property 的 getter 和 setter,则此方法不起作用。