python - একটি 'for` লুপ কেন সত্য মান গণনা করতে এত দ্রুত?




python-3.x performance (4)

@ মার্কাস মেসকেনের উত্তরের ডান বিট আছে - ফাংশন কলগুলি ধীরে ধীরে, এবং উভয় জেনেক্সপ্রস এবং লিস্টকম্পগুলি মূলত ফাংশন কল হয়।

যাইহোক, ব্যবহারিক হতে হবে:

str.count(c) ব্যবহার করা দ্রুত, এবং পাইথনের strpbrk() সম্পর্কে আমার সম্পর্কিত এই সম্পর্কিত উত্তরগুলি এখনও আরও দ্রুততর করতে পারে।

def count_even_digits_spyr03_count(n):
    s = str(n)
    return sum(s.count(c) for c in "02468")


def count_even_digits_spyr03_count_unrolled(n):
    s = str(n)
    return s.count("0") + s.count("2") + s.count("4") + s.count("6") + s.count("8")

ফলাফল:

string length: 502
count_even_digits_spyr03_list 0.04157966522
count_even_digits_spyr03_sum 0.05678154459
count_even_digits_spyr03_for 0.036128606150000006
count_even_digits_spyr03_count 0.010441866129999991
count_even_digits_spyr03_count_unrolled 0.009662931009999999

সম্প্রতি আমি একটি বোন সাইটে একটি প্রশ্ন জারি করেছি যা একটি ফাংশনটির জন্য জিজ্ঞাসা করেছিল যা একটি সংখ্যার সবকটি সংখ্যা গণনা করে। অন্যান্য উত্তরগুলির মধ্যে একটিতে দুইটি ফাংশন রয়েছে (যা দ্রুততম হয়ে উঠেছে):

def count_even_digits_spyr03_for(n):
    count = 0
    for c in str(n):
        if c in "02468":
            count += 1
    return count

def count_even_digits_spyr03_sum(n):
    return sum(c in "02468" for c in str(n))

উপরন্তু আমি একটি তালিকা বোঝার এবং list.count ব্যবহার list.count :

def count_even_digits_spyr03_list(n):
    return [c in "02468" for c in str(n)].count(True)

প্রথম দুটি ফাংশনগুলি অবশ্যই একই রকম, কেবলমাত্র প্রথমটি স্পষ্ট গণনা লুপ ব্যবহার করে, অন্যটি বিল্ট-ইন sum ব্যবহার করে। আমি আশা করি দ্বিতীয়টি দ্রুততর হবে (উদাহরণস্বরূপ এই উত্তরটির উপর ভিত্তি করে), এবং যদি আমি পূর্বের দিকে পর্যালোচনা করার জন্য অনুরোধ জানাই তবে আমি এটি সুপারিশ করব। কিন্তু, এটি প্রায় অন্য উপায় খুঁজে বের করে। সংখ্যার সংখ্যা বাড়ানোর সাথে সাথে কিছু র্যান্ডম সংখ্যার সাথে এটি পরীক্ষা করে দেখুন (তাই যে কোনো একক সংখ্যাও প্রায় 50% হয়) আমি নিম্নলিখিত সময়গুলি পাই:

কেন লুপ for ম্যানুয়াল এত দ্রুত? এটি sum চেয়ে দুইটি দ্রুত একটি ফ্যাক্টর। এবং যেহেতু বিল্ট-ইন sum নিজে থেকে একটি তালিকা ( লিঙ্কযুক্ত উত্তর অনুসারে ) তুলনায় প্রায় পাঁচ গুণ দ্রুত হওয়া উচিত, এর অর্থ হল এটি আসলে দশ গুণ দ্রুত! অর্ধেক মূল্যের জন্য কাউন্টারে কেবল একটি যোগ করা থেকে রক্ষা করা হয়, কারণ অন্য অর্ধেকটি বাতিল করা হয়, এই পার্থক্যটি ব্যাখ্যা করতে যথেষ্ট?

একটি ফিল্টার হিসাবে if তাই ব্যবহার করে:

def count_even_digits_spyr03_sum2(n):
    return sum(1 for c in str(n) if c in "02468")

তালিকা বোঝার হিসাবে একই স্তরের শুধুমাত্র সময় উন্নতি করে।

বড় সংখ্যাগুলিতে সময় বাড়ানো এবং লুপ টাইমিংয়ের স্বাভাবিকীকরণের সময়, তারা অসীমভাবে বড় সংখ্যা (> 10k সংখ্যার) জন্য রূপান্তরিত হয়, সম্ভবত সময় str(n) কারণে:


