سؤال

أنا أكتب الديكور التي تحتاج إلى استدعاء وظائف أخرى قبل استدعاء الدالة التي يتم تزيين.زينت وظيفة قد الموضعية الحجج ، ولكن وظائف الديكور سيتم فقط قبول الكلمة الحجج.هل لدى احدكم طريقة سهلة لتحويل الموضعية الحجج في الكلمة الحجج ؟

وأنا أعلم أنني يمكن الحصول على قائمة من أسماء المتغيرات من مزينة وظيفة:

>>> def a(one, two=2):
...    pass

>>> a.func_code.co_varnames
('one', 'two')

ولكن أنا لا يمكن معرفة كيفية معرفة ما صدر في positionally وما كان الكلمة.

بلدي الديكور يبدو مثل هذا:

class mydec(object):
    def __init__(self, f, *args, **kwargs):
        self.f = f

    def __call__(self, *args, **kwargs):
        hozer(**kwargs)
        self.f(*args, **kwargs)

هل هناك طريقة أخرى غير مجرد مقارنة kwargs و co_varnames ، إضافة إلى kwargs أي شيء وليس هناك أمل الأفضل ؟

هل كانت مفيدة؟

المحلول

وسوف تشمل co_varnames المتغيرات المحلية وكذلك الكلمات الرئيسية -

ملحوظة. هذا ربما لن يهم، ما البريدي باقتطاع تسلسل أقصر، ولكن قد يؤدي إلى رسائل الخطأ الخلط إذا قمت بتمرير عددا خاطئا لوسائط.

ويمكنك تجنب هذا مع func_code.co_varnames[:func_code.co_argcount]، ولكن أفضل لاستخدام تفقد حدة. أي:

import inspect
argnames, varargs, kwargs, defaults = inspect.getargspec(func)

وأنت قد تحتاج أيضا إلى معالجة الحالة حيث تحدد وظيفة **kwargs أو *args (حتى لو كان مجرد لرفع استثناء عند استخدامها مع الديكور). إذا تم تعيين هؤلاء، فإن النتيجة الثانية والثالثة من getargspec يعود اسم المتغير، وإلا فإنها ستكون لا شيء.

نصائح أخرى

وسيتم تمرير أي وسيطة الذي تم تمريره positionally إلى وسائط *. وأية وسيطة مرت ككلمة رئيسية سيتم تمريرها إلى kwargs **. إذا كان لديك قيم وسائط الموضعية وأسماء ثم يمكنك القيام به:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args)))

ولتحويل كل منهم إلى وسائط رئيسية.

إذا كنت تستخدم بايثون >= 2.7 inspect.getcallargs() يفعل ذلك بالنسبة لك في الخروج من مربع.كنت مجرد تمرير زينت وظيفة الحجة الأولى ثم بقية الحجج بالضبط كما كنت تخطط أن نسميها.على سبيل المثال:

>>> def f(p1, p2, k1=None, k2=None, **kwargs):
...     pass
>>> from inspect import getcallargs

أنا والتخطيط للقيام f('p1', 'p2', 'p3', k2='k2', extra='kx1') (لاحظ أن k1 يتم تمرير positionally كما p3) لذا...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1')
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}}

إذا كنت تعرف زينت وظيفة لن تستخدم **kwargs, ثم هذا المفتاح لن تظهر في dict, والانتهاء من ذلك (و أنا على افتراض لا يوجد *args, لأن ذلك من شأنه كسر شرط أن كل شيء له اسم).إذا كنت هل لديك **kwargs, كما يجب في هذا المثال ، تريد تضمينها مع بقية اسمه الحجج ، فإنه يأخذ واحد المزيد الخط:

>>> call_args.update(call_args.pop('kwargs'))
>>> call_args
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'}

تحديث:بايثون >= 3.3 ، انظر inspect.Signature.bind() ذات الصلة inspect.signature وظيفة للحصول على وظائف مماثلة (ولكن أكثر قوة من) inspect.getcallargs().

