কীভাবে ফান্টুলগুলি আংশিকভাবে এটি করে?


180

আঞ্চলিকভাবে কীভাবে ফান্টুলগুলিতে কাজ করে আমি তার মাথা পেতে সক্ষম হই না। আমার কাছে এখান থেকে নিম্নলিখিত কোড রয়েছে :

>>> 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তবে এর একটি অবস্থানগত যুক্তি কম less অনুপস্থিত যুক্তিটি সর্বদা পরিবর্তিত হয় 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ভেরিয়েবলটি কোথা থেকে
পেয়েছেন

2
extra_argsএটি এমন কিছু যা আংশিক কলার দ্বারা উত্তীর্ণ হয়েছে, উদাহরণ সহ p = partial(func, 1); f(2, 3, 4)এটি (2, 3, 4)
বেরিয়াল

1
তবে আমরা কেন এটি করব, কোনও বিশেষ ব্যবহারের ক্ষেত্রে যেখানে কিছু আংশিক দ্বারা সম্পন্ন করতে হবে এবং অন্য জিনিস দিয়ে করা যাবে না
ব্যবহারকারীর 1865341

@ ব্যবহারকারী 1865341 আমি উত্তরে একটি উদাহরণ যুক্ত করেছি।
বেরিয়াল

আপনার উদাহরণ দিয়ে মধ্যে সম্পর্ক কি callbackএবংmy_callback
user1865341

92

পার্টিয়াল অবিশ্বাস্যভাবে দরকারী।

উদাহরণস্বরূপ, একটি 'পাইপ-রেখাযুক্ত' ফাংশন কলগুলির ক্রম (যার মধ্যে একটি ফাংশন থেকে প্রত্যাশিত মানটি পরের দিকে প্রেরণ করা আর্গুমেন্ট)।

কখনও কখনও এই ধরনের একটি পাইপলাইন একটি ফাংশন একটি প্রয়োজন একক যুক্তি , কিন্তু ফাংশন অবিলম্বে মূল প্রজেক্টের এটা ফেরৎ থেকে দুটি মানের

এই পরিস্থিতিতে, 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

একটি আংশিক ফাংশন তৈরি করুন (কীওয়ার্ড আরগ ব্যবহার করে)

>>> 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লাইব্রেরি ব্যবহার করে বিতরণ কোড লেখা writing পুল পদ্ধতি ব্যবহার করে একটি পুলের প্রক্রিয়া তৈরি করা হয়:

>>> import multiprocessing as MP

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

Pool মানচিত্রের পদ্ধতি রয়েছে তবে এটি কেবল একক পুনরাবৃত্তিযোগ্য লাগে, সুতরাং আপনাকে যদি দীর্ঘ পরামিতি তালিকার কোনও ফাংশনে পাস করার প্রয়োজন হয় তবে একটি ব্যতীত সমস্ত কিছু ঠিক করার জন্য আংশিক হিসাবে ফাংশনটিকে পুনরায় সংজ্ঞায়িত করুন:

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

1
এই ফাংশনটির কোনও ব্যবহারিক ব্যবহার রয়েছে কি না
ব্যবহারকারীর 1865341

3
@ user1865341 আমার উত্তর দুটি exemplarly ব্যবহারের ক্ষেত্রে যোগ
ডগ

আইএমএইচও, এটি আরও ভাল উত্তর কারণ এটি অবজেক্ট এবং শ্রেণীর মতো সম্পর্কযুক্ত ধারণা রাখে না এবং ফাংশনগুলিতে মনোনিবেশ করে যা এই বিষয়গুলি সম্পর্কে।
আখন

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এবং তাই
Azat Ibrakov

33

পার্টিয়ালগুলি নতুন উত্পন্ন ফাংশনগুলি তৈরি করতে ব্যবহার করা যেতে পারে যার কিছু পূর্বনির্ধারিত ইনপুট প্যারামিটার রয়েছে

Partials কিছু বাস্তব জগতে ব্যবহার দেখতে, এই সত্যিই ভাল ব্লগ পোস্টে উল্লেখ করুন:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

একটি সহজ কিন্তু ব্লগ থেকে ঝরঝরে শিক্ষানবিস উদাহরণ, কভার কিভাবে একটি ব্যবহার পারে partialউপর re.searchকোড আরো পাঠযোগ্য না। re.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 () আর্গুমেন্ট 'এ' এর জন্য একাধিক মান পেয়েছে"


তাই না? আপনি prt=partial(func, 7)
এটির

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

লক্ষ্য করুন যে উপরের উদাহরণে একটি নতুন কলযোগ্য ফিরিয়ে দেওয়া হয়েছিল যা প্যারামিটার (সি) হিসাবে যুক্তি হিসাবে গ্রহণ করবে। মনে রাখবেন যে এটিও ফাংশনের শেষ যুক্তি।

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আমার কর্মী ফাংশনটিতে কেবলমাত্র একটি ইনপুট আর্গুমেন্ট সহ কলযোগ্য হিসাবে দেখতে ব্যবহার করতে হয়েছিল তবে বাস্তবে আমার কর্মী ফাংশনে একাধিক ইনপুট আর্গুমেন্ট ছিল।

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