Python'da işlev zinciri oluşturma


90

On Codewars.com ben şu görevi karşılaştı:

addArt arda çağrıldığında sayıları toplayan bir işlev oluşturun . Yani add(1)dönmeli 1, add(1)(2)dönmeli 1+2, ...

Ben Python temel aşina olduğum sürece, ben bir işlev, yani böyle arka arkaya çağrılacak yapabiliyor bir işlevi hiç karşılaşmadığımız f(x)olarak adlandırılabilir f(x)(y)(z).... Şimdiye kadar, bu gösterimi nasıl yorumlayacağımı bile bilmiyorum.

Bir matematikçi olarak, bunun f(x)(y)her xbir işlevi atayan g_{x}ve sonra geri dönen g_{x}(y)ve benzer şekilde bir işlev olduğundan şüpheleniyorum f(x)(y)(z).

Bu yorum doğru olursa, Python bana çok ilginç görünen dinamik olarak işlevler oluşturmama izin verirdi. Son bir saattir web'de arama yaptım, ancak doğru yönde bir müşteri adayı bulamadım. Bu programlama kavramının nasıl adlandırıldığını bilmediğim için, bu çok şaşırtıcı olmayabilir.

Bu kavramı nasıl adlandırıyorsunuz ve bunun hakkında daha fazla nereden okuyabilirim?



2
İpucu: İç içe geçmiş bir işlev dinamik olarak oluşturulur, üst işlevinin yerellerine erişebilir ve bir (çağrılabilir) nesne olarak döndürülebilir.
Jonathon Reinhart

@JonathonReinhart Problem hakkında böyle düşünüyordum. Ama nasıl uygulanacağını gerçekten göremedim.
Stefan Mesken

3
Bir kenara: Python kesinlikle dinamik olarak işlevler oluşturmanıza izin verecektir. İlginizi çekiyorsa, işte okuyabileceğiniz birkaç ilgili kavram: WP: Birinci sınıf işlevler | Python'da daha yüksek dereceden bir işlevi nasıl yaparsınız? | functools.partial()| WP: Closures
Lukas Graf

@LukasGraf Bir bakacağım. Teşekkür ederim!
Stefan Mesken

Yanıtlar:


102

Bunun olup olmadığını bilmiyoruz işlev öyle olduğu kadar zincirleme çağrılabilir fonksiyonları beri zincirleme ama vardır Yaptığım hiçbir zarar gelmez sanırım callables. Her iki durumda da, bunu yapmanın iki yolu var:

Alt sınıflandırma intve tanımlama __call__:

İlk yol , güncellenmiş değerle kendisinin yeni bir örneğini döndüren özel bir intalt sınıftır __call__:

class CustomInt(int):
    def __call__(self, v):
        return CustomInt(self + v)

İşlev addartık CustomInt, kendisinin güncellenmiş bir değerini döndüren bir çağrılabilir olarak arka arkaya çağrılabilen bir örnek döndürmek için tanımlanabilir :

>>> def add(v):
...    return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44)  # and so on..
50

Ek olarak, bir int alt sınıf olarak, döndürülen değer s'nin __repr__ve __str__davranışını korur int. Daha karmaşık işlemler için, diğer kaçışları uygun şekilde tanımlamalısınız .

@Caridorc'un bir yorumda belirttiği gibi, addbasitçe şu şekilde de yazılabilir:

add = CustomInt 

Sınıfı addyerine olarak yeniden adlandırmak CustomIntda benzer şekilde çalışır.


Bir kapanış tanımlayın, değer elde etmek için ekstra çağrı gerektirir:

Aklıma gelen tek yol, sonucu döndürmek için fazladan boş bir argüman çağrısı gerektiren iç içe geçmiş bir işlevi içerir. Ben değilim değil kullanaraknonlocal ve fonksiyon nesnelere özelliklerini eklemek için opt pitonlarına arasında taşınabilir hale getirmek için:

def add(v):
    def _inner_adder(val=None):  
        """ 
        if val is None we return _inner_adder.v 
        else we increment and return ourselves
        """
        if val is None:    
            return _inner_adder.v
        _inner_adder.v += val
        return _inner_adder
    _inner_adder.v = v  # save value
    return _inner_adder 

Bu sürekli olarak kendisini ( _inner_adder) döndürür , eğer a valsağlanırsa onu artırır ( _inner_adder += val) ve değilse, değeri olduğu gibi döndürür. Bahsettiğim gibi, ()artan değeri döndürmek için ekstra bir çağrı gerektirir :

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6

6
Etkileşimli kodda add = CostumIntda çalışmalı ve daha basit olmalıdır.
Caridorc

