2012-05-04 1076 views
6

पर'starmap` को प्राथमिकता दी जा सकती है, तो Clunky calculation of differences between an incrementing set of numbers, is there a more beautiful way? प्रश्न का उत्तर देते हुए, मैं दो समाधानों के साथ आया, List Comprehension और अन्य itertools.starmap का उपयोग कर रहा था।जब 'सूची समझ'

मेरे लिए, list comprehension सिंटेक्स अधिक स्पष्ट, पठनीय, कम वर्बोज़ और अधिक पायथनिक दिखता है। लेकिन अभी भी starmap इटारटोल में अच्छी तरह से उपलब्ध है, मैं सोच रहा था, इसके लिए एक कारण होना चाहिए।

मेरा प्रश्न है जब starmapList Comprehension से अधिक पसंद किया जा सकता है?

नोट शैली की बात तो यह निश्चित रूप से There should be one-- and preferably only one --obvious way to do it.

हेड हेड से तुलना

पठनीयता में गिना जाता है के विपरीत है तो इसकी। --- LC

इसकी फिर से धारणा की बात है, लेकिन मेरे लिए LCstarmap की तुलना में अधिक पठनीय है। starmap का उपयोग करने के लिए, या तो आपको operator आयात करने की आवश्यकता है, या lambda या कुछ स्पष्ट multi-variable फ़ंक्शन को परिभाषित करने और फिर भी itertools से अतिरिक्त आयात को आयात करने की आवश्यकता है।

प्रदर्शन --- LC

>>> def using_star_map(nums): 
    delta=starmap(sub,izip(nums[1:],nums)) 
    return sum(delta)/float(len(nums)-1) 
>>> def using_LC(nums): 
    delta=(x-y for x,y in izip(nums[1:],nums)) 
    return sum(delta)/float(len(nums)-1) 
>>> nums=[random.randint(1,10) for _ in range(100000)] 
>>> t1=Timer(stmt='using_star_map(nums)',setup='from __main__ import nums,using_star_map;from itertools import starmap,izip') 
>>> t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC;from itertools import izip') 
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000) 
235.03 usec/pass 
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000) 
181.87 usec/pass 
+0

मुझे नहीं लगता कि आपके द्वारा किए गए तरीके की तुलना करना उचित है। दोनों कार्यों को मतभेदों को 'डेल्टास' में सहेजना चाहिए क्योंकि फिलहाल 'use_star_map' कम पठनीय है क्योंकि यह सब एक पंक्ति में है। इसे यहां बदलें: 'deltas = starmap (उप, ज़िप (nums [1:], nums)) '' sum (deltas)/float (len (nums) -1) ' – jamylak

+0

@jamylak: इसे इंगित करने के लिए धन्यवाद। लेकिन दुर्भाग्य से यह प्रदर्शन अंतर नहीं बदलता है। – Abhijit

+0

इसका मतलब नहीं था लेकिन हम पठनीयता के बारे में भी बात कर रहे हैं। – jamylak

उत्तर

3

यह काफी हद तक एक शैली बात है। जो भी आपको अधिक पठनीय लगता है चुनें।

"आप इस TOOWTDI का उल्लंघन करती है सोच सकते हैं, लेकिन जैसा कि मैंने पहले भी कहा है, कि एक था:

को" वहाँ सिर्फ एक ही रास्ता यह करने के लिए है "संबंध में, स्वेन Marnach कृपया इस Guido quote प्रदान करता है सफेद झूठ (साथ ही 2000 के आसपास पर्ल के नारे के लिए एक गाल प्रतिक्रिया)। (मानव पाठकों के लिए) के इरादे व्यक्त करने में सक्षम होने के नाते अक्सर कई रूपों है कि अनिवार्य रूप से एक ही बात, है, लेकिन पाठक के लिए अलग लग बीच चुनने की आवश्यकता है। "

एक प्रदर्शन हॉटस्पॉट में, आप चुनना चाहेंगे समाधान जो सबसे तेज़ चलता है (जो मुझे लगता है कि इस मामले में starmap आधारित होगा)।

प्रदर्शन पर - स्टर्मैप इसके विनाशकारी के कारण धीमा है; तथापि Starmap यहाँ आवश्यक नहीं है:

