もた日記

くだらないことを真面目にやる

Pythonメモ : あまり知られていない(かもしれない)テクニック集 その3

github.com

上記リポジトリにあまり知られていない(かもしれない)Pythonのテクニックが49個まとめられていたので残りを見ていく(その1、その2は下記リンク)。

wonderwall.hatenablog.com

wonderwall.hatenablog.com


lightweightswitch.py : 辞書をswitch文として使う

Pythonにはswitch文がないので辞書をswitch文の代わりとして使う方法。

コード

#! /usr/bin/env python3
"""lightweight switch statement"""
a = {
    True: 1,
    False: -1,
    None: 0
}
print(a.get(False, 0))

"""works with functions as well"""
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

b = {
    '+': add,
    '-': subtract
}

print(b['+'](1, 1))

実行結果

-1
2


namedformatting.py : 辞書を使った文字列フォーマット

辞書を使った文字列フォーマットの方法。

コード

#! /usr/bin/env python3
"""easy string formatting using dicts"""

d = {'name': 'Jeff', 'age': 24}
print("My name is %(name)s and I'm %(age)i years old." % d)

"""for .format, use this method"""

d = {'name': 'Jeff', 'age': 24}
print("My name is {name} and I'm {age} years old.".format(**d))

"""alternate .format method"""
print("My name is {} and I'm {} years old.".format('Jeff','24'))

"""dict string formatting"""
c = {'email': 'jeff@usr.com', 'phone': '919-123-4567'}
print('My name is {0[name]}, my email is {1[email]} and my phone number is {1[phone]}'.format(d, c))

"""object string formatting"""
class Person:
  pass
me = Person()
me.name = 'Jeff'
me.email = 'jeff@usr.com'
me.phone = '919-123-4567'
print('My name is {me.name}, my email is {me.email} and my phone number is {me.phone}'.format(me=me))

実行結果

My name is Jeff and I'm 24 years old.
My name is Jeff and I'm 24 years old.
My name is Jeff and I'm 24 years old.
My name is Jeff, my email is jeff@usr.com and my phone number is 919-123-4567
My name is Jeff, my email is jeff@usr.com and my phone number is 919-123-4567


boolisslow.py : ブール値でのループは遅い(Python 2のみ)

TrueFalseはPython 3では予約語だが、Python 2では予約語ではないのでループで使用すると遅くなる。実行結果はPython 3では同じだが、Python 2ではブール値の方が遅い(timeitは実行時間計測モジュール)。なおPython 2ではTrueFalseの値を変えることができる。

コード

"""
True and False are keywords in python3, but not in Python2. In Python2
the value of True is 1, and the value of False is 0. True and False can be
regarded as vars and thus they are slow in while loop.

By the way, True and False can be changed in Python2.

Note: This leads to a performance improvement only in Python2.
"""

from timeit import timeit


def test_true():
    count = 1000
    while True:  # here is True
        if count < 0:
            break
        count -= 1


def test_1():
    count = 1000
    while 1:  # here is 1
        if count < 0:
            break
        count -= 1


# on my macbook pro 15
# test_true is about 59.3047289848 seconds
# test_1 is about 39.0966179371 seconds
print(timeit(test_true, number=1000000))
print(timeit(test_1, number=1000000))


# True and False can be changed in python2
True = 0
False = 100

print(True)
print(False)

実行結果(Python3。True = 0False = 100はエラーになるのでコメントアウト)

48.65638999501243
49.07187956303824
True
False

実行結果(Python2)

39.3514950275
28.439852953
0
100


calculator.py : operatorモジュールを使用した計算例

operatorモジュールはPythonの組み込み演算子に対応する関数群を提供する(ドキュメント)。例えば、operator.add(x, y)x+yは等価。

コード

#!/usr/bin/env python3
"""
This program lets you create a simple command line calculator without using
the 'if..else' construct. It uses built-in 'operator' module to accomplish the
same

Created with help of an answer on stackoverflow. Don't have the exact link.
"""

import operator
ops = {
    "+": operator.add,
    "-": operator.sub,
    "/": operator.truediv,
    "*": operator.mul
}

