在Python中,如果调用不存在的方法,则会抛出AttributeError。例如
#! /usr/bin/python3
import argparse
import boto3
from datetime import datetime
from datetime import timezone
def build_argparser():
parser = argparse.ArgumentParser(description='List S3 buckets by file date.')
parser.add_argument('-p', '--profile', help='Profile to use')
return parser
if __name__ == "__main__":
parser = build_argparser()
args = parser.parse_args()
if args.profile == None:
s3 = boto3.resource('s3')
else:
profile = boto3.session.Session(profile_name=args.profile)
s3 = profile.resource('s3')
for bucket in s3.buckets.all():
print(bucket.name)
latest_key = ""
latest_datetime = datetime
for object in bucket.objects.all():
#print('\t' + str(object.key) + ': ' + str(object.last_modified))
if latest_datetime == datetime or latest_datetime < object.last_modified:
latest_key = object.key
latest_datetime = object.last_modified
print('\t' + str(latest_key) + ': ' + str(latest_datetime))
在下面的代码中,MagicMock类没有名为hello的函数,或者未为方法hello创建任何补丁。仍然在代码下面并不会引发AttributeError
>>> class A:
... def yo(self):
... print(1)
...
>>> a = A()
>>> a.yo()
1
>>> a.hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hello'
MagicMock如何做到这一点?当调用任何方法(可能未定义)时,如何创建可以执行操作的类?
答案 0 :(得分:2)
Python数据模型记录了一个钩子__getattr__
,当属性访问无法以通常的方式解析时,应调用该钩子。模仿者使用它来返回一个新的模仿实例-即,模仿将未知属性定义为factories。
以更简单的方式重现模拟的实现,您只需将__getattr__
和__call__
转换为工厂函数:
class M:
def __call__(self):
return M()
def __getattr__(self, name):
return M()
用法示例:
>>> mock = M()
>>> mock.potato
<__main__.M at 0xdeadbeef>
>>> mock.potato()
<__main__.M at 0xcafef00d>
MagicMock如何做到这一点?
这部分不专门针对MagicMock
,普通的Mock
也会做到这一点(名称中的“魔术”只是指可以更好地模拟magic methods的其他功能) 。 MagicMock
inherits such behavior from one of the base classes:
>>> MagicMock.mro()
[unittest.mock.MagicMock,
unittest.mock.MagicMixin,
unittest.mock.Mock,
unittest.mock.CallableMixin,
unittest.mock.NonCallableMock, # <--- this one overrides __getattr__!
unittest.mock.Base,
object]
如何创建一个可以在调用任何方法(可能未定义)时执行操作的类?
这取决于您要在普通属性访问之前还是之后。如果要排在最前面,则应定义__getattribute__
,在搜索类/实例名称空间之前,将无条件调用它来实现属性访问。但是,如果您要优先考虑普通属性(即生活在对象__dict__
中的属性)和descriptors的优先级,则应按照前面的讨论定义__getattr__
。
答案 1 :(得分:1)
我实际上不知道MagicMock
的工作原理(我从未使用过,但是我听说过好东西),但是这一部分行为可以复制(可能还有其他多种可能)解决方案),通过劫持__getattr__
的方式,使其返回一个可调用对象,该对象在被调用时会创建一个新的模拟实例:
class MM:
def __init__(self, name=None):
# store a name, TODO: random id, etc.
self.name = name
def __repr__(self):
# make it pretty
if self.name:
r = f'<MM name={self.name}>'
else:
r = f'<MM>'
return r
def __getattr__(self, attrname):
# we want a factory for a mock instance with a name corresponding to attrname
def magicattr():
return MM(name=f"'mock.{attrname}()'")
return magicattr
执行后,我们将看到以下内容:
>>> MM()
<MM>
>>> MM().hello()
<MM name='mock.hello()'>
我并没有为定义id
而过分,但基本技巧可以在上面的精简示例中看到。
以上方法的工作方式是访问.hello
或任何其他属性都经过我们的自定义__getattr__
,这使我们有机会即时生成一个伪造的(模拟的)方法,无论我们使用什么属性想要。据我了解,MagicMock
的众多好处之一就是我们不必担心默认情况下会抛出AttributeError
,它正常工作。