سؤال

كيف يمكن إنشاء وظيفة تكرارية (أو كائن مكرر) في بيثون؟

هل كانت مفيدة؟

المحلول

تتوافق كائنات التكرار في بايثون مع بروتوكول التكرار، مما يعني أنها توفر طريقتين: __iter__() و next().ال __iter__ تقوم بإرجاع كائن التكرار ويتم استدعاؤه ضمنيًا في بداية الحلقات.ال next() تُرجع الطريقة القيمة التالية ويتم استدعاؤها ضمنيًا عند كل زيادة في الحلقة. next() يثير استثناء StopIteration عندما لا تكون هناك قيمة أخرى لإرجاعها، والتي يتم التقاطها ضمنيًا عن طريق إنشاءات التكرار لإيقاف التكرار.

فيما يلي مثال بسيط للعداد:

class Counter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def next(self): # Python 3: def __next__(self)
        if self.current > self.high:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1


for c in Counter(3, 8):
    print c

سيؤدي هذا إلى طباعة:

3
4
5
6
7
8

هذا أسهل في الكتابة باستخدام المولد، كما هو موضح في الإجابة السابقة:

def counter(low, high):
    current = low
    while current <= high:
        yield current
        current += 1

for c in counter(3, 8):
    print c

سيكون الإخراج المطبوع هو نفسه.تحت الغطاء، يدعم كائن المولد بروتوكول التكرار ويفعل شيئًا مشابهًا تقريبًا للفئة Counter.

مقال ديفيد ميرتز، التكرارات والمولدات البسيطة, ، هي مقدمة جيدة جدًا.

نصائح أخرى

هناك أربع طرق لبناء دالة تكرارية:

أمثلة:

# generator
def uc_gen(text):
    for char in text:
        yield char.upper()

# generator expression
def uc_genexp(text):
    return (char.upper() for char in text)

# iterator protocol
class uc_iter():
    def __init__(self, text):
        self.text = text
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.text[self.index].upper()
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

# getitem method
class uc_getitem():
    def __init__(self, text):
        self.text = text
    def __getitem__(self, index):
        result = self.text[index].upper()
        return result

لرؤية جميع الطرق الأربع في العمل:

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
    for ch in iterator('abcde'):
        print ch,
    print

مما يؤدي إلى:

A B C D E
A B C D E
A B C D E
A B C D E

ملحوظة:

نوعين المولدات (uc_gen و uc_genexp) لا يمكن reversed();المكرر العادي (uc_iter) سوف تحتاج إلى __reversed__ الطريقة السحرية (التي يجب أن تُرجع مُكرِّرًا جديدًا يعود إلى الوراء)؛و getitem قابل للتكرار (uc_getitem) يجب أن يكون __len__ الطريقة السحرية:

    # for uc_iter
    def __reversed__(self):
        return reversed(self.text)

    # for uc_getitem
    def __len__(self)
        return len(self.text)

للإجابة على السؤال الثانوي للعقيد بانيك حول المكرر الذي تم تقييمه بتكاسل لا نهائي، إليك هذه الأمثلة، باستخدام كل من الطرق الأربع المذكورة أعلاه:

# generator
def even_gen():
    result = 0
    while True:
        yield result
        result += 2


# generator expression
def even_genexp():
    return (num for num in even_gen())  # or even_iter or even_getitem
                                        # not much value under these circumstances

# iterator protocol
class even_iter():
    def __init__(self):
        self.value = 0
    def __iter__(self):
        return self
    def __next__(self):
        next_value = self.value
        self.value += 2
        return next_value

# getitem method
class even_getitem():
    def __getitem__(self, index):
        return index * 2

import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
    limit = random.randint(15, 30)
    count = 0
    for even in iterator():
        print even,
        count += 1
        if count >= limit:
            break
    print

مما يؤدي إلى (على الأقل بالنسبة لعينتي):

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32

أولا وقبل كل شيء وحدة itertools يعد مفيدًا بشكل لا يصدق لجميع أنواع الحالات التي قد يكون فيها المكرر مفيدًا، ولكن هنا كل ما تحتاجه لإنشاء مكرر في بايثون:

أَثْمَر

أليس هذا رائعا؟يمكن استخدام العائد ليحل محل طبيعي يعود في وظيفة.تقوم بإرجاع الكائن بنفس الطريقة، ولكن بدلاً من تدمير الحالة والخروج، فإنها تحفظ الحالة عندما تريد تنفيذ التكرار التالي.فيما يلي مثال على ذلك أثناء العمل تم سحبه مباشرة من قائمة وظائف itertools:

def count(n=0):
    while True:
        yield n
        n += 1

كما هو مذكور في وصف الوظائف (إنه عدد() وظيفة من وحدة itertools...) ، فإنها تنتج مكررًا يُرجع أعدادًا صحيحة متتالية تبدأ بـ n.

