Writing idiomatic python

1. if Statements

1.1. Chain comparisons to make if statements more concise

Don’t:

1
2
if x <= y and y <= z:
    return Ture

Do:

1
2
if 1 < x < 6:
    print 'python'

1.2. Avoid placing conditional branch code on the same line as the colon

Don’t:

1
2
3
4
name = 'Tom'
address = "NY"
if name: print(name)
print(address)

Do:

1
2
3
4
5
name = 'Tom'
address = "NY"
if name:
    print(name)
print(address)

1.3. Avoid repeating variable name in compound if statement

Don’t:

1
2
3
4
is_generic_name=False
name='Tom'
if name=='Tom' or name=='Dick' or name=='Harry':
    is_generic_name=True

Do:

1
2
name='Tom'
is_generic_name = name in ('Tom','Dick','Harry')

1.4. Avoid comparing directly to True,False,or None

All of the following are considered False
None
False
•zero for numeric types
•empty sequences
•empty dictionaries
•a value of 0 or False returned when either __len__ or __nonzero__ is called

Don’t

1
2
if foo == True:
    pass

Do:

1
2
3
4
5
6
if foo:
    pass

def insert_value(value,position=None):
    if position is not None:
        pass

1.5. Use if and else as a short ternary operator replacement(三元运算符 ? :)

Don’t:

1
2
3
4
5
foo = True
value = 0
if foo:
    value = 1
print(value)

Do:

1
2
3
foo = True
value = 1 if foo else 0
print(value)

2. for Loops

2.1. enumerate function in loops instead of creating an index variable

Don’t:

1
2
3
4
5
my_container = ['Larry', 'Moe', 'Curly']
index = 0
for element in my_container:
    print ('{} {}'.format(index, element))
    index += 1

Do:

1
2
3
my_container = ['Larry', 'Moe', 'Curly']
for index, element in enumerate(my_container):
    print ('{} {}'.format(index, element))

2.2. Use the in keywords to iterate over an iterable

Don’t:

1
2
3
4
5
my_container = ['Larry', 'Moe', 'Curly']
index = 0
while index < len(my_container):
    print (my_container[index])
    index += 1

Do:

1
2
3
my_container = ['Larry', 'Moe', 'Curly']
for ele in my_container:
    print(ele)

2.3. Use else to execute code after a for loop concludes

Don’t:

1
2
3
4
5
6
7
8
9
10
for user in get_all_users():
    has_malformed_email_address=False
    print('Checking{}'.format(user))
    for email_address in user.get_all_email_addresses():
        if email_is_malformed(email_address):
            has_malformed_email_address=True
            print('Has a malformede mail address!')
            break
    if not has_malformed_email_address:
        print('All email addresses are valid!')

Do:

1
2
3
4
5
6
7
8
for user in get_all_users():
    print('Checking{}'.format(user))
    for email_address in user.get_all_email_addresses():
        if email_is_malformed(email_address):
            print('Has a malformede mail address!')
            break
    else:
        print('All email addresses are valid!')

3. Functions

3.1. Avoid using a mutable object as the default value for a function argument

list,dict,set and most class instances are mutable.
string,int,tuple objects are all immutabled.

Don’t:

1
2
3
def test(a,L=[]):
    L.append(a)
    return L

Do:

1
2
3
4
5
def test(a,L=None):
    if L is None:
        L = []
    L.append(a)
    return L

3.2. Use return to evaluate expressions in addition to return values

Don’t:

1
2
3
4
5
def all_equal(a,b,c):
    result=False
    if a==b==c:
        result=True
    return result

Do:

1
2
def all_equal(a,b,c):
    return a==b==c

3.3. Learn to use keyword arguments properly

Don’t:

1
2
3
4
5
6
7
8
def print_list(list_value,sep):
    print('{}'.format(sep).join(list_value))

the_list=['a','b','c']
the_other_list=['Jeff','hates','Java']
print_list(the_list,'')
print_list(the_other_list,'')
print_list(the_other_list,',')

