2015年6月14日日曜日

Writing Idiomatic Python 3.3(Kindle版)を読んで参考になった点~その1

KindleでPython関連の書籍は幾つか購入してるのですが、きちんと読んだ内容をBlogでまとめてはいなかったので最近読んだ書籍「Writing Idiomatic Python 3.3」で参考になった点を良くかBlogにまとめておこうと思います。

本書籍の特徴はタイトルの通り様々な側面から悪いサンプル(Harmful)をまず示した上でPythonicなサンプル(Idiomatic)を示して、どこが悪いかどこを直すべきかが分かりやすく解説されています。今回は4章「Control Structures and Functions」で参考になったTipsをメモしておこうと思います。

ここで紹介してるのはごく一部なので興味あれば一度本書席をチェックしていただければと思います。


4.1 If Statement

4.1.2 Avoid repeating variable name in compound in statement

# Harmful
is_generic_name = False
name = 'Tom'
if name == 'Tom' or name == 'Dick' or name == 'Harry':
    is_generic_name = True
    
# Idiomatic
name = 'Tom'
is_generic_name = name in ('Tom', 'Dick', 'Harry')

4.2 For Loops

4.2.3 Use else to execute code after a for loop concludes

# Harmful
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 (u'email addressが不正な形式です!')
            break
    if not has_malformed_email_address:
        print ('All email address are valid!')
        
# Idiomatic
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(u'email addressが不正な形式です!')
            break
    else:
        print('All email address are valid')

4.3 Functions

4.3.2 Use *args and **kwargs to accept arbitrary arguments

# Harmful
def make_api_call(foo, bar, baz):
    if baz in ('Unicorn', 'Oven', 'New York'):
        return foo(bar)
    else:
        return bar(foo)

# I need to add another parameter to 'make_api_call'
# without breaking everyone's existing code.
# I have two options...

def so_many_options():
    # I can tack on new parameters, but only if I make
    # all of them optional ...
    def make_api_call(foo, bar, baz, qux=None, foo_polarity=None,
                      baz_coefficient=None, quux_capacitor=None,
                      file_not_fould=None):
        # ... and so on ad infinitum
        return file_not_found

def version_graveyard():
    # ... or I can create a new function each time the signature
    # hanges.
    def make_api_call_v2(foo, bar, baz, qux):
        return make_api_call(foo, bar, baz) - qux

    def make_api_call_v3(foo, bar, baz, qux, foo_polarity):
        if foo_polarity != 'reserved':
            return make_api_call_v2(foo, bar, baz, qux)
        return None

    def make_api_call_v4(foo, bar, baz, qux, foo_polarity,
                         baz_coefficient):
        return make_api_call_v3(foo, bar, baz, qux, foo_polarity) * baz_coefficient

    def make_api_call_v5(foo, bar, baz, qux, foo_polarity,
                         baz_coefficient, quux_capacitor):
        # I don't need 'foo', 'bar', or 'baz' anymore, but I have to keep
        # supporting them...
        return baz_coefficient * quux_capacitor
    # ...

# Idiomatic
def make_api_call(foo, bar, baz):
    if baz in ('Unicorn', 'Oven', 'New York'):
        return foo(bar)
    else:
        return bar(foo)
    
    # I need to add another parameter to 'make_api_call'
    # without breaking everyone's existing coe.
    # Easy ...
    
    def new_hotness():
        def make_api_call(foo, bar, baz, *args, **kwargs):
            # Now I can accept any type and number of arguments
            # without worring about breaking existing code.
            baz_coefficient = kwargs['the_baz']
            
            # I can even forward my args to a different fucntion 
            # without knwoing their contents!
            return baz_coefficient in new_functions(args)

4.4 Exceptions

4.4.2 Use Exceptions to Write Code in an "EAFP" Style

※EASP→Easier to Ask for Forgiveness than Permission( which assumes things will go well and catches exceptions if they don't.)
# Harmful
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 None

# Idiomatic
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.4.3 Avoid "Swallowing(無条件で受け入れる)" Useful Exceptions With Bare Except Clauses

# Harmful
import requests
def get_json_response(url):
    try:
        r = requests.get(url)
        return r.json()
    except:
        print('Oops, something went wrong!')
        return None

# Idiomatic
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)
        return r.json()
    except:
        # do some logging here, but don't handle the exception ...
        raise 

0 件のコメント:

コメントを投稿