بيثون: كيفية تجاهل خطوط التخزين عند القراءة في ملف

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

سؤال

في بيثون، قرأت للتو خط شكل ملف نصي وأرغب في معرفة كيفية تشكيل التعليقات مع التجزئة مع # في بداية الخط.

أعتقد أنه يجب أن يكون شيء مثل هذا:

for 
   if line !contain #
      then ...process line
   else end for loop 

لكنني جديد في بيثون وأنا لا أعرف بناء الجملة

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

المحلول

يمكنك استخدام ابدا ب()

على سبيل المثال

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()

نصائح أخرى

أوصي أنك لا تتجاهل الخط بأكمله عندما ترى # حرف؛ فقط تجاهل بقية الخط. يمكنك القيام بذلك بسهولة مع وظيفة طريقة سلسلة تسمى partition:

with open("filename") as f:
    for line in f:
        line = line.partition('#')[0]
        line = line.rstrip()
        # ... do something with line ...

partition إرجاع tuple: كل شيء قبل سلسلة القسم، وسلسلة القسم، وكل شيء بعد سلسلة القسم. لذلك، عن طريق الفهرسة مع [0] نأخذ الجزء فقط قبل سلسلة التقسيم.

تحرير: إذا كنت تستخدم إصدار من Python الذي لا يملك partition(), ، إليك رمز يمكنك استخدامها:

with open("filename") as f:
    for line in f:
        line = line.split('#', 1)[0]
        line = line.rstrip()
        # ... do something with line ...

هذا ينقسم السلسلة على حرف "#"، ثم يبقي كل شيء قبل الانقسام. ال 1 الوسيطة يجعل .split() طريقة توقف بعد انقسام واحد؛ لأننا مجرد الاستيلاء على السلسلة الفرعية (عن طريق الفهرسة [0]) سوف تحصل على نفس الإجابة دون 1 حجة، ولكن قد يكون هذا أسرع قليلا. (المبسطة من الرمز الأصلي الخاص بي بفضل تعليق من ignr. كان الرمز الأصلي الخاص بي ميسييا دون سبب وجيه؛ شكراgnr.)

يمكنك أيضا كتابة نسختك الخاصة من partition(). وبعد هنا واحد يسمى part():

def part(s, s_part):
    i0 = s.find(s_part)
    i1 = i0 + len(s_part)
    return (s[:i0], s[i0:i1], s[i1:])

أشار dalle إلى أن "#" يمكن أن تظهر داخل سلسلة. ليس من السهل التعامل مع هذه الحالة بشكل صحيح، لذلك تجاهلتها للتو، لكن يجب أن قلت شيئا.

إذا كان ملف الإدخال الخاص بك لديه قواعد بسيطة بما فيه الكفاية للأسلوتات المقتبسة، فهذا ليس صعبا. سيكون من الصعب إذا قبلت أي سلسلة من Python قانوني مقتبس، لأن هناك علامات اقتباسات واحدة مقتبسة واحدة، ونقلت مزدوجة المعروضة، مع وجود عكوكات عاكسة تهرب من نهاية الخط، وأسلوتات مقتبسة ثلاثية (باستخدام اقتباسات واحدة أو مزدوجة)، و حتى الأوتار الخام! الطريقة الممكنة الوحيدة للتعامل مع كل ما ستكون آلة حالة معقدة.

ولكن إذا قصرنا أنفسنا على سلسلة بسيطة مقتبسة، يمكننا التعامل معها باستخدام آلة حالة بسيطة. يمكننا حتى أن نسمح اقتباس مزدوج في الخلفية داخل السلسلة.

c_backslash = '\\'
c_dquote = '"'
c_comment = '#'


def chop_comment(line):
    # a little state machine with two state varaibles:
    in_quote = False  # whether we are in a quoted string right now
    backslash_escape = False  # true if we just saw a backslash

    for i, ch in enumerate(line):
        if not in_quote and ch == c_comment:
            # not in a quote, saw a '#', it's a comment.  Chop it and return!
            return line[:i]
        elif backslash_escape:
            # we must have just seen a backslash; reset that flag and continue
            backslash_escape = False
        elif in_quote and ch == c_backslash:
            # we are in a quote and we see a backslash; escape next char
            backslash_escape = True
        elif ch == c_dquote:
            in_quote = not in_quote

    return line

