यह कैसे करता है?


181

मैं अपने सिर को प्राप्त करने में सक्षम नहीं हूं कि फंक्शंस में आंशिक कैसे काम करता है। मेरा निम्नलिखित कोड यहाँ से है :

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
    return x + y

>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

अब लाइन में

incr = lambda y : sum(1, y)

मुझे लगता है कि जो कुछ भी तर्क मैं करने के लिए पारित पाने incrके रूप में पारित हो जाएगा yकरने के लिए lambdaजो वापस आ जाएगी sum(1, y)यानी 1 + y

मैं समझता हूँ कि। लेकिन मुझे यह समझ में नहीं आया incr2(4)

आंशिक कार्य के 4रूप xमें कैसे हो जाता है? मेरे लिए, 4को प्रतिस्थापित करना चाहिए sum2। बीच का रिश्ता क्या है xऔर 4?

जवाबों:


218

मोटे तौर पर, partialऐसा कुछ होता है (कीवर्ड आर्ग्स सपोर्ट आदि के अलावा):

def partial(func, *part_args):
    def wrapper(*extra_args):
        args = list(part_args)
        args.extend(extra_args)
        return func(*args)

    return wrapper

इसलिए, partial(sum2, 4)आपको कॉल करके एक नया फ़ंक्शन (एक कॉल करने योग्य, सटीक होने के लिए) बनाता है जो व्यवहार करता है sum2, लेकिन एक स्थितीय तर्क कम है। उस गुम तर्क को हमेशा प्रतिस्थापित किया जाता है 4, ताकिpartial(sum2, 4)(2) == sum2(4, 2)

जैसे कि इसकी आवश्यकता क्यों है, विभिन्न प्रकार के मामले हैं। सिर्फ एक के लिए, मान लीजिए आपको एक समारोह पास करना है, जहाँ 2 तर्क होने की उम्मीद है:

class EventNotifier(object):
    def __init__(self):
        self._listeners = []

    def add_listener(self, callback):
        ''' callback should accept two positional arguments, event and params '''
        self._listeners.append(callback)
        # ...

    def notify(self, event, *params):
        for f in self._listeners:
            f(event, params)

लेकिन एक कार्य के लिए आपके पास पहले से ही किसी तीसरे contextऑब्जेक्ट तक पहुंच की आवश्यकता होती है :

def log_event(context, event, params):
    context.log_event("Something happened %s, %s", event, params)

तो, कई समाधान हैं:

एक कस्टम ऑब्जेक्ट:

class Listener(object):
   def __init__(self, context):
       self._context = context

   def __call__(self, event, params):
       self._context.log_event("Something happened %s, %s", event, params)


 notifier.add_listener(Listener(context))

लैम्ब्डा:

log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)

विभाजन के साथ:

context = get_context()  # whatever
notifier.add_listener(partial(log_event, context))

उन तीन में से, partialसबसे छोटा और सबसे तेज़ है। (अधिक जटिल मामले के लिए आपको एक कस्टम ऑब्जेक्ट चाहिए, हालांकि)।


1
यू कहां से प्राप्त हुआ extra_argsचर
user1865341

2
extra_argsकुछ ऐसा है जो आंशिक कॉलर द्वारा पारित किया गया है, उदाहरण में p = partial(func, 1); f(2, 3, 4)इसके साथ है (2, 3, 4)
बेरील

1
लेकिन हम ऐसा क्यों करेंगे, किसी भी विशेष उपयोग के मामले में जहां कुछ आंशिक रूप से ही किया जाना चाहिए और दूसरी चीज के साथ नहीं किया जा सकता है
user1865341

@ user1865341 मैंने उत्तर के लिए एक उदाहरण जोड़ा।
बेरियल

अपने उदाहरण के साथ, क्या संबंध है callbackऔरmy_callback
user1865341

92

partials अविश्वसनीय रूप से उपयोगी हैं।

