ينسى روبي المتغيرات المحلية خلال فترة أثناء الحلقة؟

StackOverflow https://stackoverflow.com/questions/1654637

  •  11-09-2019
  •  | 
  •  

سؤال

أقوم بمعالجة ملف نصي رقم قياسي: لذلك أنا أبحث عن سلسلة البداية التي تشكل بداية سجل: لا يوجد علامة نهاية السجل، لذلك يمكنني استخدام بداية السجل التالي لتخليص السجل الأخير.

لذلك قمت ببناء برنامج بسيط للقيام بذلك، لكنني أرى شيئا ما يقاومني: يبدو أن الروبي ينسى المتغيرات المحلية موجودة - أو هل وجدت خطأ برمجة؟ [على الرغم من أنني لا أعتقد أن لدي: إذا قمت بتحديد الرسالة "المتغير" قبل حلقةتي، فأنا لا أرى الخطأ].

إليك مثال مبسط مع مثال على بيانات الإدخال ورسالة الخطأ في التعليقات:

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:

نص Alt http://bks0.books.google.com/books؟id=jcubtcr5xwwc&printsec=frontcover&img=1&zoom=5&sig=acfu3u1rnykha_p7vekppm1ow3o9ram0nq.

كتل ونطاق متغير

كتل تحديد نطاق متغير جديد: المتغيرات التي تم إنشاؤها داخل كتلة موجودة فقط داخل هذه الكتلة وغير محددة خارج الكتلة. كن حذرا، ولكن المتغيرات المحلية في الطريقة متاحة لأي كتل داخل هذه الطريقة. لذلك إذا قامت كتلة بتعيين قيمة متغير محددة بالفعل خارج الكتلة، فلا يقوم هذا بإنشاء متغير محلي جديد، ولكن بدلا من ذلك تعيين قيمة جديدة إلى المتغير الموجود بالفعل. في بعض الأحيان، هذا هو بالضبط السلوك الذي نريد:

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, ، ثم استمر في الضغط على السهم الأيسر حتى تقوم أيضا بما يلي:

  1. الوصول إلى بداية النطاق الحالي (يجب أن تفهم قواعد نطق الروبي لمعرفة متى يحدث هذا)
  2. رموز الوصول الذي يعين 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top