Functools kısmi ne yapıyor?


181

Ben functools kısmi nasıl çalışır hakkında başımı alamıyorum. Buradan aşağıdaki kodu aldım :

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

Şimdi sırada

incr = lambda y : sum(1, y)

Ben geçmek ne olursa olsun argüman olsun incrolarak geçirilecek bunun yiçin lambdadönecektir hangi sum(1, y)ie 1 + y.

Onu anlıyorum. Ama bunu anlamadım incr2(4).

Kısmi işlevdeki 4gibi geçişler nasıl yapılır x? Benim için 4yerine sum2. Arasındaki ilişki nedir xve 4?

Yanıtlar:


218

Kabaca, partialböyle bir şey yapar (anahtar kelime argümanları desteği dışında):

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

    return wrapper

Böylece, partial(sum2, 4)sizi çağıran sum2, ancak daha az bir konumsal argümanı olan yeni bir işlev (kesin olarak çağrılabilir) oluşturun . Bu eksik argüman her zaman ile değiştirilir 4, böylecepartial(sum2, 4)(2) == sum2(4, 2)

Neden gerekli olduğuna gelince, çeşitli durumlar var. Birincisi, bir işlevi 2 argümanı olması beklenen bir yerde geçirmeniz gerektiğini varsayalım:

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)

Ancak, zaten sahip olduğunuz bir işlevin contextişini yapmak için üçüncü bir nesneye erişmesi gerekir :

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

Yani, birkaç çözüm var:

Özel bir nesne:

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

Lambda:

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

Kısmi ile:

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

Bu üçünden partialen kısa ve en hızlısıdır. (Daha karmaşık bir durum için özel bir nesne isteyebilirsiniz).


1
u extra_argsdeğişkeni
nereden aldınız

2
extra_argskısmi arayan tarafından geçen bir şeydir, p = partial(func, 1); f(2, 3, 4)onunla örnekte (2, 3, 4).
bereal

1
ama bunu neden yapalım ki, bir şeyin sadece kısmi olarak yapılması gereken ve başka bir şeyle yapılamayacağı herhangi bir özel kullanım durumu
user1865341

@ user1865341 Cevaba bir örnek ekledim.
bereal

senin örnekle, arasındaki ilişki nedir callbackvemy_callback
user1865341

92

parsiyeller inanılmaz derecede faydalıdır.

Örneğin, 'boru hatlı' işlev çağrıları dizisinde (bir işlevden döndürülen değerin bir sonrakine iletilen argüman olduğu).

Bazen böyle bir boru hattındaki bir işlev için tek bir bağımsız değişken gerekir , ancak hemen akış yönündeki işlev iki değer döndürür .

Bu senaryoda, functools.partialbu işlev ardışık düzenini sağlam tutmanıza izin verebilir.

Aşağıda belirli ve izole bir örnek verilmiştir: bazı verileri her veri noktasının belirli bir hedeften uzaklığına göre sıralamak istediğinizi varsayalım:

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

Bu verileri hedeften uzaklığa göre sıralamak için, elbette yapmak istediğiniz şey şudur:

data.sort(key=euclid_dist)

ancak yapamazsınız - sort yönteminin anahtar parametresi yalnızca tek bir argüman alan işlevleri kabul eder .

tek bir parametre euclid_distalan bir işlev olarak yeniden yazın :

from functools import partial

p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist şimdi tek bir argümanı kabul ediyor,

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

böylece sıralama yönteminin anahtar bağımsız değişkeninin kısmi işlevini ileterek verilerinizi sıralayabilirsiniz:

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

Veya örneğin, işlevin bağımsız değişkenlerinden biri dış döngüde değişir, ancak iç döngüde yineleme sırasında sabitlenir. Kısmi kullanarak, iç döngünün yinelemesi sırasında ek parametreyi iletmeniz gerekmez, çünkü değiştirilmiş (kısmi) işlev gerektirmez.

>>> from functools import partial

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

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

kısmi işlev oluşturma (arg anahtar sözcüğünü kullanarak)

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

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

konumsal bir argümanla kısmi bir işlev de oluşturabilirsiniz

>>> pfnx = partial(fnx, 12)

>>> pfnx(4, 5)
      21

ancak bu atar (örneğin, anahtar kelime bağımsız değişkeniyle kısmi oluşturma ve ardından konumsal bağımsız değişkenler kullanarak çağırma)

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

başka bir kullanım örneği: python multiprocessingkütüphanesini kullanarak dağıtılmış kod yazma . Havuz yöntemi kullanılarak bir işlem havuzu oluşturulur:

>>> import multiprocessing as MP

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