उदाहरण के लिए, फ़ंक्शन कॉल के 'पाइप-लाइन किए गए' क्रम में (जिसमें किसी फ़ंक्शन से लौटाया गया मान तर्क अगले में पास हो जाता है)।

कभी-कभी इस तरह के पाइपलाइन में एक फ़ंक्शन को एक एकल तर्क की आवश्यकता होती है , लेकिन इसके तुरंत बाद का फ़ंक्शन दो मान देता है

इस परिदृश्य में, functools.partialआप इस फ़ंक्शन पाइपलाइन को अक्षुण्ण रखने की अनुमति दे सकते हैं।

यहां एक विशिष्ट, अलग-थलग उदाहरण दिया गया है: मान लीजिए कि आप कुछ लक्ष्य से प्रत्येक डेटा बिंदु की दूरी के आधार पर कुछ डेटा सॉर्ट करना चाहते हैं:

# create some data
import random as RND
fnx = lambda: RND.randint(0, 10)
data = [ (fnx(), fnx()) for c in range(10) ]
target = (2, 4)

import math
def euclid_dist(v1, v2):
    x1, y1 = v1
    x2, y2 = v2
    return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)

लक्ष्य से दूरी के आधार पर इस डेटा को क्रमबद्ध करने के लिए, आप जो करना चाहते हैं वह यह है:

data.sort(key=euclid_dist)

लेकिन आप नहीं कर सकते हैं - सॉर्ट विधि का प्रमुख पैरामीटर केवल उन कार्यों को स्वीकार करता है जो एक तर्क लेते हैं ।

इसलिए euclid_distएक एकल पैरामीटर लेने वाले फ़ंक्शन के रूप में फिर से लिखें :

from functools import partial

p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist अब एक भी तर्क स्वीकार करता है,

>>> p_euclid_dist((3, 3))
  1.4142135623730951

तो अब आप अपने डेटा को सॉर्ट विधि के प्रमुख तर्क के लिए आंशिक फ़ंक्शन में पास कर सकते हैं:

data.sort(key=p_euclid_dist)

# verify that it works:
for p in data:
    print(round(p_euclid_dist(p), 3))

    1.0
    2.236
    2.236
    3.606
    4.243
    5.0
    5.831
    6.325
    7.071
    8.602

या उदाहरण के लिए, फ़ंक्शन का एक तर्क बाहरी लूप में बदलता है लेकिन आंतरिक लूप में पुनरावृत्ति के दौरान तय किया जाता है। आंशिक का उपयोग करके, आपको आंतरिक लूप के पुनरावृत्ति के दौरान अतिरिक्त पैरामीटर में पास होने की आवश्यकता नहीं है, क्योंकि संशोधित (आंशिक) फ़ंक्शन की आवश्यकता नहीं है।

>>> from functools import partial

>>> def fnx(a, b, c):
      return a + b + c

>>> fnx(3, 4, 5)
      12

आंशिक फ़ंक्शन बनाएं (कीवर्ड arg का उपयोग करके)

>>> pfnx = partial(fnx, a=12)

>>> pfnx(b=4, c=5)
     21

आप स्थिति संबंधी तर्क के साथ एक आंशिक फ़ंक्शन भी बना सकते हैं

>>> pfnx = partial(fnx, 12)

>>> pfnx(4, 5)
      21

लेकिन यह फेंक देगा (उदाहरण के लिए, कीवर्ड तर्क के साथ आंशिक बनाना तो स्थिति संबंधी तर्क का उपयोग करके कॉल करना)

>>> pfnx = partial(fnx, a=12)

>>> pfnx(4, 5)
      Traceback (most recent call last):
      File "<pyshell#80>", line 1, in <module>
      pfnx(4, 5)
      TypeError: fnx() got multiple values for keyword argument 'a'

एक अन्य उपयोग मामला: अजगर के multiprocessingपुस्तकालय का उपयोग करके वितरित कोड लिखना । पूल विधि का उपयोग करके प्रक्रियाओं का एक पूल बनाया जाता है:

>>> import multiprocessing as MP

>>> # create a process pool:
>>> ppool = MP.Pool()