حسنا، هذا قد يكون مبالغة. كتبت عليه لحزمة dectools (على PyPi)، حتى تتمكن من الحصول على التحديثات هناك. تقوم بإرجاع القاموس مع الأخذ بعين الاعتبار الموضعية، الكلمة الرئيسية، والوسائط الافتراضية. هناك مجموعة اختبار في حزمة (test_dict_as_called.py):

 def _dict_as_called(function, args, kwargs):
""" return a dict of all the args and kwargs as the keywords they would
be received in a real function call.  It does not call function.
"""

names, args_name, kwargs_name, defaults = inspect.getargspec(function)

# assign basic args
params = {}
if args_name:
    basic_arg_count = len(names)
    params.update(zip(names[:], args))  # zip stops at shorter sequence
    params[args_name] = args[basic_arg_count:]
else:
    params.update(zip(names, args))    

# assign kwargs given
if kwargs_name:
    params[kwargs_name] = {}
    for kw, value in kwargs.iteritems():
        if kw in names:
            params[kw] = value
        else:
            params[kwargs_name][kw] = value
else:
    params.update(kwargs)

# assign defaults
if defaults:
    for pos, value in enumerate(defaults):
        if names[-len(defaults) + pos] not in params:
            params[names[-len(defaults) + pos]] = value

# check we did it correctly.  Each param and only params are set
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name])
                                  )-set([None])

return params

إليك أحدث طريقة لحل هذه inspect.signature باستخدام (لبيثون 3.3+). سأعطيك مثالا التي يمكن تشغيلها / اختبارها نفسك أولا ومن ثم تظهر كيفية تعديل التعليمات البرمجية الأصلي معها.

وفيما يلي اختبار وظيفة التي تلخص فقط عن أي وسائط / kwargs الممنوحة لها. واحد على الأقل مطلوب الحجة (a) وهناك واحد الكلمة فقط حجة مع قيمة افتراضية (b)، فقط لاختبار جوانب مختلفة من التوقيعات وظيفة.

def silly_sum(a, *args, b=1, **kwargs):
    return a + b + sum(args) + sum(kwargs.values())

والآن دعونا جعل مجمع لsilly_sum التي يمكن استدعاؤها في نفس الطريقة كما silly_sum (مع استثناء ونحن سنصل إلى) ولكن الذي يمر فقط في kwargs إلى silly_sum ملفوفة.

def wrapper(f):
    sig = inspect.signature(f)
    def wrapped(*args, **kwargs):
        bound_args = sig.bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(bound_args) # just for testing

        all_kwargs = bound_args.arguments
        assert len(all_kwargs.pop("args")) == 0
        all_kwargs.update(all_kwargs.pop("kwargs"))
        return f(**all_kwargs)
    return wrapped

وsig.bind بإرجاع كائن BoundArguments، ولكن هذا لا يأخذ في الاعتبار افتراضات ما لم استدعاء apply_defaults صراحة. وبذلك سوف تولد أيضا الصفوف (tuple) فارغة للوسائط وديكت فارغة للkwargs إذا لم تعط *args / **kwargs.

sum_wrapped = wrapper(silly_sum)
sum_wrapped(1, c=9, d=11)
# prints <BoundArguments (a=1, args=(), b=1, kwargs={'c': 9, 'd': 11})>
# returns 22

وبعد ذلك نحن مجرد الحصول على القاموس الحجج وإضافة أي **kwargs في. الاستثناء لاستخدام هذا المجمع هو أن *args لا يمكن تمريرها إلى وظيفة. هذا هو لأنه لا توجد أسماء لهؤلاء، لذلك لا يمكننا تحويلها إلى kwargs. إذا كان يمر من خلال بمثابة kwarg اسمه وسائط غير مقبول، يمكن القيام به بدلا من ذلك.


وهنا هو كيف يمكن تطبيقها على رمز الأصلي:

import inspect


class mydec(object):
    def __init__(self, f, *args, **kwargs):
        self.f = f
        self._f_sig = inspect.signature(f)

    def __call__(self, *args, **kwargs):
        bound_args = self._f_sig.bind(*args, **kwargs)
        bound_args.apply_defaults()
        all_kwargs = bound_args.arguments
        assert len(all_kwargs.pop("args")) == 0
        all_kwargs.update(all_kwargs.pop("kwargs"))
        hozer(**all_kwargs)
        self.f(*args, **kwargs)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top