인스턴스 메서드의 장식자가 클래스에 액세스할 수 있습니까?
대략 다음과 같은 것이 있습니다.기본적으로 인스턴스 메소드 정의에서 인스턴스 메소드에 사용되는 데코레이터에서 인스턴스 메소드 클래스에 액세스해야 합니다.
def decorator(view):
# do something that requires view's class
print view.im_class
return view
class ModelA(object):
@decorator
def a_method(self):
# do some stuff
pass
현재 코드는 다음을 제공합니다.
특성 오류: 'function' 개체에 'im_class' 특성이 없습니다.
비슷한 질문/답변을 발견했습니다. - Python decorator는 함수가 클래스에 속한다는 것을 잊고 Python decorator에서 클래스를 가져오도록 합니다. - 그러나 이것들은 첫 번째 매개 변수를 캡처하여 런타임에 인스턴스를 잡는 해결 방법에 의존합니다.저의 경우, 클래스에서 수집한 정보를 기반으로 메소드를 호출할 예정이므로 빨리 전화가 걸려오기를 기대합니다.
Python 2.6 이상을 사용하는 경우 클래스 장식기를 사용할 수 있습니다. 아마도 다음과 같은 것이 사용될 수 있습니다(경고: 테스트되지 않은 코드).
def class_decorator(cls):
for name, method in cls.__dict__.iteritems():
if hasattr(method, "use_class"):
# do something with the method and class
print name, cls
return cls
def method_decorator(view):
# mark the method as something that requires view's class
view.use_class = True
return view
@class_decorator
class ModelA(object):
@method_decorator
def a_method(self):
# do some stuff
pass
메소드 장식자는 "use_class" 특성을 추가하여 메소드를 관심 대상으로 표시합니다. 함수와 메소드도 개체이므로 메소드에 추가 메타데이터를 첨부할 수 있습니다.
클래스가 작성된 후 클래스 장식가는 모든 방법을 검토하고 표시된 방법에 필요한 모든 작업을 수행합니다.
모든 메소드에 영향을 미치려면 메소드 장식자를 제외하고 클래스 장식자만 사용할 수 있습니다.
python 3.6 이후로 매우 간단한 방법으로 이를 수행할 수 있습니다.문서에 따르면__set_name__
소유 클래스 소유자가 생성될 때 호출됩니다.다음은 예입니다.
class class_decorator:
def __init__(self, fn):
self.fn = fn
def __set_name__(self, owner, name):
# do something with owner, i.e.
print(f"decorating {self.fn} and using {owner}")
self.fn.class_name = owner.__name__
# then replace ourself with the original method
setattr(owner, name, self.fn)
클래스를 만들 때 호출됩니다.
>>> class A:
... @class_decorator
... def hello(self, x=42):
... return x
...
decorating <function A.hello at 0x7f9bedf66bf8> and using <class '__main__.A'>
>>> A.hello
<function __main__.A.hello(self, x=42)>
>>> A.hello.class_name
'A'
>>> a = A()
>>> a.hello()
42
언제 생성되는지에 더 __set_name__
이 호출되면 "클래스 개체 만들기"에 있는 문서를 참조할 수 있습니다.
다른 사람들이 지적했듯이, 클래스는 장식가가 호출될 때 만들어지지 않았습니다.그러나 데코레이터 매개변수를 사용하여 함수 객체에 주석을 추가한 다음 메타 클래스의 함수를 다시 장식할 수 있습니다.__new__
기능에 .__dict__
에게는, 직적으로나, 적도에는게어접,적나는▁directly,func.foo = 1
속성 오류가 발생했습니다.
Mark가 제안하는 바와 같이:
- 모든 장식가는 클래스가 만들어지기 전에 호출되므로 장식가는 알 수 없습니다.
- 이러한 방법에 태그를 지정하고 필요한 후 처리를 나중에 수행할 수 있습니다.
- 후 처리를 위한 두 가지 옵션이 있습니다. 클래스 정의가 자동으로 끝날 때 또는 응용 프로그램이 실행되기 전 어딘가에 있습니다.저는 기본 클래스를 사용하는 첫 번째 옵션을 선호하지만, 당신은 두 번째 방법도 따를 수 있습니다.
이 코드는 자동 후 처리를 사용하는 방법을 보여줍니다.
def expose(**kw):
"Note that using **kw you can tag the function with any parameters"
def wrap(func):
name = func.func_name
assert not name.startswith('_'), "Only public methods can be exposed"
meta = func.__meta__ = kw
meta['exposed'] = True
return func
return wrap
class Exposable(object):
"Base class to expose instance methods"
_exposable_ = None # Not necessary, just for pylint
class __metaclass__(type):
def __new__(cls, name, bases, state):
methods = state['_exposed_'] = dict()
# inherit bases exposed methods
for base in bases:
methods.update(getattr(base, '_exposed_', {}))
for name, member in state.items():
meta = getattr(member, '__meta__', None)
if meta is not None:
print "Found", name, meta
methods[name] = member
return type.__new__(cls, name, bases, state)
class Foo(Exposable):
@expose(any='parameter will go', inside='__meta__ func attribute')
def foo(self):
pass
class Bar(Exposable):
@expose(hide=True, help='the great bar function')
def bar(self):
pass
class Buzz(Bar):
@expose(hello=False, msg='overriding bar function')
def bar(self):
pass
class Fizz(Foo):
@expose(msg='adding a bar function')
def bar(self):
pass
print('-' * 20)
print("showing exposed methods")
print("Foo: %s" % Foo._exposed_)
print("Bar: %s" % Bar._exposed_)
print("Buzz: %s" % Buzz._exposed_)
print("Fizz: %s" % Fizz._exposed_)
print('-' * 20)
print('examine bar functions')
print("Bar.bar: %s" % Bar.bar.__meta__)
print("Buzz.bar: %s" % Buzz.bar.__meta__)
print("Fizz.bar: %s" % Fizz.bar.__meta__)
출력 결과:
Found foo {'inside': '__meta__ func attribute', 'any': 'parameter will go', 'exposed': True}
Found bar {'hide': True, 'help': 'the great bar function', 'exposed': True}
Found bar {'msg': 'overriding bar function', 'hello': False, 'exposed': True}
Found bar {'msg': 'adding a bar function', 'exposed': True}
--------------------
showing exposed methods
Foo: {'foo': <function foo at 0x7f7da3abb398>}
Bar: {'bar': <function bar at 0x7f7da3abb140>}
Buzz: {'bar': <function bar at 0x7f7da3abb0c8>}
Fizz: {'foo': <function foo at 0x7f7da3abb398>, 'bar': <function bar at 0x7f7da3abb488>}
--------------------
examine bar functions
Bar.bar: {'hide': True, 'help': 'the great bar function', 'exposed': True}
Buzz.bar: {'msg': 'overriding bar function', 'hello': False, 'exposed': True}
Fizz.bar: {'msg': 'adding a bar function', 'exposed': True}
이 예에서는 다음과 같습니다.
- 임의의 매개 변수를 사용하여 함수에 주석을 달 수 있습니다.
- 각 클래스에는 노출된 메서드가 있습니다.
- 노출된 방법을 상속할 수도 있습니다.
- 노출 기능이 업데이트되면 메서드가 재정의될 수 있습니다.
이것이 도움이 되길 바랍니다.
문제는 데코레이터가 호출되었을 때 클래스가 아직 존재하지 않는다는 것입니다.사용해 보십시오.
def loud_decorator(func):
print("Now decorating %s" % func)
def decorated(*args, **kwargs):
print("Now calling %s with %s,%s" % (func, args, kwargs))
return func(*args, **kwargs)
return decorated
class Foo(object):
class __metaclass__(type):
def __new__(cls, name, bases, dict_):
print("Creating class %s%s with attributes %s" % (name, bases, dict_))
return type.__new__(cls, name, bases, dict_)
@loud_decorator
def hello(self, msg):
print("Hello %s" % msg)
Foo().hello()
이 프로그램은 다음을 출력합니다.
Now decorating <function hello at 0xb74d35dc>
Creating class Foo(<type 'object'>,) with attributes {'__module__': '__main__', '__metaclass__': <class '__main__.__metaclass__'>, 'hello': <function decorated at 0xb74d356c>}
Now calling <function hello at 0xb74d35dc> with (<__main__.Foo object at 0xb74ea1ac>, 'World'),{}
Hello World
보시다시피, 여러분은 여러분이 원하는 것을 할 수 있는 다른 방법을 찾아야 할 것입니다.
Ants가 지적했듯이, 당신은 클래스 내에서 클래스에 대한 참조를 얻을 수 없습니다.그러나 실제 클래스 유형 개체를 조작하지 않고 다른 클래스를 구분하려는 경우 각 클래스에 대해 문자열을 전달할 수 있습니다.클래스 스타일 장식기를 사용하여 원하는 다른 매개변수를 장식기에 전달할 수도 있습니다.
class Decorator(object):
def __init__(self,decoratee_enclosing_class):
self.decoratee_enclosing_class = decoratee_enclosing_class
def __call__(self,original_func):
def new_function(*args,**kwargs):
print 'decorating function in ',self.decoratee_enclosing_class
original_func(*args,**kwargs)
return new_function
class Bar(object):
@Decorator('Bar')
def foo(self):
print 'in foo'
class Baz(object):
@Decorator('Baz')
def foo(self):
print 'in foo'
print 'before instantiating Bar()'
b = Bar()
print 'calling b.foo()'
b.foo()
인쇄:
before instantiating Bar()
calling b.foo()
decorating function in Bar
in foo
또한, 장식가에 관한 브루스 에켈의 페이지를 보세요.
플라스크-classy가 하는 일은 메소드에 저장하는 임시 캐시를 만든 다음 다른 것을 사용합니다. (플라스크가 클래스를 등록할 때 사용하는 것은register
클래스 메소드)를 사용하여 메소드를 실제로 래핑합니다.
가져오기 시 메소드를 래핑할 수 있도록 이번에는 메타 클래스를 사용하여 이 패턴을 다시 사용할 수 있습니다.
def route(rule, **options):
"""A decorator that is used to define custom routes for methods in
FlaskView subclasses. The format is exactly the same as Flask's
`@app.route` decorator.
"""
def decorator(f):
# Put the rule cache on the method itself instead of globally
if not hasattr(f, '_rule_cache') or f._rule_cache is None:
f._rule_cache = {f.__name__: [(rule, options)]}
elif not f.__name__ in f._rule_cache:
f._rule_cache[f.__name__] = [(rule, options)]
else:
f._rule_cache[f.__name__].append((rule, options))
return f
return decorator
실제 클래스에서(메타 클래스를 사용하여 동일한 작업을 수행할 수 있음):
@classmethod
def register(cls, app, route_base=None, subdomain=None, route_prefix=None,
trailing_slash=None):
for name, value in members:
proxy = cls.make_proxy_method(name)
route_name = cls.build_route_name(name)
try:
if hasattr(value, "_rule_cache") and name in value._rule_cache:
for idx, cached_rule in enumerate(value._rule_cache[name]):
# wrap the method here
출처: https://github.com/apiguy/flask-classy/blob/master/flask_classy.py
다음은 간단한 예입니다.
def mod_bar(cls):
# returns modified class
def decorate(fcn):
# returns decorated function
def new_fcn(self):
print self.start_str
print fcn(self)
print self.end_str
return new_fcn
cls.bar = decorate(cls.bar)
return cls
@mod_bar
class Test(object):
def __init__(self):
self.start_str = "starting dec"
self.end_str = "ending dec"
def bar(self):
return "bar"
출력은 다음과 같습니다.
>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec
다른 답변에서 지적했듯이 데코레이터는 기능적인 것이므로 클래스가 아직 만들어지지 않았기 때문에 이 메서드가 속한 클래스에 액세스할 수 없습니다.그러나, 기능을 "표시"하기 위해 장식자를 사용하고 나중에 방법을 처리하기 위해 메타 클래스 기술을 사용하는 것은 완전히 괜찮습니다. 왜냐하면,__new__
메타 클래스에 의해 클래스가 생성되었습니다.
다음은 간단한 예입니다.
우리는 사용합니다.@field
메소드를 특수 필드로 표시하고 메타 클래스에서 처리합니다.
def field(fn):
"""Mark the method as an extra field"""
fn.is_field = True
return fn
class MetaEndpoint(type):
def __new__(cls, name, bases, attrs):
fields = {}
for k, v in attrs.items():
if inspect.isfunction(v) and getattr(k, "is_field", False):
fields[k] = v
for base in bases:
if hasattr(base, "_fields"):
fields.update(base._fields)
attrs["_fields"] = fields
return type.__new__(cls, name, bases, attrs)
class EndPoint(metaclass=MetaEndpoint):
pass
# Usage
class MyEndPoint(EndPoint):
@field
def foo(self):
return "bar"
e = MyEndPoint()
e._fields # {"foo": ...}
함수는 디케이터 코드가 실행되는 정의 시점의 메서드인지 여부를 알 수 없습니다.클래스/인스턴스 식별자를 통해 액세스해야 클래스/인스턴스를 알 수 있습니다.이 제한을 극복하기 위해 설명자 객체를 사용하여 액세스/호출 시간까지 실제 장식 코드를 지연시킬 수 있습니다.
class decorated(object):
def __init__(self, func, type_=None):
self.func = func
self.type = type_
def __get__(self, obj, type_=None):
func = self.func.__get__(obj, type_)
print('accessed %s.%s' % (type_.__name__, func.__name__))
return self.__class__(func, type_)
def __call__(self, *args, **kwargs):
name = '%s.%s' % (self.type.__name__, self.func.__name__)
print('called %s with args=%s kwargs=%s' % (name, args, kwargs))
return self.func(*args, **kwargs)
이를 통해 개별(정적|클래스) 메소드를 장식할 수 있습니다.
class Foo(object):
@decorated
def foo(self, a, b):
pass
@decorated
@staticmethod
def bar(a, b):
pass
@decorated
@classmethod
def baz(cls, a, b):
pass
class Bar(Foo):
pass
이제 내부 조사를 위해 장식자 코드를 사용할 수 있습니다.
>>> Foo.foo
accessed Foo.foo
>>> Foo.bar
accessed Foo.bar
>>> Foo.baz
accessed Foo.baz
>>> Bar.foo
accessed Bar.foo
>>> Bar.bar
accessed Bar.bar
>>> Bar.baz
accessed Bar.baz
...함수 동작을 변경하는 경우:
>>> Foo().foo(1, 2)
accessed Foo.foo
called Foo.foo with args=(1, 2) kwargs={}
>>> Foo.bar(1, b='bcd')
accessed Foo.bar
called Foo.bar with args=(1,) kwargs={'b': 'bcd'}
>>> Bar.baz(a='abc', b='bcd')
accessed Bar.baz
called Bar.baz with args=() kwargs={'a': 'abc', 'b': 'bcd'}
이것은 오래된 질문이지만 비너스적인 것을 발견했습니다.http://venusian.readthedocs.org/en/latest/
방법을 꾸미고 수업과 방법을 동시에 접할 수 있는 능력이 있는 것 같습니다.호출 참고setattr(ob, wrapped.__name__, decorated)
비너스어를 사용하는 일반적인 방법이 아니며 목적을 다소 위반합니다.
어느 쪽이든...아래 예제는 완료되었으며 실행되어야 합니다.
import sys
from functools import wraps
import venusian
def logged(wrapped):
def callback(scanner, name, ob):
@wraps(wrapped)
def decorated(self, *args, **kwargs):
print 'you called method', wrapped.__name__, 'on class', ob.__name__
return wrapped(self, *args, **kwargs)
print 'decorating', '%s.%s' % (ob.__name__, wrapped.__name__)
setattr(ob, wrapped.__name__, decorated)
venusian.attach(wrapped, callback)
return wrapped
class Foo(object):
@logged
def bar(self):
print 'bar'
scanner = venusian.Scanner()
scanner.scan(sys.modules[__name__])
if __name__ == '__main__':
t = Foo()
t.bar()
장식자가 반환해야 하는 장식된 메서드에서 메서드가 호출되는 오브젝트 클래스에 액세스할 수 있습니다.이와 같은 경우:
def decorator(method):
# do something that requires view's class
def decorated(self, *args, **kwargs):
print 'My class is %s' % self.__class__
method(self, *args, **kwargs)
return decorated
모델 A 클래스를 사용하면 다음과 같은 작업을 수행할 수 있습니다.
>>> obj = ModelA()
>>> obj.a_method()
My class is <class '__main__.ModelA'>
장식된 방법으로 수업에 접근하기 위해 생각할 수 있는 모든 것들이 포함되어 있기 때문에 저는 제 예시를 추가하고 싶습니다.@tyrion이 제안하는 것처럼 설명자를 사용합니다.장식가는 인수를 가져와 설명자에게 전달할 수 있습니다.클래스의 메서드 또는 클래스가 없는 함수를 모두 처리할 수 있습니다.
import datetime as dt
import functools
def dec(arg1):
class Timed(object):
local_arg = arg1
def __init__(self, f):
functools.update_wrapper(self, f)
self.func = f
def __set_name__(self, owner, name):
# doing something fancy with owner and name
print('owner type', owner.my_type())
print('my arg', self.local_arg)
def __call__(self, *args, **kwargs):
start = dt.datetime.now()
ret = self.func(*args, **kwargs)
time = dt.datetime.now() - start
ret["time"] = time
return ret
def __get__(self, instance, owner):
from functools import partial
return partial(self.__call__, instance)
return Timed
class Test(object):
def __init__(self):
super(Test, self).__init__()
@classmethod
def my_type(cls):
return 'owner'
@dec(arg1='a')
def decorated(self, *args, **kwargs):
print(self)
print(args)
print(kwargs)
return dict()
def call_deco(self):
self.decorated("Hello", world="World")
@dec(arg1='a function')
def another(*args, **kwargs):
print(args)
print(kwargs)
return dict()
if __name__ == "__main__":
t = Test()
ret = t.call_deco()
another('Ni hao', world="shi jie")
@아스테리오 곤잘레스
나는 당신의 방법을 선호하지만, Python 3이 새로운 메타 클래스 처리를 준수하기 위해서는 약간 변경되어야 합니다(또한, 일부 인쇄문에는 괄호가 없었습니다).
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Aug 9 15:27:30 2021
@author: yves
"""
def expose(**kw):
"Note that using **kw you can tag the function with any parameters"
def wrap(func):
name = func.__name__
assert not name.startswith('_'), "Only public methods can be exposed"
meta = func.__meta__ = kw
meta['exposed'] = None
return func
return wrap
class ExposableMetaclass(type):
def __new__(cls, name, bases, state):
methods = state['_exposed_'] = dict()
# inherit bases exposed methods
for base in bases:
methods.update(getattr(base, '_exposed_', {}))
for name, member in state.items():
meta = getattr(member, '__meta__', None)
if meta is not None:
print("Found", name, meta)
methods[name] = member
return type.__new__(cls, name, bases, state)
class Exposable(metaclass=ExposableMetaclass):
"Base class to expose instance methods"
_exposable_ = None # Not necessary, just for pylint
class Foo(Exposable):
@expose(any='parameter will go', inside='__meta__ func attribute')
def foo(self):
pass
class Bar(Exposable):
@expose(hide=True, help='the great bar function')
def bar(self):
pass
class Buzz(Bar):
@expose(hello=False, msg='overriding bar function')
def bar(self):
pass
class Fizz(Foo):
@expose(msg='adding a bar function')
def bar(self):
pass
print('-' * 20)
print("showing exposed methods")
print("Foo: %s" % Foo._exposed_)
print("Bar: %s" % Bar._exposed_)
print("Buzz: %s" % Buzz._exposed_)
print("Fizz: %s" % Fizz._exposed_)
내 요구를 충족시켰습니다!
언급URL : https://stackoverflow.com/questions/2366713/can-a-decorator-of-an-instance-method-access-the-class
'source' 카테고리의 다른 글
Android에서 화면 회전 방지 (0) | 2023.07.26 |
---|---|
python에서 URL을 여는 방법 (0) | 2023.07.26 |
Python에서 가상 환경의 이름을 변경하는 방법은 무엇입니까? (0) | 2023.07.21 |
로컬 React 프론트엔드를 로컬 Spring Boot 미들웨어 응용 프로그램에 연결할 때 CORS 오류가 발생했습니다. (0) | 2023.07.21 |
심층 학습 난 손실 이유 (0) | 2023.07.21 |