Do:

1
2
3
4
5
6
7
8
def print_list(list_value,sep=' '):
    print('{}'.format(sep).join(list_value))

the_list=['a','b','c']
the_other_list=['Jeff','hates','Java']
print_list(the_list)
print_list(the_other_list)
print_list(the_other_list,',')

3.4. Use *args and **kwargs to accept arbitrary arguments

Do:

1
2
3
def call(foo,bar,baz,*args,**kwargs):
    baz_coefficient = kwargs['the_baz']
    return baz_coefficient in some_function(args)

3.5. Learn to treat functions as values

Don’t:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def print_addition_table():
    for x in xrange(1,4):
        for y in xrange(1,4):
            print(str(x+y)+'\n')

def print_subtraction_table():
    for x in xrange(1,4):
        for y in xrange(1,4):
            print(str(x-y)+'\n')

def print_multiplication_table():
    for x in xrange(1,4):
        for y in xrange(0,4):
            print(str(x*y)+'\n')

def print_division_table():
    for x in xrange(1,4):
        for y in xrange(1,4):
            print(str(x/y)+'\n')

print_addition_table()
print_subtraction_table()
print_multiplication_table()
print_division_table()

Do:

1
2
3
4
5
6
7
8
import operator as op
def print_table(operator):
    for x in xrange(1,4):
        for y in xrange(0,4):
            print(operator(x,y)+'\n')

for operator in (op.add,op.sub,op.mul,op.div):
    print_table(operator)

3.6. Use the function-based version of print

Don’t:

1
print 1,'foo',__name__

Do:

1
2
from __future__ import print_function
print(1,'foo',__name__)

4. Exceptions

4.1. Don’t be Afraid to Use Exceptions

4.2. Use Exceptions to Write Code in an “EAFP” Style

Code that doesn’t use exceptions is always checking if it’s OK to do something.

Don’t:

1
2
3
4
5
6
7
8
9
10
def get_log_level(config_dict):
    if'ENABLE_LOGGING' in config_dict:
        if config_dict['ENABLE_LOGGING'] !=True:
            return None
        elif not 'DEFAULT_LOG_LEVEL' in config_dict:
            return None
        else:
            return config_dict['DEFAULT_LOG_LEVEL']
    else:
        return None

Do:

1
2
3
4
5
6
7
8
9
def get_log_level(config_dict):
    try:
        if config_dict['ENABLE_LOGGING']:
            return config_dict['DEFAULT_LOG_LEVEL']
    except KeyError:
        #if either value wasn't present,a
        #KeyError will be raised,so
        #return None
        return None

4.3. Avoid ‘’Swallowing’’ Useful Exceptions With Bare Except Clauses

A common mistake made by novices when using exceptions is to feel compelled to catch anye xception code could raise.

Don’t:

1
2
3
4
5
6
7
8
import requests
def get_json_response(url):
    try:
        r=requests.get(url)
        return r.json()
    except:
        print('Oops,somethingwentwrong!')
        return None

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
def get_json_response(url):
    return requests.get(url).json()

#If we need to make note of the exception,we
#would write the function this way...
def alternate_get_json_response(url):
    try:
        r=requests.get(url)
        returnr.json()
    except:
        #do some logging here,but don't handle the exception
        #...
        raise

5. Data Structure

5.1 Use multiple assignment to condense variables all set to the same value

1
x = y = z = 5

instead of
1
2
3
x = 5
y = 5
z = 5

5.2. Avoid using a temporary variable when performing a swap of two values

1
2
3
4
x = 10
y = 5
# tmp=x, x=y, y=temp
x, y = y, x

5.3. Chain string functions to make a simple series of transformations more clear

1
2
str1 = 'i am a bug!'
str4 = str1.strip().upper().replace('!', '?')

5.4. Use ''.join when creating a single string for list elements

1
2
mylist = ['i', 'am', 'a', 'bug']
resultStr = ''.join(mylist)

