قم ببناء مكرر بايثون الأساسي
سؤال
كيف يمكن إنشاء وظيفة تكرارية (أو كائن مكرر) في بيثون؟
المحلول
تتوافق كائنات التكرار في بايثون مع بروتوكول التكرار، مما يعني أنها توفر طريقتين: __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.
مقال ديفيد ميرتز، التكرارات والمولدات البسيطة, ، هي مقدمة جيدة جدًا.
نصائح أخرى
هناك أربع طرق لبناء دالة تكرارية:
- إنشاء مولد (يستخدم الكلمة الرئيسية العائد)
- استخدم تعبير المولد (com.genexp)
- إنشاء مكرر (يحدد
__iter__
و__next__
(أوnext
في بيثون 2.x)) - إنشاء فئة يمكن لبيثون تكرارها بمفردها (يحدد
__getitem__
)
أمثلة:
# 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)