Pool bir harita yöntemine sahiptir, ancak yalnızca tek bir yinelenebilir sürer, bu nedenle daha uzun bir parametre listesiyle bir işlevi iletmeniz gerekiyorsa, biri hariç tümünü düzeltmek için işlevi kısmi olarak yeniden tanımlayın:

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

1
Bu fonksiyon somewher herhangi pratik kullanımı vardır
user1865341

3
@ user1865341 cevabım için iki örnek kullanım örneği ekledi
doug

IMHO, bu daha iyi bir cevap çünkü nesneler ve sınıflar gibi ilgisiz kavramları uzak tutuyor ve bununla ilgili olan fonksiyonlara odaklanıyor.
akhan

35

kısa cevap, partialaksi takdirde varsayılan değerleri olmayan bir fonksiyonun parametrelerine varsayılan değerler verir.

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
Biz varsayılan değerleri geçersiz kılabilirler hatta müteakip parametreleri overriden geçersiz çünkü bu doğrudur yarısıdır partialve benzeri
Azat Ibrakov

33

Kısmi değerler, bazı girdi parametreleri önceden atanmış yeni türetilmiş işlevler yapmak için kullanılabilir

Kısmi gerçek dünya kullanımını görmek için, bu gerçekten iyi blog gönderisine bakın:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

Basit ama blogdan düzgün acemi örnek kapaklar birini kullanabilir nasıl partialüzerinde re.searchkod daha okunabilir hale getirmek için. re.searchyöntemin imzası:

search(pattern, string, flags=0) 

Uygulayarak partialbiz normal ifadenin birden çok sürümünü oluşturabilir searchböylece örneğin, şartlarımıza uygun:

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

Şimdi is_spaced_apartve is_grouped_togethertüretilmiş iki yeni fonksiyonlar re.searcholduğunu var pattern(çünkü argüman uygulanan patternilk argüman olan re.searchyöntemin imzasının).

Bu iki yeni işlevin imzası (çağrılabilir):

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

Daha sonra bu kısmi işlevleri bazı metinlerde şu şekilde kullanabilirsiniz:

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

Bu özel örneği ve çok daha fazlasını kapsadığı için konuyu daha derinlemesine anlamak için yukarıdaki bağlantıya başvurabilirsiniz .


1
Bu eşdeğer değil is_spaced_apart = re.compile('[a-zA-Z]\s\=').searchmi? Öyleyse, partialdeyimin daha hızlı yeniden kullanım için normal ifadeyi derlemesinin bir garantisi var mı?
Aristide

10

Benim düşünceme göre, bu pitonda körüklemenin bir yoludur .

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

Sonuç 3 ve 4'tür.


1

Ayrıca, kısmi işlev bazı parametreleri "sabit kodlamak" istediğimiz başka bir işlevi geçtiğinde, bunun en sağdaki parametre olması gerektiğini belirtmek gerekir.

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

ama aynısını yaparsak, bunun yerine bir parametreyi değiştirirsek

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

hata verir, "TypeError: func () 'a' argümanı için birden fazla değer aldı"


Ha? En soldaki parametreyi şu şekilde prt=partial(func, 7)
yaparsınız

0

Bu cevap daha çok örnek bir koddur. Yukarıdaki tüm cevaplar kişinin neden kısmi kullanması gerektiği konusunda iyi açıklamalar vermektedir. Gözlemlerimi vereceğim ve kısmi davaları kullanacağım.

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

Yukarıdaki kodun çıktısı:

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

Yukarıdaki örnekte, argüman olarak (c) parametresini alacak yeni bir callable döndürüldüğüne dikkat edin. Ayrıca işlevin son argümanı olduğunu unutmayın.

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

Yukarıdaki kodun çıktısı da:

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

* Anahtar kelime olmayan argümanları açmak için kullanıldığına ve hangi argümanın alabileceği açısından döndürülen callable öğesinin yukarıdakiyle aynı olduğuna dikkat edin.

Başka bir gözlem şudur: Aşağıdaki örnek, kısmi, bildirilmemiş parametreyi (a) argüman olarak alacak olan bir çağrılabilir işareti döndürdüğünü göstermektedir.

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)

Yukarıdaki kodun çıktısı:

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

Benzer şekilde,

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

Yukarıdaki kod baskıları

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

Modülden Pool.map_asyncyöntem kullanırken kullanmak zorunda kaldım multiprocessing. Worker (işçi) işlevine yalnızca bir argüman iletebilirsiniz, bu nedenle partialworker (işçi) işlevimin yalnızca tek bir girdi argümanı ile çağrılabilir gibi görünmesini sağlamak zorunda kaldım, ancak gerçekte worker (işçi) işlevimin birden çok girdi argümanı vardı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.