ينسى روبي المتغيرات المحلية خلال فترة أثناء الحلقة؟
سؤال
أقوم بمعالجة ملف نصي رقم قياسي: لذلك أنا أبحث عن سلسلة البداية التي تشكل بداية سجل: لا يوجد علامة نهاية السجل، لذلك يمكنني استخدام بداية السجل التالي لتخليص السجل الأخير.
لذلك قمت ببناء برنامج بسيط للقيام بذلك، لكنني أرى شيئا ما يقاومني: يبدو أن الروبي ينسى المتغيرات المحلية موجودة - أو هل وجدت خطأ برمجة؟ [على الرغم من أنني لا أعتقد أن لدي: إذا قمت بتحديد الرسالة "المتغير" قبل حلقةتي، فأنا لا أرى الخطأ].
إليك مثال مبسط مع مثال على بيانات الإدخال ورسالة الخطأ في التعليقات:
flag=false
# message=nil # this is will prevent the issue.
while line=gets do
if line =~/hello/ then
if flag==true then
puts "#{message}"
end
message=StringIO.new(line);
puts message
flag=true
else
message << line
end
end
# Input File example:
# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc
#
# Error when running: [nb, first iteration is fine]
# <StringIO:0x2e845ac>
# hello
# test.rb:5: undefined local variable or method `message' for main:Object (NameError)
#
المحلول
أعتقد أن هذا لأن الرسالة محددة داخل الحلقة. في نهاية حلقة التكرار "رسالة" يخرج من النطاق. تحديد "رسالة" خارج الحلقة يمنع المتغير من الخروج من النطاق في نهاية كل تكرار حلقة. لذلك أعتقد أن لديك الإجابة الصحيحة.
يمكنك إخراج قيمة الرسالة في بداية كل تكرار حلقة لاختبار ما إذا كان اقتراحي صحيحا.
نصائح أخرى
من لغة البرمجة Ruby:
كتل ونطاق متغير
كتل تحديد نطاق متغير جديد: المتغيرات التي تم إنشاؤها داخل كتلة موجودة فقط داخل هذه الكتلة وغير محددة خارج الكتلة. كن حذرا، ولكن المتغيرات المحلية في الطريقة متاحة لأي كتل داخل هذه الطريقة. لذلك إذا قامت كتلة بتعيين قيمة متغير محددة بالفعل خارج الكتلة، فلا يقوم هذا بإنشاء متغير محلي جديد، ولكن بدلا من ذلك تعيين قيمة جديدة إلى المتغير الموجود بالفعل. في بعض الأحيان، هذا هو بالضبط السلوك الذي نريد:
total = 0
data.each {|x| total += x } # Sum the elements of the data array
puts total # Print out that sum
ولكن في بعض الأحيان، لا نريد تغيير المتغيرات في النطاق المرفق، لكننا نفعل ذلك عن غير قصد. هذه المشكلة هي مصدر قلق خاص لمعلمات Block في Ruby 1.8. في Ruby 1.8، إذا كانت معلمة الكتلة تشترك في اسم المتغير الموجود، فإن الدعوات من الكتلة ببساطة تعيين قيمة إلى هذا المتغير الموجود بدلا من إنشاء متغير محلي جديد. التعليمة البرمجية التالية، على سبيل المثال، هي إشكالية لأنه يستخدم نفس المعرف الأول كمعلمة Block كتلتين متداخيتين:
1.upto(10) do |i| # 10 rows
1.upto(10) do |i| # Each has 10 columns
print "#{i} " # Print column number
end
print " ==> Row #{i}\n" # Try to print row number, but get column number
end
Ruby 1.9 مختلف: معلمات Block هي دائما محلية في كتلةها، ودعاة الكتلة لا تعني القيم أبدا للمتغيرات الموجودة. إذا تم استدعاء Ruby 1.9 بعلم -w، فسوف يحذرك إذا كانت هناك معلمة كتلة لها نفس الاسم كمتغير موجود. يساعدك ذلك على تجنب كتابة التعليمات البرمجية التي تعمل بشكل مختلف في 1.8 و 1.9.
Ruby 1.9 يختلف بطريقة مهمة أخرى أيضا. تم تمديد بناء جملة الكتلة للسماح لك بإعلان المتغيرات المحلية المحلية المضمونة لتكون محلية، حتى لو كان متغير بنفس الاسم موجود بالفعل في النطاق المرفق. للقيام بذلك، اتبع قائمة معلمات Block مع فاصلة منقوطة وقائمة مفصولة بفواصل من المتغيرات المحلية كتلة. هنا مثال:
x = y = 0 # local variables
1.upto(4) do |x;y| # x and y are local to block
# x and y "shadow" the outer variables
y = x + 1 # Use y as a scratch variable
puts y*y # Prints 4, 9, 16, 25
end
[x,y] # => [0,0]: block does not alter these
في هذا الرمز، X عبارة عن معلمة كتلة: إنه يحصل على قيمة عند استدعاء الكتلة بالعائد. Y هو متغير كتلة محلية. لا تتلقى أي قيمة من استدعاء العائد، ولكن لديها قيمة لا شيء حتى تقوم الكتلة في الواقع بتعيين بعض القيمة الأخرى إليها. تتمثل نقطة إعلان هذه المتغيرات المحلية في الكتلة في ضمان أنك لن تطمع عن غير قصد بقيمة بعض المتغير الموجود. (قد يحدث هذا إذا تم قطع كتلة وصق من طريقة واحدة إلى أخرى، على سبيل المثال.) إذا استدعيت روبي 1.9 مع خيار -w
يمكن أن تحتوي الكتل أكثر من معلمة واحدة وأكثر من متغير محلي واحد، بالطبع. هنا كتلة مع اثنين من المعلمات وثلاثة المتغيرات المحلية:
hash.each {|key,value; i,j,k| ... }
على عكس بعض الإجابات الأخرى، while
الحلقات لا تخلق في الواقع نطاق جديد. المشكلة التي تراها هي أكثر دقة.
للمساعدة في إظهار التباين، تم تمرير كتل إلى طريقة مكالمة فعل قم بإنشاء نطاق جديد، بحيث يختفي المتغير المحلي المعين حديثا داخل الكتلة بعد خروج الكتلة:
### block example - provided for contrast only ###
[0].each {|e| blockvar = e }
p blockvar # NameError: undefined local variable or method
ولكن while
حلقات (مثل قضيتك) مختلفة:
arr = [0]
while arr.any?
whilevar = arr.shift
end
p whilevar # prints 0
السبب في الحصول على الخطأ في حالتك هو لأن الخط الذي يستخدم message
:
puts "#{message}"
يظهر قبل أي رمز يعين message
.
إنه نفس السبب يثير هذا الرمز خطأ إذا a
لم يتم تعريفه مسبقا:
# Note the single (not double) equal sign.
# At first glance it looks like this should print '1',
# because the 'a' is assigned before (time-wise) the puts.
puts a if a = 1
لا النحافة، ولكن تحليل الرؤية
ما يسمى "مشكلة" - أي المتغير المحلي الرؤية ضمن نطاق واحد - بسبب روبي محلل. وبعد نظرا لأننا نفكر فقط في نطاق واحد، فإن قواعد النطاق لديهم لا شيئ للقيام بهذه المشكلة. في مرحلة التحليل، يقرر المحللون عند أي مواقع مصدر متغير محلي مرئي، وهذا الرؤية لا ليس التغيير أثناء التنفيذ.
عند تحديد ما إذا تم تعريف المتغير المحلي (أي defined?
إرجاع TRUE) في أي وقت من النقطة في التعليمات البرمجية، يتحقق المحفنة النطاق الحالي لمعرفة ما إذا كان أي رمز قد عينه من قبل، حتى إذا لم يتم تشغيل هذا الرمز أبدا (لا يمكن أن يعرف هذا الرمز (لا يمكن أن يعرف المحللون أي شيء حول ما لديه أو لم يتم تشغيله مرحلة التحليل). "قبل" المعنى: على خط أعلاه، أو على نفس الخط وإلى الجانب الأيسر.
تمرين لتحديد ما إذا تم تعريف المحلي (أي مرئية)
لاحظ أن ما يلي ينطبق فقط على المتغيرات المحلية، وليس الأساليب. (تحديد ما إذا كانت هناك طريقة محددة في نطاق أكثر تعقيدا، لأنها تتضمن البحث في الوحدات النمطية ودروس الأجداد.)
طريقة ملموسة لرؤية السلوك المتغير المحلي هي فتح الملف الخاص بك في محرر نصوص. لنفترض أيضا أنه بالضغط بشكل متكرر على مفتاح السهم الأيسر، يمكنك نقل المؤشر إلى الوراء من خلال الملف بأكمله. لنفترض الآن أنك تتساءل عما إذا كان استخدام معين message
سوف رفع NameError
. وبعد للقيام بذلك، ضع مؤشرك في المكان الذي تستخدمه message
, ، ثم استمر في الضغط على السهم الأيسر حتى تقوم أيضا بما يلي:
- الوصول إلى بداية النطاق الحالي (يجب أن تفهم قواعد نطق الروبي لمعرفة متى يحدث هذا)
- رموز الوصول الذي يعين
message
إذا وصلت إلى مهمة قبل الوصول إلى حدود النطاق، فهذا يعني استخدامك message
لن ترفع NameError
. وبعد إذا كنت لا تصل إلى أي مهمة، فسوف يرفع الاستخدام NameError
.
اعتبارات أخرى
في حالة ظهور مهمة متغيرة تظهر في التعليمات البرمجية ولكنها غير مدفوعة، يتم تهيئة المتغير ل nil
:
# a is not defined before this
if false
# never executed, but makes the binding defined/visible to the else case
a = 1
else
p a # prints nil
end
بينما الحلقة حالة اختبار
إليك حالة اختبار صغير لإظهار غريب السلوك أعلاه عندما يحدث ذلك في حلقة أثناء الوقت. المتغير المصاب هنا هو dest_arr
.
arr = [0,1]
while n = arr.shift
p( n: n, dest_arr_defined: (defined? dest_arr) )
if n == 0
dest_arr = [n]
else
dest_arr << n
p( dest_arr: dest_arr )
end
end
ما النواتج:
{:n=>0, :dest_arr_defined=>nil}
{:n=>1, :dest_arr_defined=>nil}
{:dest_arr=>[0, 1]}
النقاط البارزة:
- التكرار الأول سهل،
dest_arr
يتم تهيئة ل[0]
. - لكننا بحاجة إلى إيلاء اهتمام وثيق في التكرار الثاني (متى
n
يكون1
):- في البداية،
dest_arr
غير محدد! - ولكن عندما يصل الرمز
else
قضية،dest_arr
مرئي مرة أخرى، لأن مترجم الترجمة يرى أنه تم تعريفه مسبقا (2 خطوط). - إشعار أيضا، ذلك
dest_arr
فقط مختفي في بداية الحلقة؛ لا تضيع قيمتها أبدا.
- في البداية،
وهذا ما يفسر أيضا السبب في تعيين محلي الخاص بك قبل while
حلقة إصلاح المشكلة. لا تحتاج المهمة إلى تنفيذها؛ يحتاج فقط إلى الظهور في التعليمات البرمجية المصدر.
مثال لامدا
f1 = ->{ f2 }
f2 = ->{ f1 }
p f2.call()
# Fails because the body of f1 tries to access f2 before an assignment for f2 was seen by the parser.
p f1.call() # undefined local variable or method `f2'.
إصلاح هذا عن طريق وضع f2
المهمة قبل f1
الجسم. تذكر أن المهمة لا تحتاج فعليا إلى تنفيذها!
f2 = nil # Could be replaced by: if false; f2 = nil; end
f1 = ->{ f2 }
f2 = ->{ f1 }
p f2.call()
p f1.call() # ok
طريقة اخفاء Gotcha
الأشياء تصبح شعر حقا إذا كان لديك متغير محلي بنفس الاسم كأسلاك:
def dest_arr
:whoops
end
arr = [0,1]
while n = arr.shift
p( n: n, dest_arr: dest_arr )
if n == 0
dest_arr = [n]
else
dest_arr << n
p( dest_arr: dest_arr )
end
end
المخرجات:
{:n=>0, :dest_arr=>:whoops}
{:n=>1, :dest_arr=>:whoops}
{:dest_arr=>[0, 1]}
سيتم إجراء مهمة متغيرة محلية في نطاق "قناع" / "الظل" طريقة استدعاء نفس الاسم. (لا يزال بإمكانك استدعاء الطريقة باستخدام الأقواس الصريحة أو جهاز استقبال صريح. لذلك يشبه هذا السابق while
اختبار حلقة، باستثناء ذلك بدلا من أن تصبح غير محددة فوق رمز المهمة، dest_arr
طريقة يصبح "غير مأهول" / "غير مشبد" بحيث تكون الطريقة قوسين أكثر قابلية للتطوير. ولكن أي رمز بعد المهمة سترى المتغير المحلي.
بعض أفضل الممارسات يمكننا أن نستمد من كل هذا
- لا تسمية المتغيرات المحلية هي نفس أسماء الأسلوب في نفس النطاق
- لا تضع المهمة الأولية لمتغير محلي في نص أ
while
أوfor
حلقة، أو أي شيء يسبب التنفيذ للقفز في نطاق (استدعاء Lambdas أوContinuation#call
يمكن أن تفعل هذا أيضا). ضع المهمة قبل الحلقة.
لماذا تعتقد أن هذا خطأ؟ يخبرك المترجم الخاص بك أن الرسالة قد تكون غير محددة عندما تنفذ هذه القطعة المعينة من التعليمات البرمجية.
لست متأكدا من السبب في أنك فوجئت: على الخط 5 (على افتراض أن message = nil
الخط ليس هناك)، يمكنك استخدام متغير أن المترجم لم يسمع من قبل. يقول المترجم "ما هو message
ب إنها ليست طريقة أعرفها، إنها ليست متغير أعرفها، إنها ليست كلمة رئيسية ... "ثم تحصل على رسالة خطأ.
إليك مثال أبسط أن نظهر لك ما أقصده:
while line = gets do
if line =~ /./ then
puts message # How could this work?
message = line
end
end
الذي يعطي:
telemachus ~ $ ruby test.rb < huh
test.rb:3:in `<main>': undefined local variable or method `message' for main:Object (NameError)
أيضا، إذا كنت ترغب في إعداد الطريق ل message
, ، أود تهيئة ذلك message = ''
, ، بحيث تكون سلسلة (بدلا من nil
). خلاف ذلك، إذا كان خطك الأول لا تطابق مرحبا، وينتهي بك الأمر إلى إضافة line
ل nil
- والتي سوف تحصل لك هذا الخطأ:
telemachus ~ $ ruby test.rb < huh
test.rb:4:in `<main>': undefined method `<<' for nil:NilClass (NoMethodError)
يمكنك ببساطة القيام بذلك:
message=''
while line=gets do
if line =~/hello/ then
# begin a new record
p message unless message == ''
message = String.new(line)
else
message << line
end
end
# hello this is a record
# this is also part of the same record
# hello this is a new record
# this is still record 2
# hello this is record 3 etc etc