لم أرغب حقا في الحصول على هذا معقد في سؤال "مبتدئ"، لكن آلة الحالة هذه بسيطة بشكل معقول، وآمل أن تكون مثيرة للاهتمام.

أنا قادم في وقت متأخر، لكن مشكلة التعامل مع نمط قذيفة (أو نمط بيثون) # التعليقات هو واحد شائع جدا.

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

for line in whatever:
    line = line.split('#',1)[0].strip()
    if not line:
        continue
    # process line

الحل الأكثر قوة هو استخدام shlex.:

import shlex
for line in instream:
    lex = shlex.shlex(line)
    lex.whitespace = '' # if you want to strip newlines, use '\n'
    line = ''.join(list(lex))
    if not line:
        continue
    # process decommented line

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

الحالة المشتركة عندما تكون أيضا تقسيم كل خط إدخال في الحقول (على Whitespace) أكثر بساطة:

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 

هذا هو أقصر شكل ممكن:

for line in open(filename):
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE

ال startswith() تتم إرجاع الأسلوب على سلسلة صحيح إذا كانت السلسلة التي تسميهها عند بدء تشغيل السلسلة التي مرت بها.

في حين أن هذا على ما يرام في بعض الظروف مثل البرامج النصية شل، فإن لديها مشكلتين. أولا، لا يحدد كيفية فتح الملف. الوضع الافتراضي لفتح الملف هو 'r', ، مما يعني "قراءة الملف في الوضع الثنائي". نظرا لأنك تتوقع تقديم ملف نصي أفضل لفتحه 'rt'. وبعد على الرغم من أن هذا التمييز غير ذي صلة في أنظمة التشغيل مثل UNIX، فمن المهم على نظام التشغيل Windows (وعلى ما Macs Pre-OS X).

المشكلة الثانية هي مقبض الملفات المفتوح. ال open() تقوم الدالة بإرجاع كائن ملف، ويعتبر ممارسة جيدة لإغلاق الملفات عند الانتهاء منها. للقيام بذلك، اتصل close() طريقة على الكائن. الآن، سوف بيثون من المحتمل هل هذا من أجلك، في النهاية؛ في كائنات Python يتم احتساب المرجع، وعندما يذهب عدد مرجعي الكائن إلى الصفر، يحصل تحريرها، وفي مرحلة ما بعد إصدار كائن سيتم تحريره، سيتصل Python Destructor (طريقة خاصة تسمى __del__). لاحظ أنني قلت من المحتمل: لدى Python عادة سيئة لا تدعو في الواقع إلى الديمقراطية على الكائنات التي تنخفض عد المرجعي إلى الصفر قبل انتهاء البرنامج قريبا. أعتقد أنه في عجلة من امرنا!

بالنسبة للبرامج قصيرة العمر مثل البرامج النصية شل، وخاصة لكائنات الملفات، لا يهم هذا. سيقوم نظام التشغيل تلقائيا بتنظيف أي مقابض ملفات تركت عند انتهاء البرنامج. ولكن إذا قمت بفتح الملف، اقرأ المحتويات، ثم بدأت حسابا طويلا دون إغلاق مقبض الملف بشكل صريح أولا، من المحتمل أن يترك Python مقبض الملف مفتوحا أثناء حسابك. وهذا ممارسة سيئة.

سيعمل هذا الإصدار في أي إصدار 2.x من Python، ويصلح كل من المشكلات التي ناقشتها أعلاه:

f = open(file, 'rt')
for line in f:
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE
f.close()

هذا هو أفضل شكل عام للإصدارات القديمة من بيثون.

كما اقترحه Steveha، فإن استخدام بيان "مع" يعتبر الآن أفضل الممارسات. إذا كنت تستخدم 2.6 أو أعلى، يجب عليك كتابة ذلك بهذه الطريقة:

with open(filename, 'rt') as f:
  for line in f:
    if line.startswith('#'):
      continue
    # PROCESS LINE HERE

سيؤدي بيان "مع" إلى تنظيف مقبض الملف لك.

في سؤالك، قلت "خطوط تبدأ مع #"، لذلك هذا ما أظهرته لك هنا. إذا كنت ترغب في تصفية الخطوط التي تبدأ ب بيضاء اختيارية و من ثم "#"، يجب عليك تجريد Whitespace قبل البحث عن "#". في هذه الحالة، يجب عليك تغيير هذا:

    if line.startswith('#'):

