ผู้ตกแต่งวิธีการอินสแตนซ์สามารถเข้าถึงคลาสได้หรือไม่?


109

ฉันมีบางอย่างคร่าวๆดังต่อไปนี้ โดยทั่วไปฉันต้องเข้าถึงคลาสของวิธีอินสแตนซ์จากมัณฑนากรที่ใช้กับวิธีอินสแตนซ์ในคำจำกัดความ

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

รหัสตามที่ให้:

AttributeError: วัตถุ 'function' ไม่มีแอตทริบิวต์ 'im_class'

ฉันพบคำถาม / คำตอบที่คล้ายกัน - มัณฑนากร Python ทำให้ฟังก์ชันลืมไปว่ามันเป็นของคลาสและรับคลาสในมัณฑนากร Python - แต่สิ่งเหล่านี้ต้องอาศัยวิธีแก้ปัญหาที่จับอินสแตนซ์ในขณะรันไทม์โดยการฉกพารามิเตอร์แรก ในกรณีของฉันฉันจะเรียกใช้เมธอดตามข้อมูลที่รวบรวมจากคลาสของมันดังนั้นฉันจึงแทบรอไม่ไหวให้โทรเข้ามา

คำตอบ:


68

หากคุณใช้ 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" - ฟังก์ชันและเมธอดเป็นอ็อบเจ็กต์ด้วยดังนั้นคุณจึงสามารถแนบข้อมูลเมตาเพิ่มเติมเข้ากับพวกเขาได้

หลังจากสร้างคลาสแล้วมัณฑนากรชั้นเรียนแล้วให้ทำตามวิธีการทั้งหมดและทำสิ่งที่จำเป็นกับวิธีการที่ทำเครื่องหมายไว้

หากคุณต้องการให้วิธีการทั้งหมดได้รับผลกระทบคุณสามารถละทิ้งเมธอดมัณฑนากรและใช้มัณฑนากรชั้นเรียน


2
ขอบคุณฉันคิดว่านี่คือเส้นทางที่จะไป โค้ดพิเศษเพียงบรรทัดเดียวสำหรับคลาสใด ๆ ที่ฉันต้องการใช้มัณฑนากรนี้ บางทีฉันอาจใช้ metaclass ที่กำหนดเองและทำการตรวจสอบแบบเดียวกันนี้ในช่วงใหม่ ... ?
Carl G

3
ใครก็ตามที่พยายามใช้สิ่งนี้กับ staticmethod หรือ classmethod จะต้องการอ่าน PEP นี้: python.org/dev/peps/pep-0232 ไม่แน่ใจว่าเป็นไปได้เพราะคุณไม่สามารถตั้งค่าแอตทริบิวต์ใน class / static method ได้และฉันคิดว่าพวกมันฮุบ เพิ่มคุณสมบัติของฟังก์ชันที่กำหนดเองเมื่อนำไปใช้กับฟังก์ชัน
Carl G

สิ่งที่ฉันกำลังมองหาสำหรับ ORM ที่ใช้ DBM ของฉัน ... ขอบคุณเพื่อน
Coyote21

คุณควรใช้inspect.getmro(cls)เพื่อประมวลผลคลาสพื้นฐานทั้งหมดในคลาสมัณฑนากรเพื่อรองรับการสืบทอด
schlamar

1
โอ้ดูเหมือนว่าinspectการช่วยเหลือstackoverflow.com/a/1911287/202168
Anentropic

16

เนื่องจาก python 3.6 คุณสามารถใช้object.__set_name__เพื่อทำสิ่งนี้ให้สำเร็จได้ด้วยวิธีง่ายๆ เอกสารระบุว่า__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__มีการเรียกว่าคุณสามารถอ้างถึงเอกสารเกี่ยวกับ "การสร้างวัตถุชั้น"


1
การใช้มัณฑนากรกับพารามิเตอร์จะเป็นอย่างไร เช่น@class_decorator('test', foo='bar')
luckydonald

2
@luckydonald คุณสามารถเข้าใกล้มันคล้ายกับปกติตกแต่งที่ใช้ข้อโต้แย้ง Just havedef decorator(*args, **kwds): class Descriptor: ...; return Descriptor
Matt Eding