x = input("Enter an operator [OPTIONS: +, -, *, /]: ")
y = int(input("Enter number: "))
z = int(input("Enter number: "))

print (ops[x](y, z))

実行結果

Enter an operator [OPTIONS: +, -, *, /]: +
Enter number: 1
Enter number: 2
3


setoperators.py : setのoperatorとメソッドを使った集合演算

setのoperatorとメソッドを使った集合演算(ドキュメント)。

コード

#! /usr/bin/env python3
"""Python provides usual set operator"""
a = set(['a', 'b', 'c', 'd'])
b = set(['c', 'd', 'e', 'f'])
c = set(['a', 'c'])

# Intersection
print(a & b)

# Subset
print(c < a)

# Difference
print(a - b)

# Symmetric Difference
print(a ^ b)

# Union
print(a | b)

"""using methods instead of operators which take any iterable as a second arg"""

a = {'a', 'b', 'c', 'd'}
b = {'c', 'd', 'e', 'f'}
c = {'a', 'c'}

print(a.intersection(["b"]))

print(a.difference(["foo"]))

print(a.symmetric_difference(["a", "b", "e"]))

print(a.issuperset(["b", "c"]))

print(a.issubset(["a", "b", "c", "d", "e", "f"]))

print(a.isdisjoint(["y", 'z']))

print(a.union(["foo", "bar"]))

a.intersection_update(["a", "c", "z"])
print(a)

実行結果

{'d', 'c'}
True
{'a', 'b'}
{'a', 'b', 'f', 'e'}
{'d', 'e', 'b', 'a', 'c', 'f'}
{'b'}
{'b', 'a', 'd', 'c'}
{'d', 'c', 'e'}
True
True
True
{'d', 'b', 'a', 'c', 'bar', 'foo'}
{'a', 'c'}


codetofunction.py : コードで書いた計算式を関数にする

コードで書いた計算式を関数にする方法。

コード

#! /usr/bin/env python3
'''
A simple way to convert arbitrary Python code into a function
'''

import math # using sin, cos and sqrt for example

''' Takes a code string and returns a ready-to-use function '''
def compile_(s):
    code = """def f(x):\n  return {}""".format(s) # wrap the string as a function f(x)
    scope = {"sin": math.sin, "cos": math.cos, "sqrt": math.sqrt} # define the scope for the code to use
    exec(code, scope) # execute code inside the given scope
    # f(x) gets defined inside %vis%
    return scope["f"] # now we only have to extract it and return

f = compile_("x**2 + 2*sin(x)")
print(f(10))

実行結果

98.91195777822126


nested_functions.py : ネストされた関数

ネストされた関数の例。

コード

#!/usr/bin/env python3
"""nested functions"""
def addBy(val):
    def func(inc):
        return val + inc
    return func

addFive = addBy(5)
print(addFive(4))

addThree = addBy(3)
print(addThree(7))

実行結果

9
10


objgetnamedattribute.py : オブジェクトの属性値を取得

getattrでオブジェクトの属性値を取得する方法(ドキュメント)。

コード

#! /usr/bin/env python3
""" Return the value of the named attribute of an object """
class obj():
    attr = 1

foo = "attr"
print(getattr(obj, foo))

実行結果

1


unique_by_attr.py : 同じ属性値のオブジェクトの重複削除

同じ属性値のオブジェクトの重複を削除する方法。__eq__などは特殊メソッドのドキュメントを参照。

コード

#! /usr/bin/env python3
"""
    If we have some sequence of objects and want to remove items with the same attribute value
    Python creates a dict, where keys are value if attribute (bar in our case), values are object of the sequence.
    After that the dict is transformed back to list
    Note: in result we save the last from repeating elements (item2 in our case)!
"""


class Foo(object):
    def __init__(self, value):
        self.bar = value

    def __eq__(self, other):
        return self.bar == getattr(other, 'bar')

    def __hash__(self):
        return int(self.bar)

    def __repr__(self):
        return '{}'.format(self.bar)

item1 = Foo(15)
item2 = Foo(15)
item3 = Foo(5)

lst = [item1, item2, item3]


print(set(lst))

# original
unique_lst = list({getattr(obj, 'bar'): obj for obj in lst}.values())
print(unique_lst)  # [item2, item3]