from timeit import Timer 
import random 
from itertools import starmap, izip,imap 
from operator import sub 

def using_imap(nums): 
    delta=imap(sub,nums[1:],nums[:-1]) 
    return sum(delta)/float(len(nums)-1) 

def using_LC(nums): 
    delta=(x-y for x,y in izip(nums[1:],nums)) 
    return sum(delta)/float(len(nums)-1) 

nums=[random.randint(1,10) for _ in range(100000)] 
t1=Timer(stmt='using_imap(nums)',setup='from __main__ import nums,using_imap') 
t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC') 

अपने कंप्यूटर पर:

>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000) 
172.86 usec/pass 
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000) 
178.62 usec/pass 

imap एक छोटा सा तेजी से बाहर आता है, शायद इसलिए कि यह ज़िप करने/destructuring टाल।

+0

लेकिन पायथन के जेन कहते हैं, 'एक होना चाहिए - और अधिमानतः इसे करने का एकमात्र तरीका।' – Abhijit

+0

@ अभिजीत हां, लेकिन आपको जो कुछ भी पढ़ा जाता है उस पर विश्वास नहीं करना चाहिए। पाइथन में अक्सर दिए गए कार्य को पूरा करने के कई समान तरीके हैं। – Marcin

+0

लेकिन क्या यह पाइथन की कोर फिलॉसफी नहीं है जो इसे 'रूबी', 'पर्ल' से अलग बनाती है .... – Abhijit

10

अंतर जो मैं सामान्य रूप से देखता हूं वह map()/starmap() सबसे उपयुक्त है जहां आप सचमुच सूची में प्रत्येक आइटम पर फ़ंक्शन को कॉल कर रहे हैं।इस मामले में, वे एक छोटे से स्पष्ट कर रहे हैं:

(f(x) for x in y) 
map(f, y) # itertools.imap(f, y) in 2.x 

(f(*x) for x in y) 
starmap(f, y) 

जैसे ही आप lambda या filter में फेंक के रूप में अच्छी तरह से की आवश्यकता होगी, शुरू करते हैं, आप सूची कंप्यूटर अनुप्रयोग/जनरेटर अभिव्यक्ति करने के लिए स्विच करना चाहिए, लेकिन ऐसे मामलों में जहां यह एक है एकल कार्य, वाक्य रचना की जनरेटर अभिव्यक्ति के लिए वाक्यविन्यास बहुत verbose लगता है।

वे परस्पर विनिमय कर रहे हैं, और जहां संदेह में, जनरेटर अभिव्यक्ति से चिपक के रूप में यह सामान्य रूप में अधिक पठनीय है, लेकिन एक साधारण मामला (map(int, strings), starmap(Vector, points)) का उपयोग कर map()/starmap() कभी कभी चीजों को आसान को पढ़ने के लिए कर सकते हैं।

उदाहरण:

एक उदाहरण मैं कहाँ लगता है starmap() अधिक पठनीय है:

from collections import namedtuple 
from itertools import starmap 

points = [(10, 20), (20, 10), (0, 0), (20, 20)] 

Vector = namedtuple("Vector", ["x", "y"]) 

for vector in (Vector(*point) for point in points): 
    ... 

for vector in starmap(Vector, points): 
    ... 

और map() के लिए:

values = ["10", "20", "0"] 

for number in (int(x) for x in values): 
    ... 

for number in map(int, values): 
    ... 

प्रदर्शन:

python -m timeit -s "from itertools import starmap" -s "from operator import sub" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(sub, numbers))"       
1000000 loops, best of 3: 0.258 usec per loop 

python -m timeit -s "numbers = zip(range(100000), range(100000))" "sum(x-y for x, y in numbers)"       
1000000 loops, best of 3: 0.446 usec per loop 

एक namedtuple निर्माण के लिए:

python -m timeit -s "from itertools import starmap" -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "list(starmap(Vector, numbers))" 
1000000 loops, best of 3: 0.98 usec per loop 

python -m timeit -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "[Vector(*pos) for pos in numbers]" 
1000000 loops, best of 3: 0.375 usec per loop 