5.5. Prefer the format function for formatting strings

1
2
3
name = "tony"
age = 100
str2 = "myname : {} my age {}".format(name, age)

5.6. Usea list comprehension to create a transformed version of an existing list

1
2
3
mylist = range(20)
odd_list = [e for e in mylist if e % 2 == 1]
last_five_list = mylist[-5:]

5.7. Prefer list comprehensions to the built-in map() and filter() functions

Don’t:

1
2
3
4
5
6
7
the_list=[1,2,3,4,5,6,7,8,9,10]

def is_odd(number):
    retrun number % 2 == 1

odd_numbers=filter(is_odd,the_list)
odd_numbers_times_two=list(map(lambdax:x*2,odd_numbers))

Do:

1
odd_numbers_times_two = [n*2 for n in the_list if n % 2 == 1]

5.8. Use the built-in function sum to calculate the sum of a list of values

1
the_sum = sum(the_list)

5.9. Use all to determine if all elements of an iterable are True

1
2
3
def contains_zero(itor):
    """all只有在列表中的每个元素都返回True时才会返回True"""
    return not all(itor)

5.10. Prefer xrange to range unless you need the resulting list

1
2
3
for index in xrange(3, 1000000000):
    if index % 2 == 0:
        print index

5.11. 用dict对象完成 switch…case… 的功能

5.12. 使用dict.get方法可以提供一个默认值

1
print mydict.get('b', 2)

5.13. list/dict/set comprehension

1
2
3
4
5
user_list = [{'name': 'lucy', 'email': 'lucy@g.com'}, {'name': 'lily',\
'email': 'lily@g.com'}]
{user['name']: user['email'] for user in user_list if 'email' in user}

users_first_names = {user.first_name for user in users}

5.14. Use sets to eliminate duplicate entries from Iterable containers

1
unique_names = set(name_list)

5.15. Use collections.namedtuple to make tuple-heavy code more clear

1
2
3
4
5
6
7
8
9
rows = [('lily', 20, 2000), ('lucy', 19, 2500)]
for row in rows:
    print '{}` age is {}, salary is {} '.format(row[0], row[1], row[2])

from collections import namedtuple
Employee = namedtuple('Employee', 'name, age, salary')
for row in rows:
    employee = Employee._make(row)
    print '{}` age is {}, salary is {} '.format(employee.name, employee.age, employee.salary)

5.16. Use _ as a placeholder for data in a tuple that should be ignored

Don’t:

1
2
3
4
(name,age,temp,temp2)=get_user_info(user)
if age>21:
    output='{name} can drink!'.format(name=name)
#"Wait,where are temp and temp2 being used?"

Do:

1
2
3
4
(name,age,_,_)=get_user_info(user)
if age>21:
    output='{name} cand rink!'.format(name=name)
#"Clearly,only name and age are interesting"

5.17. Use tuples to unpack data

Don’t:

1
2
3
4
5
6
7
list_from_comma_separated_value_file=['dog','Fido',10]
animal=list_from_comma_separated_value_file[0]
name=list_from_comma_separated_value_file[1]
age=list_from_comma_separated_value_file[2]

output=('{name} the {animal} is {age} years old'.format(\
    animal=animal,name=name,age=age))

Do:

1
2
3
4
list_from_comma_separated_value_file=['dog','Fido',10]
(animal,name,age)=list_from_comma_separated_value_file
output=('{name} the {animal} is {age} years old'.format(\
    animal=animal,name=name,age=age))

5.18. Use a tuple to return multiple values from a function

1
2
3
4
5
6
7
8
from collections import Counter
def calculate_staistics(value_list):
    mean=float(sum(value_list)/len(value_list))
    median=value_list[int(len(value_list)/2)]
    mode=Counter(value_list).most_common(1)[0][0]
    return(mean,median,mode)

(mean,median,mode)=calculate_staistics([10,20,20,30])

6. Classes

