ফানটুলস.আর্যাপস কী করে?


650

অন্য প্রশ্নের উত্তরের একটি মন্তব্যে , কেউ বলেছে যে তারা কি functools.wrapsকরছে তা নিশ্চিত নয় । সুতরাং, আমি এই প্রশ্নটি জিজ্ঞাসা করছি যাতে ভবিষ্যতের রেফারেন্সের জন্য স্ট্যাকওভারফ্লোতে এর একটি রেকর্ড থাকবে: functools.wrapsঠিক কী করে ?

উত্তর:


1069

আপনি যখন কোনও ডেকরেটর ব্যবহার করেন, আপনি একটি ফাংশন অন্যটির সাথে প্রতিস্থাপন করছেন। অন্য কথায়, যদি আপনি একটি সজ্জা আছে

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

তারপরে আপনি যখন বলবেন

@logged
def f(x):
   """does some math"""
   return x + x * x

এটি বলার মতোই

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

এবং আপনার ফাংশন ফাংশন fদিয়ে প্রতিস্থাপিত হয় with_logging। দুর্ভাগ্যক্রমে, এর অর্থ হ'ল যদি আপনি বলেন তবে

print(f.__name__)

এটি মুদ্রণ করবে with_loggingকারণ এটি আপনার নতুন ফাংশনের নাম। প্রকৃতপক্ষে, আপনি যদি ডকাস্ট্রিংয়ের দিকে নজর দেন তবে fএটি ফাঁকা হবে কারণ with_loggingকোনও ডক্টর্টিং নেই, এবং তাই আপনার লেখা ডক্ট্রাস্টিং আর থাকবে না। এছাড়াও, যদি আপনি সেই ফাংশনের জন্য পাইডোক ফলাফলটি দেখেন তবে এটি একটি যুক্তি হিসাবে তালিকাভুক্ত হবে না x; পরিবর্তে এটি গ্রহণ হিসাবে তালিকাভুক্ত করা হবে *argsএবং **kwargsকারণ হ'ল_লগিং যা গ্রহণ করে।

যদি কোনও ডেকোরেটার ব্যবহার করার অর্থ হ'ল কোনও ফাংশন সম্পর্কে এই তথ্যটি হারাতে থাকে তবে এটি মারাত্মক সমস্যা হবে। আমাদের জন্য তাই functools.wraps। এটি একটি ডেকোরেটারে ব্যবহৃত একটি ফাংশন গ্রহণ করে এবং ফাংশনটির নাম, ডকস্ট্রিং, আর্গুমেন্টের তালিকা ইত্যাদির অনুলিপি করার কার্যকারিতা যুক্ত করে এবং যেহেতু wrapsনিজেই একটি সজ্জাকারী হয় তাই নিম্নলিখিত কোডটি সঠিক কাজ করে:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

7
হ্যাঁ, আমি ডেকরেটারের মডিউলটি এড়াতে পছন্দ করি যেহেতু ফান্টটুলস.আর্যাপগুলি স্ট্যান্ডার্ড লাইব্রেরির অংশ এবং এইভাবে অন্য কোনও বাহ্যিক নির্ভরতার পরিচয় দেয় না। তবে ডেকোরেটর মডিউলটি প্রকৃতপক্ষে সহায়তা সমস্যার সমাধান করে, যা আশা করা যায় যে ফান্টটুলস raআর্যাপসটি কোনও একদিন হবে।
এলি কোর্টরাইট

6
আপনি মোড়ানো ব্যবহার না করলে কী ঘটতে পারে তার একটি উদাহরণ এখানে রয়েছে: ডক্টল পরীক্ষাগুলি হঠাৎ অদৃশ্য হয়ে যেতে পারে। এর কারণ ডক্টলগুলি সজ্জিত ফাংশনগুলিতে টেস্টগুলি সন্ধান করতে পারে না যদি না মোড়ানো () এর মতো কিছু সেগুলি অনুলিপি করে।
অ্যান্ড্রু কুক

88
কেন আমাদের functools.wrapsএই কাজের প্রয়োজন , এটি কেবল প্রথম স্থানে সজ্জা বিন্যাসের অংশ হওয়া উচিত নয়? আপনি কখন @ wraps ব্যবহার করতে চান না ?
উইম