Pool एक नक्शा विधि है, लेकिन इसमें केवल एक ही पुनरावृत्ति होती है, इसलिए यदि आपको एक लंबी पैरामीटर सूची वाले फ़ंक्शन में पास होने की आवश्यकता है, तो फ़ंक्शन को आंशिक के रूप में फिर से परिभाषित करें, लेकिन सभी को ठीक करने के लिए:

>>> ppool.map(pfnx, [4, 6, 7, 8])

1
क्या इस फ़ंक्शन का कोई व्यावहारिक उपयोग कहीं है
user1865341

3
@ user1865341 मेरे जवाब देने के लिए दो exemplarly उपयोग के मामलों जोड़ा
डौग

IMHO, यह एक बेहतर जवाब है क्योंकि यह वस्तुओं और कक्षाओं जैसी असंबंधित अवधारणाओं को बाहर रखता है और उन कार्यों पर ध्यान केंद्रित करता है जो यह सब है।
अखाड़ा

35

संक्षिप्त उत्तर, partialएक फ़ंक्शन के मापदंडों को डिफ़ॉल्ट मान देता है जो अन्यथा डिफ़ॉल्ट मान नहीं होगा।

from functools import partial

def foo(a,b):
    return a+b

bar = partial(foo, a=1) # equivalent to: foo(a=1, b)
bar(b=10)
#11 = 1+10
bar(a=101, b=10)
#111=101+10

5
यह आधा सच है क्योंकि हम डिफ़ॉल्ट मानों को ओवरराइड कर सकते हैं, हम बाद में partialऔर इतने पर भी ओवरराइड मापदंडों को ओवरराइड कर सकते हैं
अज़ात इब्राकोव

33

नए व्युत्पन्न फ़ंक्शंस बनाने के लिए पार्टिकल्स का उपयोग किया जा सकता है जिसमें कुछ इनपुट पैरामीटर पूर्व-नियत हैं

भागमभाग के कुछ वास्तविक विश्व उपयोग को देखने के लिए, इस अच्छी ब्लॉग पोस्ट का संदर्भ लें:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

ब्लॉग से एक सरल लेकिन साफ ​​शुरुआत का उदाहरण, यह बताता है कि कोड को अधिक पठनीय बनाने के लिए कोई कैसे उपयोग partialकर सकता है re.searchre.searchविधि का हस्ताक्षर है:

search(pattern, string, flags=0) 

आवेदन करके partialहम searchअपनी आवश्यकताओं के अनुरूप नियमित अभिव्यक्ति के कई संस्करण बना सकते हैं , उदाहरण के लिए:

is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')

अब is_spaced_apartऔर is_grouped_togetherदो नए कार्य re.searchहैं जो patternतर्क से लागू होते हैं (चूंकि विधि के हस्ताक्षर patternमें पहला तर्क है re.search)।

इन दो नए कार्यों (हस्ताक्षर करने योग्य) का हस्ताक्षर है:

is_spaced_apart(string, flags=0)     # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied

इसके बाद आप कुछ पाठों पर इन आंशिक कार्यों का उपयोग कर सकते हैं:

for text in lines:
    if is_grouped_together(text):
        some_action(text)
    elif is_spaced_apart(text):
        some_other_action(text)
    else:
        some_default_action()

आप विषय की गहराई से समझ पाने के लिए ऊपर दिए गए लिंक का उल्लेख कर सकते हैं , क्योंकि यह इस विशिष्ट उदाहरण को शामिल करता है और बहुत कुछ ..


1
के बराबर नहीं है is_spaced_apart = re.compile('[a-zA-Z]\s\=').search? यदि हां, तो क्या इस बात की गारंटी है कि partialमुहावरे का पुन: उपयोग करने के लिए नियमित अभिव्यक्ति का संकलन किया जाता है?
एरिस्टाइड

10

मेरी राय में, यह अजगर में करी को लागू करने का एक तरीका है ।

from functools import partial
def add(a,b):
    return a + b