ว้าวขอบคุณมาก ไม่รู้เกี่ยวกับ__set_name__แม้ว่าฉันจะใช้ Python 3.6+ มานานแล้วก็ตาม
kawing-chiu

วิธีนี้มีข้อเสียเปรียบประการหนึ่งคือตัวตรวจสอบแบบคงที่ไม่เข้าใจสิ่งนี้เลย Mypy จะคิดว่านั่นhelloไม่ใช่วิธีการ แต่เป็นวัตถุประเภทหนึ่งclass_decoratorแทน
kawing-chiu

@ kawing-chiu ถ้าไม่มีอะไรทำงานคุณสามารถใช้if TYPE_CHECKINGเพื่อกำหนดclass_decoratorเป็นมัณฑนากรปกติที่ส่งคืนประเภทที่ถูกต้อง
tyrion

15

ตามที่คนอื่น ๆ ชี้ให้เห็นว่าชั้นเรียนนั้นไม่ได้ถูกสร้างขึ้นในเวลาที่มีการเรียกมัณฑนากร อย่างไรก็ตามเป็นไปได้ที่จะใส่คำอธิบายประกอบวัตถุฟังก์ชันด้วยพารามิเตอร์มัณฑนากรจากนั้นตกแต่งฟังก์ชันใหม่ใน__new__เมธอดของเมตาคลาส คุณจะต้องเข้าถึง__dict__แอตทริบิวต์ของฟังก์ชันโดยตรงอย่างน้อยสำหรับฉันก็func.foo = 1ส่งผลให้เกิด AttributeError


6
setattrควรใช้แทนการเข้าถึง__dict__
schlamar

7

ตามที่ Mark แนะนำ:

  1. มัณฑนากรใด ๆ เรียกว่าคลาสก่อนสร้างขึ้นดังนั้นมัณฑนากรจึงไม่รู้จัก
  2. เราสามารถติดแท็กวิธีการเหล่านี้และทำการโพสต์ที่จำเป็นในภายหลัง
  3. เรามีสองตัวเลือกสำหรับการประมวลผลภายหลัง: โดยอัตโนมัติเมื่อสิ้นสุดคำจำกัดความของคลาสหรือที่ใดที่หนึ่งก่อนที่แอปพลิเคชันจะทำงาน ฉันชอบตัวเลือกที่ 1 โดยใช้คลาสพื้นฐาน แต่คุณสามารถทำตามแนวทางที่ 2 ได้เช่นกัน

รหัสนี้แสดงวิธีการทำงานโดยใช้การประมวลผลอัตโนมัติ:

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}

โปรดทราบว่าในตัวอย่างนี้:

  1. เราสามารถใส่คำอธิบายประกอบฟังก์ชันด้วยพารามิเตอร์ใดก็ได้
  2. แต่ละคลาสมีวิธีการสัมผัสของตัวเอง
  3. เราสามารถสืบทอดวิธีการที่เปิดเผยได้เช่นกัน
  4. วิธีการสามารถแทนที่ได้เนื่องจากมีการอัปเดตคุณลักษณะการเปิดเผย

หวังว่านี่จะช่วยได้


4

ตามที่มดระบุคุณไม่สามารถอ้างอิงถึงคลาสจากในคลาสได้ อย่างไรก็ตามหากคุณสนใจที่จะแยกความแตกต่างระหว่างคลาสต่างๆ (ไม่ได้จัดการกับอ็อบเจ็กต์ประเภทคลาสจริง) คุณสามารถส่งสตริงสำหรับแต่ละคลาสได้ คุณยังสามารถส่งผ่านพารามิเตอร์อื่น ๆ ที่คุณต้องการไปยังมัณฑนากรโดยใช้มัณฑนากรสไตล์คลาส

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

นอกจากนี้โปรดดูหน้าของ Bruce Eckel เกี่ยวกับมัณฑนากร