تعبيرات المولد هي علبة أخرى كاملة من الديدان (ديدان رائعة!).يمكن استخدامها بدلاً من أ قائمة الفهم لحفظ الذاكرة (تنشئ عمليات فهم القائمة قائمة في الذاكرة يتم تدميرها بعد الاستخدام إذا لم يتم تعيينها لمتغير، ولكن يمكن لتعبيرات المولد إنشاء كائن مولد...وهي طريقة رائعة لقول Iterator).فيما يلي مثال لتعريف تعبير المولد:

gen = (n for n in xrange(0,11))

هذا مشابه جدًا لتعريف المكرر أعلاه باستثناء النطاق الكامل المحدد مسبقًا ليكون بين 0 و10.

لقد وجدت xrange() (فوجئت أنني لم أره من قبل...) وأضفته إلى المثال أعلاه. xrange() هي نسخة قابلة للتكرار من يتراوح() والتي لها ميزة عدم إنشاء القائمة مسبقًا.سيكون من المفيد جدًا أن يكون لديك مجموعة ضخمة من البيانات التي يمكنك تكرارها ولم يكن لديك سوى قدر كبير من الذاكرة للقيام بذلك.

أرى بعضكم يفعل return self في __iter__.أردت فقط أن أشير إلى ذلك __iter__ في حد ذاته يمكن أن يكون مولدًا (وبالتالي إزالة الحاجة إلى __next__ ورفع StopIteration الاستثناءات)

class range:
  def __init__(self,a,b):
    self.a = a
    self.b = b
  def __iter__(self):
    i = self.a
    while i < self.b:
      yield i
      i+=1

بالطبع هنا يمكن أيضًا إنشاء مولد بشكل مباشر، لكن يمكن أن يكون مفيدًا للفئات الأكثر تعقيدًا.

هذا السؤال يتعلق بالكائنات القابلة للتكرار، وليس بالتكرارات.في بايثون، التسلسلات قابلة للتكرار أيضًا، لذا فإن إحدى الطرق لإنشاء فئة قابلة للتكرار هي جعلها تتصرف مثل التسلسل، أي.اعطيها __getitem__ و __len__ طُرق.لقد اختبرت هذا على بيثون 2 و 3.

class CustomRange:

    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __getitem__(self, item):
        if item >= len(self):
            raise IndexError("CustomRange index out of range")
        return self.low + item

    def __len__(self):
        return self.high - self.low


cr = CustomRange(0, 10)
for i in cr:
    print(i)

هذه وظيفة قابلة للتكرار بدون yield.أنها الاستفادة من iter وظيفة وإغلاق يحافظ على حالتها في حالة قابلة للتغيير (list) في النطاق المرفق لـ python 2.

def count(low, high):
    counter = [0]
    def tmp():
        val = low + counter[0]
        if val < high:
            counter[0] += 1
            return val
        return None
    return iter(tmp, None)

بالنسبة إلى Python 3، يتم الاحتفاظ بحالة الإغلاق بشكل غير قابل للتغيير في النطاق المرفق و nonlocal يستخدم في النطاق المحلي لتحديث متغير الحالة.

def count(low, high):
    counter = 0
    def tmp():
        nonlocal counter
        val = low + counter
        if val < high:
            counter += 1
            return val
        return None
    return iter(tmp, None)  

امتحان؛

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9

جميع الإجابات الموجودة في هذه الصفحة رائعة حقًا بالنسبة لكائن معقد.ولكن بالنسبة لتلك التي تحتوي على أنواع مدمجة قابلة للتكرار كسمات، مثل str, list, set أو dict, أو أي تنفيذ collections.Iterable, ، يمكنك حذف أشياء معينة في صفك.

class Test(object):
    def __init__(self, string):
        self.string = string

    def __iter__(self):
        # since your string is already iterable
        return (ch for ch in string)

يمكن استخدامه مثل:

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e

إذا كنت تبحث عن شيء قصير وبسيط، فربما يكفيك:

class A(object):
    def __init__(self, l):
        self.data = l

    def __iter__(self):
        return iter(self.data)

مثال على الاستخدام:

In [3]: a = A([2,3,4])

In [4]: [i for i in a]
Out[4]: [2, 3, 4]

مستوحاة من إجابة مات جريجوري، يوجد هنا مكرر أكثر تعقيدًا سيعيد a,b,...,z,aa,ab,...,zz,aaa,aab,...,zzy,zzz

    class AlphaCounter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 3: def __next__(self)
        alpha = ' abcdefghijklmnopqrstuvwxyz'
        n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
        n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
        if n_current > n_high:
            raise StopIteration
        else:
            increment = True
            ret = ''
            for x in self.current[::-1]:
                if 'z' == x:
                    if increment:
                        ret += 'a'
                    else:
                        ret += 'z'
                else:
                    if increment:
                        ret += alpha[alpha.find(x)+1]
                        increment = False
                    else:
                        ret += x
            if increment:
                ret += 'a'
            tmp = self.current
            self.current = ret[::-1]
            return tmp

for c in AlphaCounter('a', 'zzz'):
    print(c)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top