def add2number(x,y,z):
    return x + y + z

if __name__ == "__main__":
    add2 = partial(add,2)
    print("result of add2 ",add2(1))
    add3 = partial(partial(add2number,1),2)
    print("result of add3",add3(1))

परिणाम 3 और 4 है।


1

यह भी उल्लेख के लायक है कि जब आंशिक फ़ंक्शन ने एक और फ़ंक्शन पारित किया, जहां हम कुछ मापदंडों को "हार्ड कोड" करना चाहते हैं, तो यह सबसे सही पैरामीटर होना चाहिए

def func(a,b):
    return a*b
prt = partial(func, b=7)
    print(prt(4))
#return 28

लेकिन अगर हम ऐसा ही करते हैं, लेकिन इसके बजाय एक पैरामीटर बदल रहे हैं

def func(a,b):
    return a*b
 prt = partial(func, a=7)
    print(prt(4))

यह त्रुटि को फेंक देगा, "TypeError: func () को तर्क 'a' के लिए कई मान मिले


है ना? आप इस तरह से सबसे बाएं पैरामीटर को करें:prt=partial(func, 7)
DylanYoung

0

यह उत्तर एक उदाहरण कोड का अधिक है। उपरोक्त सभी उत्तर इस बारे में अच्छी व्याख्या देते हैं कि किसी को आंशिक का उपयोग क्यों करना चाहिए। मैं अपनी टिप्पणियों और आंशिक के बारे में मामलों का उपयोग कर दूँगा।

from functools import partial
 def adder(a,b,c):
    print('a:{},b:{},c:{}'.format(a,b,c))
    ans = a+b+c
    print(ans)
partial_adder = partial(adder,1,2)
partial_adder(3)  ## now partial_adder is a callable that can take only one argument

उपरोक्त कोड का आउटपुट होना चाहिए:

a:1,b:2,c:3
6

ध्यान दें कि उपरोक्त उदाहरण में एक नया कॉल करने योग्य लौटाया गया था जो कि तर्क के रूप में पैरामीटर (c) लेगा। ध्यान दें कि यह फ़ंक्शन का अंतिम तर्क भी है।

args = [1,2]
partial_adder = partial(adder,*args)
partial_adder(3)

उपरोक्त कोड का आउटपुट भी है:

a:1,b:2,c:3
6

ध्यान दें कि * का उपयोग गैर-कीवर्ड तर्कों को अनपैक करने के लिए किया गया था और कॉल करने योग्य लौटाया गया है, जिस तर्क के अनुसार इसे लिया जा सकता है।

एक और अवलोकन है: नीचे उदाहरण से पता चलता है कि आंशिक रिटर्न एक कॉल करने योग्य है जो अघोषित पैरामीटर (ए) को तर्क के रूप में ले जाएगा।

def adder(a,b=1,c=2,d=3,e=4):
    print('a:{},b:{},c:{},d:{},e:{}'.format(a,b,c,d,e))
    ans = a+b+c+d+e
    print(ans)
partial_adder = partial(adder,b=10,c=2)
partial_adder(20)

उपरोक्त कोड का आउटपुट होना चाहिए:

a:20,b:10,c:2,d:3,e:4
39

इसी तरह,

kwargs = {'b':10,'c':2}
partial_adder = partial(adder,**kwargs)
partial_adder(20)

कोड प्रिंट से ऊपर

a:20,b:10,c:2,d:3,e:4
39

मुझे इसका उपयोग तब करना था जब मैं मॉड्यूल Pool.map_asyncसे विधि का उपयोग कर रहा था multiprocessing। आप कार्यकर्ता फ़ंक्शन में केवल एक तर्क पास कर सकते हैं, इसलिए मुझे partialअपने कार्यकर्ता फ़ंक्शन को केवल एक इनपुट तर्क के साथ कॉल करने योग्य बनाने के लिए उपयोग करना था, लेकिन वास्तव में मेरे कार्यकर्ता फ़ंक्शन में कई इनपुट तर्क थे।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.