6.1. Use the isinstance function to determine the type of ano bject

isinstance来判断对象的类型

1
2
3
4
5
6
7
def get_size(some_object):
    if isinstance(some_object, (list, dict, str, tuple)):
        return len(some_object)
    elif isinstance(some_object, (bool, type(None))):
        return 1
    elif isinstance(some_object, (int, float)):
        return int(some_object)

6.2. Use leading underscores in function and variable names to denote

private attribute
Don’t:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class   Foo(object):
    def __init__(self):
        self.id=8
        self.value=self.get_value()

    def get_value(self):
        pass

    def should_destroy_earth(self):
        return self.id==42

class Baz(Foo):
    def get_value(self,some_new_parameter):
        """Since 'get_value' is called from the base class's __init__ method
        and the base class definition doesn't take a parameter,trying to
        createa Baz instance will fail"""
        pass

class Qux(Foo):
    def __init__(self):
        """We aren't aware of Foo's internals,and we innocently createan
        instance attribute named'id' and set it to 42.This overwrites
        Foo's id attribute and we inadvertently blow up the earth."""
        super(Qux,self).__init__()
        self.id=42

q=Qux()
b=Baz() #Raises'TypeError'
q.should_destroy_earth()#returnsTrue
q.id==42#returnsTrue

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Foo(object):
    def __init__(self):
        """ Since 'id' is of vital importance to us,we don't want a derived
        class accidentally overwriting it.We'll prepend with double
        underscores to introduce name mangling."""
        self.__id=8
        self.value=self.__get_value() # call our 'private copy'

    def get_value(self):
        pass

    def should_destroy_earth(self):
        return self.__id==42

    #Here,we're storing a 'private copy' of get_value,
    #and assigning it to '__get_value'.Even if aderived
    #class overrides get_value in a way incompatible with
    #ours,we're fine
    __get_value=get_value

class Baz(Foo):
    def get_value(self,some_new_parameter):
        pass

class Qux(Foo):
    def __init__(self):
        """ Now when we set 'id' to 42,it's not the same 'id' that
        'should_destroy_earth' is concerned with. In fact,if you inspecta
        Qux object,you'll find it doesn't havean __id attribute.So we can't
        mistakenly change Foo's__id attribute even if we wanted to."""
        super(Qux,self).__init__()
        #No relation to Foo's id,purely coincidental
        self.id=42

q=Qux()
b=Baz() #Works fine now
q.should_destroy_earth()#returns False
q.id==42 #returns True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# "private" data
class A(object):
    __i = 1
 
    def __init__(self):
        self._j = 2
 
    def printJ(self):
        return self._j
       
class B(A):
    def __init__(self):
        super(B, self).__init__()
        self._j = 3
 
b = B()
print b.printJ()
"""比如某一个类属性以下划线(_)
开头,则是显式的的约定该成员是具有私有属性的,最好不要在外部直接调用"""

"""而双下划线(__)开头的成员更具有“私有”的属性,
因为在外部不能直接调用,因为python解译器把这些的成员名称进行变形处理,
使外部不能轻易直接访问。"""
class A(object):
    __i = 1
 
    def __init__(self):
        self.__j = 2
 
    def printJ(self):
        return self.__j
       
class B(A):
    def __init__(self):
        super(B, self).__init__()
        self.__j = 3
 
b = B()
print b.printJ()

6.3. Use properties to “future-proof” your class implementation

Oftentimes,it is convenient to provide direct access to a class’s data attributes.A Point class,for example,may have x and y attributes rather than using getter and setter functions.
Don’t:

1
2
3
4
class Product(object):
    default__init__(self,name,price):
        self.name=name
        self.price =price

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Product(object):
    default__init__(self,name,price):
        self.name=name
        self.price =price

    @property
    def price(self):
        #now if we need to change how price is calculated,we can do it
        #here(or in the "setter" and __init__)
        retrun self._price * TAX_RATE

    @price.setter
    def price(self,value):
        #The "setter" function must have the same name as the property
        self._price=value