56
@ উইম: আমি কিছু সজ্জা রচনা করেছি যা @wrapsঅনুলিপি করা মানগুলিতে বিভিন্ন ধরণের পরিবর্তন বা টিকা দেওয়ার জন্য তাদের নিজস্ব সংস্করণ তৈরি করে। মৌলিকভাবে, এটি পাইথন দর্শনের একটি এক্সটেনশন যা স্পষ্টতই প্রচ্ছন্নতার চেয়ে ভাল এবং বিশেষ কেসগুলি নিয়ম ভাঙার পক্ষে যথেষ্ট বিশেষ নয়। (কোডটি বেশ সহজ এবং @wraps
কোনওরকম

35
@ লুকাসমালোর সমস্ত সজ্জাকারক সেগুলি সজ্জিত ফাংশনগুলি আবদ্ধ করে না। কিছু কিছু পার্শ্ব প্রতিক্রিয়া প্রয়োগ করে, যেমন কোনও ধরণের লুকিং সিস্টেমে এগুলি নিবন্ধন করে।
ssokolow

22

আমি প্রায়শই আমার সজ্জকারদের জন্য ফাংশনগুলির চেয়ে ক্লাস ব্যবহার করি। আমার এটির সাথে কিছুটা সমস্যা হচ্ছিল কারণ কোনও বস্তুর কোনও ফাংশন প্রত্যাশিত একই বৈশিষ্ট্যগুলি থাকবে না। উদাহরণস্বরূপ, কোনও বস্তুর বৈশিষ্ট্য থাকবে না __name__। আমার এটির সাথে একটি নির্দিষ্ট সমস্যা ছিল যা জ্যাঙ্গো ত্রুটিটি জানিয়েছিল যেখানে "অবজেক্টটির কোনও বৈশিষ্ট্য নেই __name__'" এটি জানা খুব কঠিন । দুর্ভাগ্যক্রমে, ক্লাস-স্টাইলের সাজসজ্জার জন্য, আমি বিশ্বাস করি না যে @ মোড়ানো কাজটি করবে। পরিবর্তে আমি এর মতো একটি বেস সজ্জা শ্রেণি তৈরি করেছি:

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

এই শ্রেণিটি সমস্ত বৈশিষ্ট্য সজ্জিত করা ফাংশনে কল করে। সুতরাং, আপনি এখন একটি সাধারণ সাজসজ্জা তৈরি করতে পারেন যা চেক করে যে 2 টি আর্গুমেন্ট এর মতো নির্দিষ্ট করা হয়েছে:

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

7
যেমনটি থেকে দস্তাবেজগুলি @wrapsবলেছেন, @wrapsকেবল এটি একটি সুবিধার কাজ functools.update_wrapper()। শ্রেণি সাজসজ্জার ক্ষেত্রে, আপনি update_wrapper()সরাসরি আপনার __init__()পদ্ধতি থেকে কল করতে পারেন । সুতরাং, আপনি তৈরি করতে প্রয়োজন হবে না DecBaseএ সব, আপনি শুধু উপর অন্তর্ভুক্ত করতে পারে __init__()এর process_loginলাইন: update_wrapper(self, func)। এখানেই শেষ.
ফাবিয়ানো

14

অজগর 3.5+ হিসাবে:

@functools.wraps(f)
def g():
    pass

এর একটি উপনাম g = functools.update_wrapper(g, f)। এটি ঠিক তিনটি জিনিস করে:

  • এটা কপি __module__, __name__, __qualname__, __doc__, এবং __annotations__গুণাবলীর fউপর g। এই ডিফল্ট তালিকাটি রয়েছে WRAPPER_ASSIGNMENTS, আপনি এটি ফান্টুলস উত্সে দেখতে পারেন ।
  • এটা আপডেট __dict__এর gথেকে সব উপাদানের সঙ্গে f.__dict__। ( WRAPPER_UPDATESউত্স দেখুন)
  • এটি একটি নতুন __wrapped__=fবৈশিষ্ট্য সেট করেg

ফলাফলটি হ'ল gএকই নাম, ডকস্ট্রিং, মডিউল নাম, এবং এর চেয়ে স্বাক্ষর হিসাবে উপস্থিত f। একমাত্র সমস্যা হ'ল স্বাক্ষর সম্পর্কিত এটি আসলে সত্য নয়: এটি কেবল inspect.signatureডিফল্টরূপে মোড়কের চেইন অনুসরণ করে। আপনি ডকটিতেinspect.signature(g, follow_wrapped=False) বর্ণিত হিসাবে ব্যবহার করে এটি পরীক্ষা করতে পারেন । এর বিরক্তিকর পরিণতি রয়েছে:

  • সরবরাহকৃত আর্গুমেন্টগুলি অবৈধ থাকলেও মোড়ক কোড কার্যকর করবে।
  • মোড়কের কোডটি প্রাপ্ত * টি অর্গস, ** কোয়ার্গস থেকে এর নাম ব্যবহার করে কোনও যুক্তি সহজেই অ্যাক্সেস করতে পারে না। প্রকৃতপক্ষে একটিতে সমস্ত কেস পরিচালনা করতে হবে (অবস্থানগত, কীওয়ার্ড, ডিফল্ট) এবং তাই এরকম কিছু ব্যবহার করতে হবে Signature.bind()

এখন functools.wrapsএবং সজ্জাকারদের মধ্যে কিছুটা বিভ্রান্তি দেখা দিয়েছে , কারণ সজ্জিতগুলি বিকাশের জন্য খুব ঘন ঘন ব্যবহারের ক্ষেত্রে ফাংশন মোড়ানো হয়। তবে দুটিই সম্পূর্ণ স্বাধীন ধারণা are আপনি যদি এই পার্থক্যটি বুঝতে আগ্রহী হন তবে আমি উভয়ের জন্য সহায়ক লাইব্রেরি প্রয়োগ করেছি: সহজেই সাজসজ্জার লেখার জন্য ডিকোপ্যাচ এবং স্বাক্ষর-সংরক্ষণের প্রতিস্থাপনের জন্য মেকফান@wraps । নোট করুন যে makefunবিখ্যাত decoratorগ্রন্থাগারের তুলনায় একই প্রমাণিত কৌশলের উপর নির্ভর করে ।


3

এটি মোড়ানো সম্পর্কে উত্স কোড:

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

2
  1. পূর্বশর্ত: আপনার অবশ্যই সজ্জা ব্যবহার করতে হবে এবং বিশেষত মোড়ক সহ অবশ্যই ব্যবহার করতে হবে know এই মন্তব্যটি এটি কিছুটা স্পষ্টভাবে ব্যাখ্যা করেছে বা এই লিঙ্কটি এটি বেশ ভালভাবে ব্যাখ্যা করেছে।

  2. যখনই আমরা উদাহরণস্বরূপ ব্যবহার করি: @ র্যাপগুলি তার পরে আমাদের নিজস্ব মোড়ক ফাংশন অনুসরণ করে। এই লিঙ্কে প্রদত্ত বিবরণ অনুযায়ী , এটি বলে যে

functools.wraps একটি মোড়কের ফাংশনটি সংজ্ঞায়িত করার সময়, একটি ফাংশন ডেকোরেটর হিসাবে আপডেট_রেপ্পার () ডাকে সুবিধাজনক ফাংশন।

এটি আংশিক (আপডেট_ওয়ালা, মোড়ানো = মোড়ানো, নির্ধারিত = বরাদ্দ, আপডেট = আপডেট) সমতুল্য।

সুতরাং @ র্যাপস ডেকোরেটর আসলে ফান্টাকুলস. পার্টিশিয়ালকে কল দেয় (ফানক [, * আরগস] [, ** কীওয়ার্ড])।

Functools.partial () সংজ্ঞা বলছে যে

আংশিক () আংশিক ফাংশন অ্যাপ্লিকেশনের জন্য ব্যবহৃত হয় যা কোনও ফাংশনের আর্গুমেন্ট এবং / অথবা কীওয়ার্ডগুলির কিছু অংশকে সরল স্বাক্ষরযুক্ত একটি নতুন অবজেক্টের ফলে "স্থির করে দেয়"। উদাহরণস্বরূপ, আংশিক () একটি কলযোগ্য তৈরি করতে ব্যবহার করা যেতে পারে যা int () ফাংশনের মতো আচরণ করে যেখানে বেস আর্গুমেন্টটি দুটিতে ডিফল্ট হয়:

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

যা আমাকে এই সিদ্ধান্তে নিয়ে আসে যে, @ র্যাপগুলি আংশিক () কে কল দেয় এবং এটি আপনার প্যাঁচার ফাংশনটিকে প্যারামিটার হিসাবে পাস করে। আংশিক () শেষে সরলীকৃত সংস্করণটি প্রদান করে অর্থাৎ মোড়ক ফাংশনের ভিতরে যা আছে তার বস্তু এবং মোড়ক ফাংশন নিজেই নয়।


-4

সংক্ষেপে, functools.wraps কেবল একটি নিয়মিত ফাংশন। আসুন এই সরকারী উদাহরণ বিবেচনা করা যাক । সাহায্যে সোর্স কোড , আমরা ও বাস্তবায়ন সম্পর্কে আরো বিস্তারিত চলমান পদক্ষেপ নিম্নরূপ দেখতে পারেন:

  1. মোড়ক (চ) কোনও বস্তু ফেরায় , ও 1 বলুন । এটি আংশিক শ্রেণীর একটি বিষয়
  2. পরবর্তী পদক্ষেপটি হ'ল @ ও 1 ... যা অজগরের সাজসজ্জার স্বরলিপি। এর অর্থ

মোড়কের = O1 .__ কল __ (মোড়কের)

বাস্তবায়ন চেক করা হচ্ছে __call__ , আমরা এই পদক্ষেপ, (বাম দিকে) এর পর দেখতে মোড়কের হয়ে বস্তু দ্বারা ফলে self.func (* self.args, * args, ** newkeywords) সৃষ্টির চেক করা হচ্ছে O1 মধ্যে __new__ , আমরা জানা self.func ফাংশন update_wrapper । এটি প্যারামিটার * আরগগুলি , ডান হাতের মোড়কটিকে এর প্রথম প্যারামিটার হিসাবে ব্যবহার করে। শেষ পদক্ষেপ চেক করা হচ্ছে update_wrapper , এক ডান দিকে দেখতে পারেন মোড়কের হিসাবে প্রয়োজন পরিবর্তিত গুণাবলীর কিছু ফিরিয়ে দেওয়া হয়।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.