2013年1月2日 星期三

[Python] PEP 8:Python程式碼風格指南 (3) ~ 程式碼撰寫建議


程式碼撰寫建議

  • Python 程式碼應該撰寫得無論在哪個實作中執行都一樣好(包含PyPy、Jython、IronPython、Cython、Psyco 或其他實作)。
    例如,CPython 中否些類型的字串連接效率是非常好的(如 a += ba = a + b)。但這些敘述在 Jython 中則執行得很慢。在函式庫中若有對執行效率要求嚴格的部份時,請務必使用 ''.join();這能確保程式碼中的字串連接無論在哪種實作中都能在線性時間(linear time,即O(n))內完成。
  • Comparisons to singletons like None should always be done with is or is not, never the equality operators.
    Also, beware of writing if x when you really mean if x is not None -- e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!
  • When implementing ordering operations with rich comparisons, it is best to implement all six operations (__eq____ne____lt____le____gt____ge__) rather than relying on other code to only exercise a particular comparison.
    To minimize the effort involved, the functools.total_ordering() decorator provides a tool to generate missing comparison methods.
    PEP 207 indicates that reflexivity rules are assumed by Python. Thus, the interpreter may swap y > x with x < yy >= x with x <= y, and may swap the arguments of x == y and x != y. The sort()and min() operations are guaranteed to use the < operator and the max() function uses the > operator. However, it is best to implement all six operations so that confusion doesn't arise in other contexts.
  • Use class-based exceptions.
    String exceptions in new code are forbidden, because this language feature is being removed in Python 2.6.
    Modules or packages should define their own domain-specific base exception class, which should be subclassed from the built-in Exception class. Always include a class docstring. E.g.:
    class MessageError(Exception):
        """Base class for errors in the email package."""
    
    Class naming conventions apply here, although you should add the suffix "Error" to your exception classes, if the exception is an error. Non-error exceptions need no special suffix.
  • When raising an exception, use raise ValueError('message') instead of the older form raise ValueError, 'message'.
    The paren-using form is preferred because when the exception arguments are long or include string formatting, you don't need to use line continuation characters thanks to the containing parentheses. The older form will be removed in Python 3.
  • When catching exceptions, mention specific exceptions whenever possible instead of using a bare except: clause.
    For example, use:
    try:
        import platform_specific_module
    except ImportError:
        platform_specific_module = None
    
    A bare except: clause will catch SystemExit and KeyboardInterrupt exceptions, making it harder to interrupt a program with Control-C, and can disguise other problems. If you want to catch all exceptions that signal program errors, use except Exception: (bare except is equivalent to except BaseException:).
    A good rule of thumb is to limit use of bare 'except' clauses to two cases:
    1. If the exception handler will be printing out or logging the traceback; at least the user will be aware that an error has occurred.
    2. If the code needs to do some cleanup work, but then lets the exception propagate upwards with raisetry...finally can be a better way to handle this case.
  • Additionally, for all try/except clauses, limit the try clause to the absolute minimum amount of code necessary. Again, this avoids masking bugs.
    Yes:
    try:
        value = collection[key]
    except KeyError:
        return key_not_found(key)
    else:
        return handle_value(value)
    
    No:
    try:
        # Too broad!
        return handle_value(collection[key])
    except KeyError:
        # Will also catch KeyError raised by handle_value()
        return key_not_found(key)
    
  • Context managers should be invoked through separate functions or methods whenever they do something other than acquire and release resources. For example:
    Yes:
    with conn.begin_transaction():
        do_stuff_in_transaction(conn)
    
    No:
    with conn:
        do_stuff_in_transaction(conn)
    
    The latter example doesn't provide any information to indicate that the __enter__ and __exit__ methods are doing something other than closing the connection after a transaction. Being explicit is important in this case.
  • Use string methods instead of the string module.
    String methods are always much faster and share the same API with unicode strings. Override this rule if backward compatibility with Pythons older than 2.0 is required.
  • Use ''.startswith() and ''.endswith() instead of string slicing to check for prefixes or suffixes.
    startswith() and endswith() are cleaner and less error prone. For example:
    Yes: if foo.startswith('bar'):
    No:  if foo[:3] == 'bar':
    
    The exception is if your code must work with Python 1.5.2 (but let's hope not!).
  • Object type comparisons should always use isinstance() instead of comparing types directly.
    Yes: if isinstance(obj, int):
    
    No:  if type(obj) is type(1):
    
    When checking if an object is a string, keep in mind that it might be a unicode string too! In Python 2.3, str and unicode have a common base class, basestring, so you can do:
    if isinstance(obj, basestring):
    
  • For sequences, (strings, lists, tuples), use the fact that empty sequences are false.
    Yes: if not seq:
         if seq:
    
    No: if len(seq)
        if not len(seq)
    
  • Don't write string literals that rely on significant trailing whitespace. Such trailing whitespace is visually indistinguishable and some editors (or more recently, reindent.py) will trim them.
  • Don't compare boolean values to True or False using ==.
    Yes:   if greeting:
    No:    if greeting == True:
    Worse: if greeting is True:
    
  • The Python standard library will not use function annotations as that would result in a premature commitment to a particular annotation style. Instead, the annotations are left for users to discover and experiment with useful annotation styles.
    Early core developer attempts to use function annotations revealed inconsistent, ad-hoc annotation styles. For example:
    • [str] was ambiguous as to whether it represented a list of strings or a value that could be either str or None.
    • The notation open(file:(str,bytes)) was used for a value that could be either bytes or str rather than a 2-tuple containing a str value followed by a bytes value.
    • The annotation seek(whence:int) exhibited an mix of over-specification and under-specification: int is too restrictive (anything with __index__ would be allowed) and it is not restrictive enough (only the values 0, 1, and 2 are allowed). Likewise, the annotation write(b: bytes) was also too restrictive (anything supporting the buffer protocol would be allowed).
    • Annotations such as read1(n: int=None) were self-contradictory since None is not an int. Annotations such as source_path(self, fullname:str) -> object were confusing about what the return type should be.
    • In addition to the above, annotations were inconsistent in the use of concrete types versus abstract types: int versus Integral and set/frozenset versus MutableSet/Set.
    • Some annotations in the abstract base classes were incorrect specifications. For example, set-to-set operations require other to be another instance of Set rather than just an Iterable.
    • A further issue was that annotations become part of the specification but weren't being tested.
    • In most cases, the docstrings already included the type specifications and did so with greater clarity than the function annotations. In the remaining cases, the docstrings were improved once the annotations were removed.
    • The observed function annotations were too ad-hoc and inconsistent to work with a coherent system of automatic type checking or argument validation. Leaving these annotations in the code would have made it more difficult to make changes later so that automated utilities could be supported.

沒有留言:

張貼留言