如何在不首先定义它们的情况下使用路径python特殊方法?

时间:2014-10-13 06:10:44

标签: python

为什么以下

class A(object): pass
class B: pass

def p(x): print x

a = A()
b = B()

a.__enter__ = lambda *a, **k: p('a.enter')
b.__enter__ = lambda *a, **k: p('b.enter')
a.__exit__  = lambda *a, **k: p('a.exit')
b.__exit__  = lambda *a, **k: p('b.exit')

print 'direct call'
a.__enter__()
b.__enter__()
a.__exit__()
b.__exit__()

print 'with statement'
with a: pass
with b: pass

打印以下内容,但也会抛出异常?

direct call
a.enter
b.enter
a.exit
b.exit
with statement
Traceback (most recent call last):
  File "lol.py", line 21, in <module>
    with a: pass
AttributeError: __exit__

GamesBrainiac是正确的,我希望猴子修补一个实例。这样做的原因是使用logging标准库的标准方法是getLoggerClass / setLoggerClassgetLogger的组合。使用前一对将覆盖所有未来的行为(不可取的),并使用后者似乎阻止我猴子修补对象具有特殊行为。

4 个答案:

答案 0 :(得分:0)

更新回复

你们lambda是未绑定的方法。传递a时,它使用类的__exit__方法,而不是实例。由于该类没有方法,因此属性错误。

演示:

class C(object):
    def __enter__(self):
        print 'entered'
    def __exit__(self, exc_type, exc_value, traceback):
        print self
        print exc_type
        print 'exiting'

c = C()
with c:
    print 'inside context'

打印:

entered
inside context
<__main__.C object at 0x7fd885e4eed0>
None
exiting

但是当我们分配一个lambda时,我们仍然得到绑定方法:

c.__exit__ = lambda *args: p('lambda exit')
with c:
    print 'inside context'

打印:

entered
inside context
<__main__.C object at 0x7fd885e4eed0>
None
exiting

你可以使用模拟库来模拟类方法(我必须从带有`sudo apt-get install python-mock的python 2的Ubuntu repos中获取它),在Python 3的标准库中使用unittest.mock :

import mock # unittest.mock in Python 3
with mock.patch.object(C, '__exit__', autospec=True) as mock_method:
    new_c = C()
    with new_c:
        print 'inside context'

# __exit__ is called with three None's if no exception
mock_method.assert_called_with(new_c, None, None, None)

打印:

entered
inside context

请注意,不再调用原始__exit__方法。

答案 1 :(得分:0)

显然,上下文管理器功能仅适用于类。您在实例上定义它们。来自python docs

  

Python的with语句支持由上下文管理器定义的运行时上下文的概念。这是使用一对方法实现的,这些方法允许用户定义的定义在语句体执行之前输入的运行时上下文,并在语句结束时退出:

然而,我确实提出了这个我觉得适合你的黑客行为:

class A:
    pass

def p(x):
    print(x)

a = A()

class MonkeyPatched(a.__class__):
    pass

MonkeyPatched.__enter__ = lambda *a, **k: p('a.enter')
MonkeyPatched.__exit__  = lambda *a, **k: p('a.exit')

a.__class__ = MonkeyPatched

with a:
    pass

a = A()

with a:
    pass

第一个with有效,但不是第二个。

这相当丑陋,我不确定是否存在设置__class__的副作用。

答案 2 :(得分:0)

你必须&#34; monkey-patch&#34;相应实例的类:

new_class = type(a.__class__.__name__, (a.__class__,), {})
new_class.__enter__ = ...
new_class.__exit__ = ...
a.__class__ = new_class

答案 3 :(得分:0)

试试这个,因为__enter__绑定到__class__

from types import MethodType


class A(object):
    pass




class B(object):
    pass

a = A()
b = B()


def p(x):
    print(x)


def en(self, *args):
    p('starting')
    return self


def ex(self, exc_type, exc_value, traceback):
    p('finishing')
    return False

a.__class__.__enter__ = MethodType(en, a)
a.__class__.__exit__ = MethodType(ex, a)

with a:
    print("Inside stuff")