使用以下方法混合datetime.strptime()
格式字符串和日期字符串参数是一个很常见的错误:
datetime.strptime("%B %d, %Y", "January 8, 2014")
而不是相反:
datetime.strptime("January 8, 2014", "%B %d, %Y")
当然,它会在运行时失败:
>>> datetime.strptime("%B %d, %Y", "January 8, 2014")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/_strptime.py", line 325, in _strptime
(data_string, format))
ValueError: time data '%B %d, %Y' does not match format 'January 8, 2014'
但是,即使在实际运行代码之前,是否有可能静态地捕获静态这个问题?是pylint
还是flake8
可以提供帮助?
我已经尝试过PyCharm代码检查,但两个代码段都没有发出任何警告。可能,因为两个参数都具有相同的类型 - 它们都是字符串,这使得问题更加困难。我们必须实际分析字符串是否是日期时间格式字符串。此外,Language Injections PyCharm / IDEA功能看起来很相似。
答案 0 :(得分:17)
我声称无法在一般情况下静态检查 。
请考虑以下代码段:
d = datetime.strptime(read_date_from_network(), read_format_from_file())
此代码可能完全有效,其中read_date_from_network
和read_format_from_file
确实返回正确格式的字符串 - 或者它们可能是完全垃圾,都返回None或某些废话。无论如何,该信息只能 在运行时确定 - 因此,静态检查器无能为力。
更重要的是,鉴于datetime.strptime的当前定义,即使我们 使用静态类型语言,我们也无法捕获此错误(除非在非常具体的情况下) - - 原因是此功能的签名从一开始就注定了我们:
classmethod datetime.strptime(date_string, format)
在此定义中,date_string
和format
都是字符串,即使它们实际上具有特殊含义。即使我们在这种静态类型的语言中有类似的东西:
public DateTime strpTime(String dateString, String format)
编译器(以及linter和其他所有人)仍然只看到:
public DateTime strpTime(String, String)
这意味着以下任何一个都不能相互区分:
strpTime("%B %d, %Y", "January 8, 2014") // strpTime(String, String) CHECK
strpTime("January 8, 2014", "%B %d, %Y") // strpTime(String, String) CHECK
strpTime("cat", "bat") // strpTime(String, String) CHECK
这并不是说它根本无法完成 - 对于静态类型语言(例如Java / C ++ /等)确实存在一些短语。当你将它们传递给某些特定的函数(比如printf等)时会检查字符串文字,但这只能在你用文字格式字符串直接调用该函数时才能完成。在我提出的第一个案例中,同样的短语变得无助,因为它还不知道字符串是否是正确的格式。
即。一个短信可能会警告这个:
// Linter regex-es the first argument, sees %B et. al., warns you
strpTime("%B %d, %Y", "January 8, 2014")
但它无法警告这一点:
strpTime(scanner.readLine(), scanner.readLine())
现在,同样可以设计成python linter,但我不相信它会非常有用,因为函数是一流的,所以我可以通过编写来轻松击败(假设的python)linter:
f = datetime.strptime
d = f("January 8, 2014", "%B %d, %Y")
然后我们再次受到冲击。
奖金:错在哪里
这里的问题是datetime.strptime
给每个字符串赋予隐含意义,但它没有将该信息表示给类型系统。可以做的是给两个不同类型的字符串 - 然后可以有更多的安全性,虽然以牺牲一些易用性为代价。
例如(使用PEP 484类型注释,a real thing!):
class DateString(str):
pass
class FormatString(str):
pass
class datetime(date):
...
def strptime(date_string: DateString, format: FormatString) -> datetime:
# etc. etc.
然后在一般情况下提供良好的linting开始是可行的 - 尽管DateString和FormatString类需要负责验证他们的输入,因为类型系统不能在那个级别做任何事情
后记:
我认为处理此问题的最佳方法是使用strftime
方法来避免此问题,该方法绑定到特定的日期时间对象并仅采用格式字符串参数。通过给我们一个功能签名来避开整个问题,当我们拥抱它时,它不会削弱我们。耶。