मेरी परीक्षण, जहां हम सरल कार्य (कोई lambda) का उपयोग कर के बारे में बात कर रहे हैं, starmap() बराबर जनरेटर अभिव्यक्ति की तुलना में तेजी है। स्वाभाविक रूप से, प्रदर्शन को पीछे की ओर लेना चाहिए जब तक कि यह एक सिद्ध बाधा न हो।

कैसे lambda किसी भी प्रदर्शन लाभ, पहले सेट में के रूप में ही उदाहरण मारता है का उदाहरण है, लेकिन साथ lambda बजाय operator.sub():

python -m timeit -s "from itertools import starmap" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(lambda x, y: x-y, numbers))" 
1000000 loops, best of 3: 0.546 usec per loop 
+0

'मानचित्र (एफ, वाई)' वाई में x के लिए '[f (x) के बराबर है '' और' (x (x) x में x के लिए नहीं), क्योंकि यह जनरेटर नहीं है। यह एक बार में निष्पादित करता है। – akaRem

+1

@akaRem Lattyware हमेशा अजगर का उपयोग करता है 3. – Marcin

+0

@akaRem क्षमा करें, मैं Python 3.x - वास्तव में, 2.x में बात कर रहा हूं, यह सच है। स्पष्टीकरण के लिए अद्यतन किया गया। –

0

Starmap बारे में .. चलें कहते हैं कि तुम L = [(0,1,2),(3,4,5),(6,7,8),..] है।

जेनरेटर comprehansion तरह

(f(a,b,c) for a,b,c in L) 

या

(f(*item) for item in L) 

लगेगा और Starmap लगेगा

starmap(f, L) 

की तरह तीसरे संस्करण हल्का और कम है। लेकिन पहला व्यक्ति बहुत स्पष्ट है और यह मुझे कुछ करने के लिए मजबूर नहीं करता है कि यह क्या करता है।

ठीक है। अब मैं और अधिक जटिल इन-लाइन कोड लिखना चाहता हूं ..

some_result = starmap(f_res, [starmap(f1,L1), starmap(f2,L2), starmap(f3,L3)]) 

यह पंक्ति स्पष्ट नहीं है, लेकिन अभी भी समझने में आसान है .. जनरेटर comprehansion यह कैसा दिखेगा में:

some_result = (f_res(a,b,c) for a,b,c in [(f1(a,b,c) for a,b,c in L1), (f2(a,b,c) for a,b,c in L2), (f3(a,b,c) for a,b,c in L3)]) 

जैसा कि आप देख, यह लंबे समय से समझने के लिए भारी है और, एक पंक्ति में नहीं रखा जा सकता है, क्योंकि यह बड़ा 79 वर्ण (पीईपी 8) है। यहां तक ​​कि छोटा संस्करण भी खराब है:

some_result = (f_res(*item) for item [(f1(*item) for item in L1), (f(*item2) for item in L2), (f3(*item) for item in L3)]) 

बहुत सारे पात्र .. बहुत सारे ब्रैकेट .. बहुत अधिक शोर।

तो। कुछ मामलों में Starmap एक बहुत ही उपयोगी उपकरण है। इसके साथ आप कम कोड लिख सकते हैं जो समझने में आसान है।

संपादित कुछ डमी परीक्षण

from timeit import timeit 
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(a,b,c)for a,b,c in L))") 
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(*item)for item in L))") 
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list(starmap(max,L))") 

आउटपुट (अजगर 2.7.2) जोड़ा

5.23479851154 
5.35265309689 
4.48601346328 

तो, Starmap भी ~ 15% तेजी से यहाँ है।

+0

मैं तर्क दूंगा कि आपके अधिक जटिल मामले में, 'starmap()' और जनरेटर अभिव्यक्ति दोनों एक अच्छा समाधान नहीं हैं। यह उस बिंदु पर आपके कोड (उदाहरण के लिए, पूर्ण जनरेटर में) का विस्तार करने के लिए और अधिक पठनीय है। –

+0

क्या 'बहुत अधिक शोर' व्याकरणिक त्रुटि है या क्या यह जानबूझकर है? – jamylak

+0

अंग्रेजी मेरी मूल भाषा नहीं है .. मेरा मतलब है कि कई अलग-अलग वर्ण हैं जो दृश्य शोर करते हैं। और इसलिए यह कोड सरल पढ़ने के लिए भी मुश्किल है (समझने की कोशिश किए बिना)। – akaRem