আপনার সমস্ত ফাংশনগুলিতে str(n) (এক কল) এবং c in "02468" (এন-তে প্রতিটি c এর জন্য str(n) এর সমান সংখ্যক কল রয়েছে। তখন থেকে আমি সহজ করতে চাই:

import timeit

num = ''.join(str(i % 10) for i in range(1, 10000001))

def count_simple_sum():
    return sum(1 for c in num)

def count_simple_for():
    count = 0
    for c in num:
        count += 1
    return count


print('For Loop Sum:', timeit.timeit(count_simple_for, number=10))
print('Built-in Sum:', timeit.timeit(count_simple_sum, number=10))

sum এখনও ধীর:

For Loop Sum: 2.8987821330083534
Built-in Sum: 3.245505138998851

এই দুটি ফাংশনগুলির মধ্যে কী পার্থক্য হল count_simple_for আপনি for c in num জন্য লুপের for c in num শুধুমাত্র count_simple_for নিক্ষেপ for c in num , কিন্তু count_simple_sum আপনার generator অবজেক্ট তৈরি করছেন ( @ মার্কাস মেসকেন থেকে dis.dis এর উত্তর দিয়ে ):

  3 LOAD_CONST               1 (<code object <genexpr> at 0x10dcc8c90, file "test.py", line 14>)
  6 LOAD_CONST               2 ('count2.<locals>.<genexpr>')

উৎপাদিত উপাদানের যোগফলের জন্য এই জেনারেটর বস্তুটির উপর সমষ্টিটি পুনরাবৃত্তি করা হচ্ছে এবং এই জেনারেটরটি প্রতিটি উপাদানতে 1 উৎপাদনের জন্য সংখ্যায় উপাদানগুলির উপর পুনরাবৃত্তি করছে। আরও একটি পুনরাবৃত্তি পদক্ষেপটি ব্যয়বহুল কারণ এটি generator.__next__() কল করতে হবে generator.__next__() প্রতিটি generator.__next__()generator.__next__() এবং এই কলগুলি try: ... except StopIteration: ব্লক যা কিছু ওভারহেড যোগ করে।


প্রকৃতপক্ষে পর্যবেক্ষিত কর্মক্ষমতা পার্থক্য অবদান যে কয়েক পার্থক্য আছে। আমি এই পার্থক্যগুলির একটি উচ্চ স্তরের ওভারভিউ দিতে চাই কিন্তু নিম্ন-স্তরের বিশদ বা সম্ভাব্য উন্নতিগুলিতে খুব বেশি যেতে না চেষ্টা করি। simple_benchmark আমি আমার নিজস্ব প্যাকেজ simple_benchmark ব্যবহার করুন।

Loops জন্য জেনারেটর বনাম

জেনারেটর এবং জেনারেটর এক্সপ্রেশন সিনট্যাক্টিক চিনি যা ইটারারেটর ক্লাসগুলি লেখার পরিবর্তে ব্যবহার করা যেতে পারে।

যখন আপনি একটি জেনারেটর লিখুন:

def count_even(num):
    s = str(num)
    for c in s:
        yield c in '02468'

অথবা একটি জেনারেটর এক্সপ্রেশন:

(c in '02468' for c in str(num))

যেটি একটি দৃশ্যমান (দৃশ্যের পিছনে) একটি রাষ্ট্র যন্ত্রের মধ্যে রূপান্তরিত হবে যা একটি ইটারেটার ক্লাসের মাধ্যমে অ্যাক্সেসযোগ্য। শেষ পর্যন্ত এটি প্রায় সমান হবে (যদিও একটি জেনারেটরের চারপাশে তৈরি প্রকৃত কোড দ্রুত হবে):

class Count:
    def __init__(self, num):
        self.str_num = iter(str(num))

    def __iter__(self):
        return self

    def __next__(self):
        c = next(self.str_num)
        return c in '02468'

সুতরাং একটি জেনারেটর সবসময় indirection একটি অতিরিক্ত স্তর থাকবে। এর অর্থ জেনারেটর (অথবা জেনারেটর এক্সপ্রেশন বা ইটারারেটর) এর মানে হল যে আপনি জেনারেটর দ্বারা __next__ কে __next__ এ কল করুন, যা নিজেই __next__ কে আপনি যে বস্তুটি __next__ করতে চান __next__ উপর কল করে। তবে এটিতে কিছু অতিরিক্ত ওভারহেড রয়েছে কারণ আপনাকে আসলেই একটি অতিরিক্ত "ইটারারর ইনস্ট্যান্স" তৈরি করতে হবে। আপনি প্রতিটি পুনরাবৃত্তি উল্লেখযোগ্য কিছু যদি সাধারণত এই overheads নগণ্য।

একটি ম্যানুয়াল লুপের তুলনায় একটি জেনারেটর কতটা ওভারহেড প্রয়োগ করে তা কেবল একটি উদাহরণ প্রদান করতে:

import matplotlib.pyplot as plt
from simple_benchmark import BenchmarkBuilder
%matplotlib notebook

bench = BenchmarkBuilder()

@bench.add_function()
def iteration(it):
    for i in it:
        pass

@bench.add_function()
def generator(it):
    it = (item for item in it)
    for i in it:
        pass

@bench.add_arguments()
def argument_provider():
    for i in range(2, 15):
        size = 2**i
        yield size, [1 for _ in range(size)]

plt.figure()
result = bench.run()
result.plot()

জেনারেটর বনাম তালিকা বোঝার

জেনারেটরের সুবিধা আছে যে তারা একটি তালিকা তৈরি করে না, তারা এক-একের মানগুলি "উত্পাদন করে"। সুতরাং জেনারেটরের "ইটারেটার ক্লাস" এর ওভারহেড থাকলে এটি একটি মধ্যবর্তী তালিকা তৈরির জন্য মেমরি সংরক্ষণ করতে পারে। এটা গতি (তালিকা বোঝার) এবং মেমরি (জেনারেটর) মধ্যে একটি বাণিজ্য বন্ধ। এই কাছাকাছি বিভিন্ন পোস্টে আলোচনা করা হয়েছে তাই আমি এখানে আরো বিস্তারিত মধ্যে যেতে চান না।

import matplotlib.pyplot as plt
from simple_benchmark import BenchmarkBuilder
%matplotlib notebook

bench = BenchmarkBuilder()

@bench.add_function()
def generator_expression(it):
    it = (item for item in it)
    for i in it:
        pass

@bench.add_function()
def list_comprehension(it):
    it = [item for item in it]
    for i in it:
        pass

@bench.add_arguments('size')
def argument_provider():
    for i in range(2, 15):
        size = 2**i
        yield size, list(range(size))

plt.figure()
result = bench.run()
result.plot()

sum ম্যানুয়াল পুনরাবৃত্তি চেয়ে দ্রুত হওয়া উচিত

হ্যাঁ, sum লুপের জন্য একটি স্পষ্টের চেয়ে প্রকৃতপক্ষে দ্রুত। আপনি পূর্ণসংখ্যা উপর পুনরাবৃত্তি বিশেষ করে যদি।

import matplotlib.pyplot as plt
from simple_benchmark import BenchmarkBuilder
%matplotlib notebook

bench = BenchmarkBuilder()

@bench.add_function()
def my_sum(it):
    sum_ = 0
    for i in it:
        sum_ += i
    return sum_

bench.add_function()(sum)

@bench.add_arguments()
def argument_provider():
    for i in range(2, 15):
        size = 2**i
        yield size, [1 for _ in range(size)]

plt.figure()
result = bench.run()
result.plot()

স্ট্রিং পদ্ধতি বনাম পাইথন লুপ কোন ধরনের

লুপ (স্পষ্ট বা অন্তর্নিহিত) এর তুলনায় স্ট্রিং পদ্ধতির মতো স্ট্রিং পদ্ধতিগুলি ব্যবহার করার সময় পারফরম্যান্সের পার্থক্য বুঝতে str.count স্ট্রিংগুলিকে আসলে একটি (অভ্যন্তরীণ) অ্যারের মান হিসাবে সংরক্ষণ করা হয়। এর অর্থ হল লুপ আসলে __next__ পদ্ধতিগুলিকে কল করে না, এটি সরাসরি অ্যারের উপর একটি লুপ ব্যবহার করতে পারে, এটি উল্লেখযোগ্যভাবে দ্রুততর হবে। তবে এটি একটি পদ্ধতি সন্ধান এবং স্ট্রিংয়ের একটি পদ্ধতি কল প্রয়োগ করে, তাই এটি খুব ছোট সংখ্যার জন্য ধীর।

একটি স্ট্রিং বনাম পুনরাবৃত্তি করতে কতক্ষণ সময় লাগে তার জন্য একটি ছোট তুলনা প্রদান করতে। অভ্যন্তরীণ অ্যারেতে পাইথনটি কতক্ষণ সময় নেয় তা কতক্ষণ সময় নেয়?

import matplotlib.pyplot as plt
from simple_benchmark import BenchmarkBuilder
%matplotlib notebook

bench = BenchmarkBuilder()

@bench.add_function()
def string_iteration(s):
    # there is no "a" in the string, so this iterates over the whole string
    return 'a' in s  

@bench.add_function()
def python_iteration(s):
    for c in s:
        pass

@bench.add_arguments('string length')
def argument_provider():
    for i in range(2, 20):
        size = 2**i
        yield size, '1'*size

plt.figure()
result = bench.run()
result.plot()

এই বেঞ্চমার্কে এটি পাইপনটি লুপের সাথে স্ট্রিংয়ের উপর পুনরাবৃত্তি করার চেয়ে স্ট্রিংয়ের উপর পুনরাবৃত্তি করার জন্য 200 গুণ দ্রুত।

কেন তাদের সব বড় সংখ্যা জন্য converge?

এটি আসলে কারণ রূপান্তর রূপান্তর সংখ্যা সংখ্যা প্রভাবশালী হবে। সুতরাং সত্যিই বিশাল সংখ্যা জন্য আপনি অপরিহার্যভাবে মাত্র একটি পরিমাপ যে সংখ্যা রূপান্তর করতে লাগে কত পরিমাপ।

যদি আপনি কোন সংখ্যা গ্রহণ করে এমন সংস্করণগুলির তুলনা করেন এবং রূপান্তরিত নম্বরটি গ্রহণ করে এমন একটি স্ট্রিং রূপে রূপান্তর করেন তবে আমি এটি দেখতে পারব (আমি এখানে বর্ণনার জন্য অন্য উত্তর থেকে ফাংশনগুলি ব্যবহার করব)। বাম সংখ্যা-বেঞ্চমার্ক এবং ডানদিকে বেঞ্চমার্ক যা স্ট্রিংগুলি নেয় - এছাড়াও দুটি অক্ষরের জন্য y অক্ষটি একই।

স্ট্রিংগুলি যে ফাংশনগুলির জন্য ব্যাঞ্চমার্কগুলি দেখতে পারে তা বড় সংখ্যাগুলির জন্য একটি সংখ্যা গ্রহণকারীর চেয়ে উল্লেখযোগ্যভাবে দ্রুততর এবং তাদের ভিতরে একটি স্ট্রিং রূপে রূপান্তর করতে পারে। এটি নির্দেশ করে যে স্ট্রিং-রূপান্তরটি বড় সংখ্যাগুলির জন্য "বোতল"। সুবিধার জন্য আমি বাম চক্রান্তের স্ট্রিং রূপান্তর শুধুমাত্র একটি বেঞ্চমার্ক অন্তর্ভুক্ত (যা বড় সংখ্যাগুলির জন্য উল্লেখযোগ্য / প্রভাবশালী)।

%matplotlib notebook

from simple_benchmark import BenchmarkBuilder
import matplotlib.pyplot as plt
import random

bench1 = BenchmarkBuilder()

@bench1.add_function()
def f1(x):
    return sum(c in '02468' for c in str(x))

@bench1.add_function()
def f2(x):
    return sum([c in '02468' for c in str(x)])

@bench1.add_function()
def f3(x):
    return sum([True for c in str(x) if c in '02468'])    

@bench1.add_function()
def f4(x):
    return sum([1 for c in str(x) if c in '02468'])

@bench1.add_function()
def explicit_loop(x):
    count = 0
    for c in str(x):
        if c in '02468':
            count += 1
    return count

@bench1.add_function()
def f5(x):
    s = str(x)
    return sum(s.count(c) for c in '02468')

bench1.add_function()(str)

@bench1.add_arguments(name='number length')
def arg_provider():
    for i in range(2, 15):
        size = 2 ** i
        yield (2**i, int(''.join(str(random.randint(0, 9)) for _ in range(size))))


bench2 = BenchmarkBuilder()

@bench2.add_function()
def f1(x):
    return sum(c in '02468' for c in x)

@bench2.add_function()
def f2(x):
    return sum([c in '02468' for c in x])

@bench2.add_function()
def f3(x):
    return sum([True for c in x if c in '02468'])    

@bench2.add_function()
def f4(x):
    return sum([1 for c in x if c in '02468'])

@bench2.add_function()
def explicit_loop(x):
    count = 0
    for c in x:
        if c in '02468':
            count += 1
    return count

@bench2.add_function()
def f5(x):
    return sum(x.count(c) for c in '02468')

@bench2.add_arguments(name='number length')
def arg_provider():
    for i in range(2, 15):
        size = 2 ** i
        yield (2**i, ''.join(str(random.randint(0, 9)) for _ in range(size)))

f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
b1 = bench1.run()
b2 = bench2.run()
b1.plot(ax=ax1)
b2.plot(ax=ax2)
ax1.set_title('Number')
ax2.set_title('String')

sum বেশ দ্রুত, কিন্তু sum হ্রাস কারণ নয়। তিনটি প্রাথমিক কারণ হ্রাস অবদান:

  • একটি জেনারেটর এক্সপ্রেশন ব্যবহার ক্রমাগত বিরতি এবং জেনারেটর পুনরায় শুরু করার জন্য ওভারহেড কারণ।
  • আপনার জেনারেটর সংস্করণটি এমনকি সংখ্যার সাথে থাকলেও শর্তহীনভাবে যোগ করে। অঙ্ক অদ্ভুত যখন এই আরো ব্যয়বহুল।
  • Ints পরিবর্তে বুলিয়ান যোগ করা তার পূর্ণসংখ্যা দ্রুত পথ ব্যবহার থেকে যোগ বাধা দেয়।

জেনারেটর তালিকা বোঝার উপর দুটি প্রাথমিক সুবিধার প্রস্তাব দেয়: তারা অনেক কম মেমরি নেয় এবং সমস্ত উপাদানের প্রয়োজন হয় না তাড়াতাড়ি তারা বাতিল করতে পারে। তারা সমস্ত উপাদান প্রয়োজন যেখানে ক্ষেত্রে একটি সুযোগ সুবিধা প্রদান করার জন্য ডিজাইন করা হয় না। প্রতি উপাদান একবার জেনারেটর স্থগিত এবং পুনরায় শুরু করা বেশ ব্যয়বহুল।

যদি আমরা একটি তালিকা বোঝার সাথে genexp প্রতিস্থাপন করি:

In [66]: def f1(x):
   ....:     return sum(c in '02468' for c in str(x))
   ....: 
In [67]: def f2(x):
   ....:     return sum([c in '02468' for c in str(x)])
   ....: 
In [68]: x = int('1234567890'*50)
In [69]: %timeit f1(x)
10000 loops, best of 5: 52.2 µs per loop
In [70]: %timeit f2(x)
10000 loops, best of 5: 40.5 µs per loop

আমরা একটি তালিকায় মেমরি একটি গুচ্ছ নষ্ট করার খরচ এ, একটি অবিলম্বে দ্রুতগতি দেখুন।

আপনি যদি আপনার জেনেক্স সংস্করণটি দেখেন:

def count_even_digits_spyr03_sum(n):
    return sum(c in "02468" for c in str(n))

আপনি if এটি কোন দেখতে পাবেন। এটা শুধু sum মধ্যে বুলিয়ান ছুড়ে ফেলে। কনস্ট্রাস্ট, আপনার লুপ:

def count_even_digits_spyr03_for(n):
    count = 0
    for c in str(n):
        if c in "02468":
            count += 1
    return count

সংখ্যা এমনকি যদি কিছু যোগ করে।

যদি আমরা পূর্বে সংজ্ঞায়িত f2 পরিবর্তন করতে পারি তবে এটি একটি অন্তর্ভুক্ত করা হয়, আমরা অন্য গতিবেগ দেখি:

In [71]: def f3(x):
   ....:     return sum([True for c in str(x) if c in '02468'])
   ....: 
In [72]: %timeit f3(x)
10000 loops, best of 5: 34.9 µs per loop

f1 , আপনার আসল কোডের মতো, 52.2 μs এবং f2 , শুধুমাত্র তালিকা বোঝার পরিবর্তনের সাথে, 40.5 μs নেয়।

এটি সম্ভবত f3 পরিবর্তে 1 ব্যবহার করে বেশ অদ্ভুত লাগছিল। কারণ এটি 1 পরিবর্তন একটি চূড়ান্ত গতিশীল সক্রিয়। sum পূর্ণসংখ্যার জন্য একটি দ্রুত পথ আছে, কিন্তু দ্রুত পথ শুধুমাত্র বস্তুর জন্য সক্রিয় করে, যার ধরন ঠিক intbool গণনা করা হয় না। এটি এমন লাইন যা আইটেমগুলিকে টাইপ int এর চেক করে:

if (PyLong_CheckExact(item)) {

একবার আমরা চূড়ান্ত পরিবর্তন করতে, 1 থেকে True পরিবর্তন:

In [73]: def f4(x):
   ....:     return sum([1 for c in str(x) if c in '02468'])
   ....: 
In [74]: %timeit f4(x)
10000 loops, best of 5: 33.3 µs per loop

আমরা এক শেষ ছোট গতির দেখতে।

তাই সব পরে, আমরা স্পষ্ট লুপ বীট না?

In [75]: def explicit_loop(x):
   ....:     count = 0
   ....:     for c in str(x):
   ....:         if c in '02468':
   ....:             count += 1
   ....:     return count
   ....: 
In [76]: %timeit explicit_loop(x)
10000 loops, best of 5: 32.7 µs per loop

নাঃ। আমরা এমনকি প্রায় ভাঙ্গা করেছি, কিন্তু আমরা এটা মারছি না। বড় অবশিষ্ট সমস্যা তালিকা। এটি নির্মাণ ব্যয়বহুল, এবং sum উপাদানগুলি পুনরুদ্ধারের জন্য এটির তালিকাটি দিয়ে যেতে হবে, যার নিজস্ব খরচ রয়েছে (যদিও আমি মনে করি যে এটি অংশটি বেশ সস্তা)। দুর্ভাগ্যবশত, যতক্ষণ আমরা পরীক্ষা-সংখ্যা-এবং-কল- sum পদ্ধতির মাধ্যমে যাচ্ছি, ততক্ষণ আমাদের তালিকা থেকে মুক্ত হওয়ার কোনও ভাল উপায় নেই। স্পষ্ট লুপ জয়।

আমরা কি আর যাইতে পারি? আচ্ছা, আমরা এতদূর স্পষ্ট লুপের কাছাকাছি sum আনতে চেষ্টা করেছি, কিন্তু যদি আমরা এই বোকা তালিকার সাথে আটকে থাকি, তবে আমরা স্পষ্ট লুপ থেকে বিচ্ছিন্ন হতে পারি এবং sum পরিবর্তে len কল করতে পারি:

def f5(x):
    return len([1 for c in str(x) if c in '02468'])

পৃথকভাবে পরীক্ষার সংখ্যা একমাত্র উপায় নয় যা আমরা লুপ বীট করার চেষ্টা করতে পারি। স্পষ্ট লুপ থেকে এমনকি আরও str.count , আমরা str.count চেষ্টা করতে পারেন। str.count সরাসরি একটি স্ট্রিং এর বাফারের উপর str.count পুনরাবৃত্ত বস্তু এবং indirection অনেক এড়ানো। আমাদের 5 বার কল করতে হবে, যা স্ট্রিংয়ে 5 পাস করে, তবে এটি এখনও বন্ধ করে দেয়:

def f6(x):
    s = str(x)
    return sum(s.count(c) for c in '02468')

দুর্ভাগ্যবশত, এই সময়টি যখন আমি টাইমিংয়ের জন্য যে সাইটটি ব্যবহার করছিলাম সেটি আমাকে অনেকগুলি সংস্থান ব্যবহার করার জন্য "টারপিট" আটকে রেখেছিল, তাই আমাকে সাইটগুলি পরিবর্তন করতে হয়েছিল। নিচের সময়গুলি সরাসরি সময়ের সাথে তুলনাযোগ্য নয়:

>>> import timeit
>>> def f(x):
...     return sum([1 for c in str(x) if c in '02468'])
... 
>>> def g(x):
...     return len([1 for c in str(x) if c in '02468'])
... 
>>> def h(x):
...     s = str(x)
...     return sum(s.count(c) for c in '02468')
... 
>>> x = int('1234567890'*50)
>>> timeit.timeit(lambda: f(x), number=10000)
0.331528635986615
>>> timeit.timeit(lambda: g(x), number=10000)
0.30292080697836354
>>> timeit.timeit(lambda: h(x), number=10000)
0.15950968803372234
>>> def explicit_loop(x):
...     count = 0
...     for c in str(x):
...         if c in '02468':
...             count += 1
...     return count
... 
>>> timeit.timeit(lambda: explicit_loop(x), number=10000)
0.3305045129964128




sum