6.4. Use __repr__ for a machine-readable representation of a class and define __str__ in a class to show a human-readable representation

While __str__ is used for printing a class in a way that a human can read,__repr__ is used for machines to read.
Don’t

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo(object):
    def __init__(self,bar=10,baz=12,cache=None):
        self.bar=bar
        self.baz=baz
        self._cache=cache or {}

    def__str__(self):
        return'Bar is{},Baz is{}'.format(self.bar,self.baz)

def log_to_console(instance):
    print(instance)

log_to_console([Foo(),Foo(cache={'x':'y'})])

Do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo(object):
    def __init__(self,bar=10,baz=12,cache=None):
        self.bar=bar
        self.baz=baz
        self._cache=cache or {}

    def __str__(self):
        return 'Bar is{},Baz is{}'.format(self.bar,self.baz)

    def __repr__(self):
        return 'Foo({},{},{})'.format(self.bar,self.baz,self._cache)

def log_to_console(instance):
    print(instance)

log_to_console([Foo(),Foo(cache={'x':'y'})])

7. Context Managers

7.1. Use a context manager to ensure resources are properly managed

Don’t:

1
2
3
4
file_handle=open(path_to_file,'r')
for line in file_handle.readlines():
    if raise_exception(line):
        print('No!An Exception!')

Do:

1
2
3
4
with open(path_to_file,'r') as file_handle:
    for line in file_handle:
        if raise_exception(line):
            print('No!AnException!')

8. Generators

8.1. Prefer a generator expression to a list comprehension for simple iteration

generator, 用 (…) 代替 [….], 就像用xrange代替range一样

1
2
for e in (i * 2 for i in oneList):
    pass

8.2. Use a generator to lazily load infinite sequences

Don’t:

1
2
3
4
5
6
7
8
9
10
11
12
13
def get_twitter_stream_for_keyword(keyword):
    imaginary_twitter_api=ImaginaryTwitterAPI()
    if imaginary_twitter_api.can_get_stream_data(keyword):
        return imaginary_twitter_api.get_stream(keyword)

current_stream=get_twitter_stream_for_keyword('#jeffknupp')
for tweet in current_stream:
    process_tweet(tweet)

def get_list_of_incredibly_complex_calculation_results(data):
    return  [first_incredibly_long_calculation(data),\
    second_incredibly_long_calculation(data),\
    third_incredibly_long_calculation(data)]

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_twitter_stream_for_keyword(keyword):
    imaginary_twitter_api=ImaginaryTwitterAPI()
    while imaginary_twitter_api.can_get_stream_data(keyword):
        yield imaginary_twitter_api.get_stream(keyword)

for tweet in get_twitter_stream_for_keyword('#jeffknupp'):
    if got_stop_signal:
        break
    process_tweet(tweet)

def get_list_of_incredibly_complex_calculation_results(data):
    yield first_incredibly_long_calculation(data)
    yield second_incredibly_long_calculation(data)
    yield third_incredibly_long_calculation(data)

10. Organizing Your Code

10.1. Use all capital letters when declaring global constant values

1
SECONDS_IN_A_DAY=60*60*24

10.2. Avoid placing multiple statements on a single line

10.3. Multiple line docstring """"""

10.4. Document what something does,not how

10.5. Arrange your import statements in a standard order

10.6. Prefer absolute imports to relative imports

10.7. Do not use from foo import * to import the contents of a module.

10.8. Use the tuples to organizea long list of modules to import

10.9. Make use of __init__.py files to simplify package interfaces

10.10. Separate your test code from your application code

10.11. Use unit tests to aidin refactoring

Refernces

1.Writing idiomatic python

2.Writing idiomatic python in ZH

http://jeffknupp.com/blog/2014/01/31/a-python-app-to-see-what-people-are-saying-about-you/

http://jeffknupp.com/blog/2014/06/18/improve-your-python-python-classes-and-object-oriented-programming/