実行結果

{5, 15}
[5, 15]


common_seq_method.py : シーケンス型で共通のメソッドを実行

シーケンス型で共通のメソッドを実行する方法。実行結果はlist(map〜[f.bar()〜でそれぞれ5個ずつprintされる。

コード

#! /usr/bin/env python3
"""Run common method of big sequence of objects"""
import operator


class Foo():
    def bar(self, *args, **kwargs):
        print('method bar works')


sequence = [Foo() for i in range(5)]

# in python3 map returns iterator so we must ask python to process elements by list()
# in python2 map(operator.methodcaller('bar'), sequence) works perfectly
list(map(operator.methodcaller('bar'), sequence))

# there is another way more understandable
[f.bar() for f in sequence]

実行結果

method bar works
method bar works
method bar works
method bar works
method bar works
method bar works
method bar works
method bar works
method bar works
method bar works


contextmanagers.py : コンテキストマネージャ

withを使うと正常に終わるか例外発生によって終わると、開いたファイルが自動的に閉じられる。これはコンテキストマネージャと呼ばれる機能でcontextlibを使うとコンテキストマネージャを作成することができる。詳しくは下記ブログを参照。

blog.amedama.jp

コード

#! /usr/bin/env python3
"""Context managers are useful for automatically releasing resources once you are done with them."""

# common context manager that will close
# a file when it has been read
with open('README.md') as f:
    contents = f.read()


# make your own context manager
import contextlib

@contextlib.contextmanager
def unlock(resource):
    resource = "unlocked"
    try:
        yield resource
    finally:
        resource = "locked"


# a resource that is locked
resource = "locked"

# test that it is indeed locked
print(resource)

# call your 'unlock' context manager with your resource
with unlock(resource) as unlocked:
    print(unlocked) # check that it is unlocked

# ensure it was re-locked when it left the 'unlock' context
print(resource)

実行結果

locked
unlocked
locked


cacheproperty.py : デコレータを使用した値のキャッシュ

デコレータを使用した値のキャッシュの例。1回目はcomputeが出力されるが、2回目以降はresultのみが出力される。デコレータについては下記リンクを参照。

qiita.com

コード

class PropertyCache:
    """ a decorator to cache property
    """

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, cls):
        if not obj:
            return self
        value = self.func(obj)
        setattr(obj, self.func.__name__, value)
        return value


class Foo:
    def __init__(self):
        self._property_to_be_cached = 'result'

    @PropertyCache
    def property_to_be_cached(self):
        print('compute')
        return self._property_to_be_cached

test = Foo()

print(test.property_to_be_cached)
print(test.property_to_be_cached)

実行結果

compute
result
result


deck_as_list.py : 特殊メソッドとnamedtupleを使用したトランプの例

特殊メソッド(ドキュメント)とnamedtuple(ドキュメント)を使用したトランプの例。コメントにあるdunder methodsとはアンダースコア2つの特殊メソッド(__getitem__など)のことらしい。

コード

#! /usr/bin/env python3

""" How to use dunder methods to add behavior to objects.
Here it's an example of how implement a french deck that can be used as a list"""

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])


class Deck:

    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

card_a = Card('A', 'spades')
print(card_a)

deck = Deck()
len(deck)

print(deck[0])
print(deck[-1])
for card in deck:
    print(card)

実行結果

Card(rank='A', suit='spades')
Card(rank='2', suit='spades')
Card(rank='A', suit='hearts')
# for car in deck:
Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
 省略
Card(rank='2', suit='diamonds')
 省略 
Card(rank='2', suit='clubs')
 省略
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')


socketmsghandling.py : ソケットプログラミング

ソケットプラグラミングの例(ドキュメント)。functools.partialドキュメント)を使うと簡潔に書ける。

コード

import socket
import functools

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn = s.connect(('localhost', 80))
msgs = []

# normal way
# while True:
#    msg = coon.recv(1024)
#    if recv:
#        msgs.append(msg)
# else:  # when no msg come, break
#         break

# hack way with iter and functools.partial
# this circle will auto break when msg is empty ''
for msg in iter(functools.partial(conn.recv, 1024), b''):
    msgs.append(msg)