ขอบคุณที่ยืนยันข้อสรุปที่น่าหดหู่ของฉันว่าเป็นไปไม่ได้ ฉันยังสามารถใช้สตริงที่มีคุณสมบัติครบถ้วนสำหรับโมดูล / คลาส ('module.Class') เก็บสตริงไว้จนกว่าคลาสจะโหลดทั้งหมดแล้วจึงดึงคลาสด้วยตัวเองด้วยการนำเข้า ดูเหมือนจะเป็นวิธีที่ไม่แห้งอย่างยิ่งในการทำงานให้สำเร็จ
Carl G

คุณไม่จำเป็นต้องใช้คลาสสำหรับมัณฑนากรประเภทนี้: วิธีการทางสำนวนคือการใช้ฟังก์ชันซ้อนในระดับพิเศษอีกระดับหนึ่งภายในฟังก์ชันมัณฑนากร แต่ถ้าคุณไม่ไปกับการเรียนก็อาจจะดีกว่าที่จะไม่ใช้ตัวพิมพ์ใหญ่ในชื่อชั้นจะทำให้การตกแต่งตัวเองดู "มาตรฐาน" คือเมื่อเทียบกับ@decorator('Bar') @Decorator('Bar')
Erik Kaplun

4

สิ่งที่flask-classyทำคือสร้างแคชชั่วคราวที่เก็บไว้ในวิธีการจากนั้นใช้อย่างอื่น (ข้อเท็จจริงที่ว่า Flask จะลงทะเบียนคลาสโดยใช้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


นั่นเป็นรูปแบบที่มีประโยชน์ แต่สิ่งนี้ไม่ได้แก้ปัญหาของตัวตกแต่งวิธีที่สามารถอ้างถึงคลาสหลักของวิธีการที่ใช้กับ
Anentropic

ฉันอัปเดตคำตอบให้ชัดเจนยิ่งขึ้นว่าสิ่งนี้จะมีประโยชน์ในการเข้าถึงคลาสในเวลานำเข้าได้อย่างไร (เช่นการใช้ metaclass + การแคชพารามิเตอร์มัณฑนากรในวิธีการนี้)
charlax

3

ปัญหาคือเมื่อมัณฑนากรเรียกว่าคลาสยังไม่มี ลองสิ่งนี้:

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

อย่างที่คุณเห็นคุณจะต้องหาวิธีอื่นในการทำสิ่งที่คุณต้องการ


เมื่อกำหนดฟังก์ชันฟังก์ชันยังไม่มีอยู่ แต่สามารถเรียกฟังก์ชันซ้ำจากภายในตัวเองได้ ฉันเดาว่านี่เป็นคุณสมบัติภาษาเฉพาะสำหรับฟังก์ชันและไม่สามารถใช้ได้กับคลาส
Carl G

DGGenuine: ฟังก์ชันนี้ถูกเรียกใช้เท่านั้นและฟังก์ชันจึงเข้าถึงตัวเองหลังจากที่สร้างเสร็จสมบูรณ์เท่านั้น ในกรณีนี้คลาสจะไม่สมบูรณ์เมื่อมีการเรียกมัณฑนากรเนื่องจากคลาสจะต้องรอผลลัพธ์ของมัณฑนากรซึ่งจะถูกเก็บไว้เป็นหนึ่งในคุณลักษณะของคลาส
u0b34a0f6ae

3

นี่คือตัวอย่างง่ายๆ:

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

1

นี่เป็นคำถามเก่า แต่เจอกับ venusian http://venusian.readthedocs.org/en/latest/

ดูเหมือนว่าจะมีความสามารถในการตกแต่งเมธอดและให้คุณเข้าถึงทั้งคลาสและเมธอดได้ในขณะที่ทำเช่นนั้น โปรดทราบว่าการโทรsetattr(ob, wrapped.__name__, decorated)ไม่ใช่วิธีการทั่วไปในการใช้ venusian และค่อนข้างจะผิดวัตถุประสงค์

ไม่ว่าจะด้วยวิธีใดก็ตาม ... ตัวอย่างด้านล่างนี้เสร็จสมบูรณ์และควรเรียกใช้

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()

1

ฟังก์ชันไม่ทราบว่าเป็นวิธีการ ณ จุดนิยามหรือไม่เมื่อโค้ดมัณฑนากรทำงาน เฉพาะเมื่อเข้าถึงผ่านตัวระบุคลาส / อินสแตนซ์เท่านั้นที่อาจทราบคลาส / อินสแตนซ์ เพื่อเอาชนะข้อ จำกัด นี้คุณสามารถตกแต่งด้วยวัตถุตัวอธิบายเพื่อชะลอรหัสตกแต่งจริงจนกว่าจะถึงเวลาเข้าถึง / โทร:

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'}

น่าเศร้าที่วิธีนี้คือหน้าที่เทียบเท่าWill McCutchenเป็นคำตอบที่ไม่เหมาะสมอย่างเท่าเทียมกัน ทั้งคำตอบนี้และคำตอบนั้นจะได้คลาสที่ต้องการในเวลาเรียกใช้เมธอดแทนที่จะเป็นเวลาตกแต่งเมธอดตามที่คำถามเดิมต้องการ วิธีเดียวที่สมเหตุสมผลในการได้รับคลาสนี้ในช่วงแรก ๆ ที่เพียงพอคือการไตร่ตรองเกี่ยวกับวิธีการทั้งหมดในช่วงเวลาที่กำหนดคลาส (เช่นผ่านคลาสมัณฑนากรหรือเมตาคลาส) </sigh>
Cecil Curry

1

ดังที่คำตอบอื่น ๆ ได้ชี้ให้เห็นว่ามัณฑนากรเป็นสิ่งที่เป็นฟังก์ชันคุณไม่สามารถเข้าถึงคลาสที่เป็นของวิธีนี้ได้เนื่องจากยังไม่ได้สร้างคลาส อย่างไรก็ตามเป็นเรื่องปกติที่จะใช้มัณฑนากรเพื่อ "ทำเครื่องหมาย" ฟังก์ชันแล้วใช้เทคนิคเมตา__new__คลาสเพื่อจัดการกับวิธีการนี้ในภายหลังเนื่องจากในขั้นตอนนี้คลาสได้ถูกสร้างขึ้นโดยเมตาคลาส

นี่คือตัวอย่างง่ายๆ:

เราใช้@fieldเพื่อทำเครื่องหมายวิธีการเป็นช่องพิเศษและจัดการกับมันใน metaclass

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": ...}

คุณพิมพ์ผิดในบรรทัดนี้if inspect.isfunction(v) and getattr(k, "is_field", False):ควรจะเป็นgetattr(v, "is_field", False)แทน
EvilTosha

0

คุณจะสามารถเข้าถึงคลาสของอ็อบเจ็กต์ที่เมธอดถูกเรียกใช้ในเมธอดตกแต่งที่มัณฑนากรของคุณควรส่งคืน ชอบมาก:

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

การใช้คลาส ModelA ของคุณนี่คือสิ่งที่ทำ:

>>> obj = ModelA()
>>> obj.a_method()
My class is <class '__main__.ModelA'>

1
ขอบคุณ แต่นี่เป็นวิธีแก้ปัญหาที่ฉันอ้างถึงในคำถามของฉันซึ่งไม่ได้ผลสำหรับฉัน ฉันกำลังพยายามใช้รูปแบบการสังเกตการณ์โดยใช้มัณฑนากรและฉันจะไม่สามารถเรียกใช้วิธีการนี้ในบริบทที่ถูกต้องจากผู้มอบหมายงานสังเกตการณ์ของฉันได้หากฉันไม่มีชั้นเรียนในบางช่วงขณะที่เพิ่มวิธีการให้กับผู้มอบหมายงานการสังเกตการณ์ การเรียกใช้เมธอดในชั้นเรียนไม่ได้ช่วยให้ฉันเรียกเมธอดได้อย่างถูกต้องตั้งแต่แรก
Carl G

ขออภัยสำหรับความขี้เกียจของฉันที่ไม่อ่านคำถามทั้งหมดของคุณ
Will McCutchen

0

ฉันแค่ต้องการเพิ่มตัวอย่างของฉันเนื่องจากมันมีทุกสิ่งที่ฉันคิดได้สำหรับการเข้าถึงคลาสจากวิธีการตกแต่ง มันใช้ตัวอธิบายตามที่ @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")
    
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.