الى هذا:

    if line.lstrip().startswith('#'):

في بيثون، سلاسل ثابتة، لذلك هذا لا يغير قيمة line. وبعد ال lstrip() تقوم الأسلوب بإرجاع نسخة من السلسلة مع إزالتها بمسافة بيضاء الرائدة.

لقد وجدت مؤخرا أن وظيفة المولد تقوم بعمل رائع في هذا. لقد استخدمت وظائف مماثلة لتخطي خطوط التعليق، الخطوط الفارغة، إلخ.

أعرف وظيفتي كما

def skip_comments(file):
    for line in file:
        if not line.strip().startswith('#'):
            yield line

بهذه الطريقة، يمكنني فقط أن أفعل

f = open('testfile')
for line in skip_comments(f):
    print line

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

أعلم أن هذا هو موضوع قديم، ولكن هذه وظيفة مولد تستخدم لأغراضي الخاصة. يتم شرائط التعليقات بغض النظر عن المكان الذي تظهر فيه في الخط، وكذلك تجريد مسافة بيضاء المبينة / الزائدة وخطوط فارغة. النص المصدر التالي:

# Comment line 1
# Comment line 2

# host01  # This host commented out.
host02  # This host not commented out.
host03
  host04  # Oops! Included leading whitespace in error!

سوف تسفر:

host02
host03
host04

هنا هو رمز موثق، والذي يتضمن عرضا تجريبي:

def strip_comments(item, *, token='#'):
    """Generator. Strips comments and whitespace from input lines.

    This generator strips comments, leading/trailing whitespace, and
    blank lines from its input.

    Arguments:
        item (obj):  Object to strip comments from.
        token (str, optional):  Comment delimiter.  Defaults to ``#``.

    Yields:
        str:  Next uncommented non-blank line from ``item`` with
            comments and leading/trailing whitespace stripped.

    """

    for line in item:
        s = line.split(token, 1)[0].strip()
        if s:
            yield s


if __name__ == '__main__':
    HOSTS = """# Comment line 1
    # Comment line 2

    # host01  # This host commented out.
    host02  # This host not commented out.
    host03
      host04  # Oops! Included leading whitespace in error!""".split('\n')


    hosts = strip_comments(HOSTS)
    print('\n'.join(h for h in hosts))

ستكون حالة الاستخدام العادي لتجريد التعليقات من ملف (أي، ملف المضيفين، كما في مثالي أعلاه). إذا كان هذا هو الحال، فسيتم تعديل نهاية الذيل للرمز أعلاه إلى:

if __name__ == '__main__':
    with open('hosts.txt', 'r') as f:
        hosts = strip_comments(f)

    for host in hosts:
        print('\'%s\'' % host)

يمكن أن تبدو نسخة أكثر إحكاما من تعبير التصفية أيضا:

for line in (l for l in open(filename) if not l.startswith('#')):
    # do something with line

(l for ... ) يطلق عليه "تعبير المولدات" الذي يعمل هنا كخط ماستر لف للملف من الملفات غير المطابقة من الملف أثناء التكرار فوقه. لا تخلط بينها بنفس الشيء في برايكيتس مربع [l for ... ] ما هو "فهم قائمة" الذي سيقرأ أولا جميع الخطوط من الملف إلى الذاكرة، ثم سيبدأ في التكرار فوقه.

في بعض الأحيان قد ترغب في الحصول عليها أقل سنين وأكثر قابلية للقراءة:

lines = open(filename)
lines = (l for l in lines if ... )
# more filters and mappings you might want
for line in lines:
    # do something with line

سيتم تنفيذ جميع المرشحات على الطيران في تكرار واحد.

استخدم Regex. re.compile("^(?:\s+)*#|(?:\s+)") لتخطي الخطوط والتعليقات الجديدة.

أنا أميل إلى استخدام

for line  in lines:
    if '#' not in line:
        #do something

سيؤدي ذلك إلى تتجاهل هذا الخط بأكمله، على الرغم من أن الإجابة التي تتضمن rpartition لها رسوم التصويت حيث يمكن أن تشمل أي معلومات من قبل #

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top