4
Yerleşikleri alt sınıflandırma ile ilgili sorun, çağrılabilir olmayan (2*add(1)(2))(3)bir TypeErrorçünkü ile başarısız olmasıdır int. Temel olarak, arama dışında herhangi bir bağlamda kullanıldığında CustomIntdüze dönüştürülür . Daha sağlam bir çözüm için , sürümler de dahil olmak üzere temelde tüm yöntemleri yeniden uygulamanız gerekir ...int__*____r*__
Bakuriu

@Caridorc Ya da hiç çağırmayın CustomIntama addonu tanımlarken.
minipif

27

Benden nefret edebilirsin, ama işte tek satırlık :)

add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)

Düzenleme: Tamam, bu nasıl çalışır? Kod @Jim'in cevabı ile aynıdır, ancak her şey tek bir satırda gerçekleşir.

  1. typeYeni türleri oluşturmak için de kullanılabilir: type(name, bases, dict) -> a new type. İçin nameadı gerçekten bu durumda gerekli değildir biz, boş bir dize sağlamaktadır. İçin bases(tuple) biz temin (int,)miras özdeş olan, int. lambdayı dicteklediğimiz sınıf nitelikleridir __call__.
  2. self.__class__(self + v) özdeş return CustomInt(self + v)
  3. Yeni tür oluşturulur ve dış lambda içinde döndürülür.

17
Daha da class add(int):__call__ = lambda self, v: add(self+v)
kısası

3
Bir sınıfın içindeki kod tam olarak normal kod gibi yürütülür, böylece atamalarla özel yöntemler tanımlayabilirsiniz. Tek fark, sınıf kapsamının biraz ... tuhaf olmasıdır.
Bakuriu

16

Birden çok kez çağrılacak bir işlev tanımlamak istiyorsanız, önce her seferinde çağrılabilir bir nesne (örneğin bir işlev) döndürmeniz gerekir, aksi takdirde __call__çağrılabilir olması için bir öznitelik tanımlayarak kendi nesnenizi oluşturmanız gerekir .

Bir sonraki nokta, tüm argümanları korumanız gerektiğidir; bu durumda, Doğrular veya özyinelemeli bir işlev kullanmak isteyebilirsiniz . Ancak Coroutine'lerin , özellikle bu tür görevler için özyinelemeli işlevlerden çok daha optimize / esnek olduğunu unutmayın .

İşte Coroutines kullanan ve kendisinin en son durumunu koruyan örnek bir fonksiyon. Dönüş değeri integerçağrılamayan bir değer olduğu için birden çok kez çağrılamayacağına dikkat edin, ancak bunu beklenen nesneye dönüştürmeyi düşünebilirsiniz ;-).

def add():
    current = yield
    while True:
        value = yield current
        current = value + current


it = add()
next(it)
print(it.send(10))
print(it.send(2))
print(it.send(4))

10
12
16

6

Bunu yapmanın pitonik yolu dinamik argümanlar kullanmak olacaktır:

def add(*args):
    return sum(args)

Aradığınız cevap bu değil ve bunu biliyor olabilirsiniz, ama yine de vereceğimi düşündüm çünkü eğer birisi bunu meraktan değil iş için yapmayı merak ediyorsa. Muhtemelen "yapılacak doğru şey" cevabına sahip olmalılar.


1
' PS ' notunu kaldırdım , nichochar. Hepimiz Python'un ne kadar zarif olduğunun farkındayız :-) Cevabın gövdesine ait olduğunu sanmıyorum.
Dimitris Fasarakis Hilliard

6
Bence add = sumo rotaya gidecek
olsaydın

6

Basitçe:

class add(int):
   def __call__(self, n):
      return add(self + n)

1
Bu kolayca en iyi cevap!
MEMark

6

Sonucu almak için bir ek kabul etmek ()istiyorsanız, şunları kullanabilirsiniz functools.partial:

from functools import partial

def add(*args, result=0):
    return partial(add, result=sum(args)+result) if args else result

Örneğin:

>>> add(1)
functools.partial(<function add at 0x7ffbcf3ff430>, result=1)
>>> add(1)(2)
functools.partial(<function add at 0x7ffbcf3ff430>, result=3)
>>> add(1)(2)()
3

Bu, aynı anda birden fazla numara belirlemeye de izin verir:

>>> add(1, 2, 3)(4, 5)(6)()
21

Tek bir numara ile sınırlamak istiyorsanız, aşağıdakileri yapabilirsiniz:

def add(x=None, *, result=0):
    return partial(add, result=x+result) if x is not None else result

Eğer add(x)(y)(z)sonucu hemen döndürmek ve daha fazla çağrılabilir olmak istiyorsanız , alt sınıflandırma intgitmenin yoludur.

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.