假设我想为类中定义的方法创建装饰器。我希望装饰器在被调用时能够在定义方法的类上设置属性(为了在一个特定用途的方法列表中注册它)。
在Python 2中,im_class
方法可以很好地完成这个任务:
def decorator(method):
cls = method.im_class
cls.foo = 'bar'
return method
然而,在Python 3中,似乎不存在这样的属性(或替代它)。我想这个想法是你可以调用type(method.__self__)
来获取类,但这对于未绑定的方法不起作用,因为在这种情况下__self__ == None
。
注意:这个问题实际上与我的情况有点无关,因为我选择在方法本身上设置属性,然后让实例扫描其所有方法该属性在适当的时间。我(目前)也在使用Python 2.6。但是,我很好奇是否有替换版本2的功能,如果没有,那么完全删除它的理由是什么。
编辑:我刚刚找到this question。这使得看起来最好的解决方案就是像我一样避免它。我仍然想知道为什么它被删除了。
答案 0 :(得分:57)
我认为编写一些能够最好地猜测定义类的东西是值得的。为了完整性'这个答案也解决了约束方法。
最糟糕的是,猜测应该完全失败,函数返回None
。但是,在任何情况下,它都不应该引发异常或返回错误的类。
我们的函数的最终版本成功地克服了大多数简单案例,以及一些陷阱。
简而言之,它的实现区分了绑定方法和“unbound methods“ (functions),因为在Python 3
中没有可靠的方法从“未绑定的方法”中提取封闭类。
MRO
中类似的方式遍历accepted answer to an equivalent question for Python 2
。Python 3.3
并且非常鲁莽(如果此功能是不必要的,它可能最好删除此块代码,只需返回None
。对于通过描述符定义的方法也有部分处理,这些方法不被分类为普通方法或函数(例如,set.union
,int.__add__
和int().__add__
但不是{ {1}})。
结果函数是:
set().union
如果您决定使用此实施,并遇到任何警告,请评论并描述发生的事情。
首先,值得注意的是def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
meth = meth.__func__ # fallback to __qualname__ parsing
if inspect.isfunction(meth):
cls = getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
if isinstance(cls, type):
return cls
return getattr(meth, '__objclass__', None) # handle special descriptor objects
中的change {(3}}(参见圭多的动机here):
已从语言中删除“未绑定方法”的概念。当将方法作为类属性引用时,您现在将获得一个普通的函数对象。
这使得实际上不可能可靠地提取定义了某个“未绑定方法”的类,除非它被绑定到该类(或其子类之一)的对象。
所以,让我们首先处理我们有一个绑定方法的“更容易的情况”。请注意,绑定方法必须用Python 3
编写,如inspect.ismethod
's documentation。
Python
然而,这个解决方案并不完美并且有其危险,因为方法可以在运行时分配,使得它们的名称可能与它们所分配的属性的名称不同(参见下面的示例)。 def get_class_that_defined_method(meth):
# meth must be a bound method
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
return None # not required since None would have been implicitly returned anyway
中也存在此问题。一种可能的解决方法是迭代所有类的属性,寻找其身份是指定方法的属性。
现在我们已经解决了这个问题,我们可以建议一个试图处理“未绑定方法”的黑客攻击。黑客,其理由和一些沮丧的话可以在this answer中找到。它依赖于手动解析the __qualname__
attribute,available only from Python 3.3
,这是非常不推荐的,但应适用于简单案例:
Python 2
由于def get_class_that_defined_method(meth):
if inspect.isfunction(meth):
return getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
return None # not required since None would have been implicitly returned anyway
和inspect.isfunction
是互斥的,因此将两种方法合并为一个解决方案会为我们提供以下内容(为即将到来的示例添加了日志记录功能):
inspect.ismethod
def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
print('this is a method')
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
if inspect.isfunction(meth):
print('this is a function')
return getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
print('this is neither a function nor a method')
return None # not required since None would have been implicitly returned anyway
到目前为止,这么好,但是......
>>> class A:
... def a(self): pass
...
>>> class B:
... def b(self): pass
...
>>> class C(A, B):
... def a(self): pass
...
>>> A.a
<function A.a at 0x7f13b58dfc80>
>>> get_class_that_defined_method(A.a)
this is a function
<class '__main__.A'>
>>>
>>> A().a
<bound method A.a of <__main__.A object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(A().a)
this is a method
<class '__main__.A'>
>>>
>>> C.a
<function C.a at 0x7f13b58dfea0>
>>> get_class_that_defined_method(C.a)
this is a function
<class '__main__.C'>
>>>
>>> C().a
<bound method C.a of <__main__.C object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(C().a)
this is a method
<class '__main__.C'>
>>>
>>> C.b
<function B.b at 0x7f13b58dfe18>
>>> get_class_that_defined_method(C.b)
this is a function
<class '__main__.B'>
>>>
>>> C().b
<bound method C.b of <__main__.C object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(C().b)
this is a method
<class '__main__.B'>
>>> def x(self): pass
...
>>> class Z:
... y = x
... z = (lambda: lambda: 1)() # this returns the inner function
... @classmethod
... def class_meth(cls): pass
... @staticmethod
... def static_meth(): pass
...
>>> Z.y
<function x at 0x7f13b58dfa60>
>>> get_class_that_defined_method(Z.y)
this is a function
<function x at 0x7f13b58dfa60>
>>>
>>> Z().y
<bound method Z.x of <__main__.Z object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(Z().y)
this is a method
this is neither a function nor a method
>>>
>>> Z.z
<function Z.<lambda>.<locals>.<lambda> at 0x7f13b58d40d0>
>>> get_class_that_defined_method(Z.z)
this is a function
<class '__main__.Z'>
>>>
>>> Z().z
<bound method Z.<lambda> of <__main__.Z object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(Z().z)
this is a method
this is neither a function nor a method
>>>
>>> Z.class_meth
<bound method type.class_meth of <class '__main__.Z'>>
>>> get_class_that_defined_method(Z.class_meth)
this is a method
this is neither a function nor a method
>>>
>>> Z().class_meth
<bound method type.class_meth of <class '__main__.Z'>>
>>> get_class_that_defined_method(Z().class_meth)
this is a method
this is neither a function nor a method
>>>
>>> Z.static_meth
<function Z.static_meth at 0x7f13b58d4158>
>>> get_class_that_defined_method(Z.static_meth)
this is a function
<class '__main__.Z'>
>>>
>>> Z().static_meth
<function Z.static_meth at 0x7f13b58d4158>
>>> get_class_that_defined_method(Z().static_meth)
this is a function
<class '__main__.Z'>
生成的结果可以部分修复(返回Z.y
),方法是在实际返回之前验证返回的值是否为类。None
生成的结果可以通过回退到解析函数的Z().z
属性来修复(该函数可以通过__qualname__
提取)。 meth.__func__
和Z.class_meth
生成的结果不正确,因为访问类方法始终返回绑定方法,其Z().class_meth
属性是类本身,而不是其对象。因此,进一步访问__self__
属性之上的__class__
属性并不能按预期工作:
__self__
可以通过检查方法的>>> Z().class_meth
<bound method type.class_meth of <class '__main__.Z'>>
>>> Z().class_meth.__self__
<class '__main__.Z'>
>>> Z().class_meth.__self__.__class__
<class 'type'>
属性是否返回__self__
的实例来解决此问题。但是,当我们的函数针对元类的方法被调用时,这可能会让人感到困惑,所以我们现在就把它保留下来。
以下是最终版本:
type
令人惊讶的是,这也解决了def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
meth = meth.__func__ # fallback to __qualname__ parsing
if inspect.isfunction(meth):
cls = getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
if isinstance(cls, type):
return cls
return None # not required since None would have been implicitly returned anyway
和Z.class_meth
现在正确返回Z().class_meth
的结果。这是因为类方法的Z
属性返回一个常规函数,其__func__
属性可以被解析:
__qualname__
修改强>
根据Bryce提出的问题,可以处理>>> Z().class_meth.__func__
<function Z.class_meth at 0x7f13b58d4048>
>>> Z().class_meth.__func__.__qualname__
'Z.class_meth'
个对象,例如method_descriptor
和set.union
个对象,例如wrapper_descriptor
},仅返回他们的__objclass__
属性(由PEP-252引入),如果存在的话:
int.__add__
但是,if inspect.ismethoddescriptor(meth):
return getattr(meth, '__objclass__', None)
会为相应的实例方法对象返回inspect.ismethoddescriptor
,即False
和set().union
:
int().__add__
返回int().__add__.__objclass__
,因此可以放弃上述if子句以解决int
的问题。不幸的是,这并没有解决int().__add__
的问题,因为没有定义set().union
属性。为了避免在这种情况下发生__objclass__
例外,AttributeError
属性不会直接访问,而是通过__objclass__
函数访问。答案 1 :(得分:35)
你似乎缺少的一点是,在Python 3中,“未绑定方法”类型已完全消失 - 一个方法,除非它被绑定,只是一个函数,没有奇怪的“类型检查”未绑定方法曾经执行过。这使语言更简单!
嗯......:
>>> class X:
... def Y(self): pass
...
>>> type(X.Y)
<class 'function'>
和瞧 - 一个不那么微妙的概念和区别担心。这种简化是Python 3与Python 2的核心优势,它(多年来)已经累积了如此多的微妙之处,以至于它处于危险之中(如果功能一直被添加到其中)真正失去其作为简单的状态语言。使用Python 3,简单就是返回! - )
答案 2 :(得分:4)
自python 3.6起,您可以使用装饰器定义__set_name__
来完成您所描述的内容。 The documentation指出创建类时会调用object.__set_name__
。
以下是装饰方法的示例,“以便将其注册到用于特定目的的方法列表中”:
>>> class particular_purpose:
... def __init__(self, fn):
... self.fn = fn
...
... def __set_name__(self, owner, name):
... owner._particular_purpose.add(self.fn)
...
... # then replace ourself with the original method
... setattr(owner, name, self.fn)
...
... class A:
... _particular_purpose = set()
...
... @particular_purpose
... def hello(self):
... return "hello"
...
... @particular_purpose
... def world(self):
... return "world"
...
>>> A._particular_purpose
{<function __main__.A.hello(self)>, <function __main__.A.world(self)>}
>>> a = A()
>>> for fn in A._particular_purpose:
... print(fn(a))
...
world
hello
请注意,这个问题与Can a Python decorator of an instance method access the class?非常相似,因此我对the answer I provided there的回答也是如此。
答案 3 :(得分:1)
python 3.6 的一个小扩展(python 2.7很好用),可以很好地解决https://stackoverflow.com/a/25959545/4013571
$currentCookieParams = session_get_cookie_params();
$rootDomain = '.example.com';
session_set_cookie_params(
$currentCookieParams["lifetime"],
$currentCookieParams["path"],
$rootDomain,
$currentCookieParams["secure"],
$currentCookieParams["httponly"]
);
session_name('mysessionname');
session_start();
setcookie($cookieName, $cookieValue, time() + 3600, '/', $rootDomain);
?>
我发现def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
meth = meth.__func__ # fallback to __qualname__ parsing
if inspect.isfunction(meth):
class_name = meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0]
try:
cls = getattr(inspect.getmodule(meth), class_name)
except AttributeError:
cls = meth.__globals__.get(class_name)
if isinstance(cls, type):
return cls
return None # not required since None would have been implicitly returned anyway
需要进行以下调整
doctest
由于某些原因,使用 except AttributeError:
cls = meth.__globals__.get(class_name)
时nose
不包含定义类