2018年7月6日 星期五

[Python] 學習Pythonic的Python - (1)


如果第一個學習的不是Python語言, 常常依照其他語言的語法來寫Python, 這裡紀錄常被遺忘但卻重要的寫法, 節錄自書本Python神乎其技 


assert 
後面搭配Bool運算, 來檢查該運算式是否符合條件, 若無則觸發exception
1. assert為內部檢查輔助除錯工具
2. 幫助找bug, 不可使用在業務邏輯上, 不可作為處理執行期間的錯誤的機制
3. 直譯器加以設定後會關閉assert, 要小心bug產生
def apply_discount(product, discount):
    price = int(product['price'] & (1.0 - discount))
    assert 0 <= price <= product['price']
    return price

在串列.字典.集合每一行末端都加入逗點
1.可以在版本管理中看出修改哪一行
names = ['a',
         'b',
         'c',]

底線.雙底線
1._var : 命名慣例, 提醒其他協作者此變數要注意不要給scope外的使用, 但直譯器不會去管
2.__var : private
3.__var__ : 系統保留特殊用法
4._ : 暫時無使用變數, 或尚未命名變數
5.var_ : 區隔系統保留字與自己的變數 EX: class_ 

字串格式化
如果格式由使用者提供使用樣板字串, python3.6+使用字串差值, 不然用.format
# python3.6以前
_name = 'charles'
'Hello, {name}'.formate(name=_name)

# python3.6+
f'Hello, {name}'

# 格式由使用者提供使用template
from string import Template
t = Template('Hello', $name!)
t.substitute(name=_name)
>>'Hello, charles'


函式

1.在pythoon裡所有東西都是物件, 包含函式, 可指派給變數, 放在資料結構當中, 作為傳入函式的參數與回傳
2.包在函式內部的子函式可以記住父函數的參數值, 直接拿來使用稱, 稱之為閉包, lexical closure

3.Decorator
裝飾器能讓你在被裝飾的函式之前與之後執行裝飾器的程式, 延伸函式的呼叫行為, 將共用的功能獨立出來, 不用修改函式
, 加上去即可使用 ,不限制一個數量, 內建裝飾器如@property, @staticmethod各有不同功能:
使用場合:
  • 紀錄log
  • 管控權限與身份認證
  • 在呼叫API時很適合搭配使用
  • 在沒明確定義參數數量時使用 *args : 回傳tuple, **kwargs : 回傳Dictionary
import functools
def upercase(func):
    functools.wraps(func)
    def wrapper(*args, **kwargs):
        print args
        print kwargs
        return func().upper()
    return wrapper

@uppercase
def hi('boy',name='Charles'):
    return 'Hello'

>>> hi()
('boy')
{name : 'Charles'}
'HELLO'
*,** 也可用來將參數拆箱
tuple = (1, 2, 3)
list = [1, 2, 3]
def print_vector(x, y, x):
    print (x, y, x)
>>> print_vector(*tuple)
1,2,3
>>> print_vector(*list)
1,2,3

Class
__repr__來說明物件, 建議每個物件都要有__repr__, 用來明確表明物件的用途
# python 3.6
class Car:
    def __init__(delf, color, mileage):
        self.color = color
        self.mileage = mileage

    def __repr__(self):
        return (f{self.__class__.__name__}
               ('f'{self.color!r}, {self.mileage!r})')

    def __str__(self):
        return f'a {self.color car}'

>>> my_car = Car('yellow',100)
>>> repr(my_car)
'Car('yellow',100)'
>>> str(my_car)
'a yellow car'


staticmethod,classmethod說明
for python2.7
class calc:

    @staticmethod
    def add(x,y):
        answer = x + y
        print(answer)

#call staticmethod add directly 
#without declaring instance and accessing class variables
calc.add(5,7)
Or, Use instance method if you need to call other instance methods or use anything inside class
class calc:

    def add(self,x,y):
        print(self._add(x,y)) #call another instance method _add
    def _add(self,x,y):
        return x+y

#declare instance
c = calc()
#call instance method add
c.add(5,7) 
Additionally, Use classmethod if you need to use class variables but without declaring instance
class calc:

    some_val = 1

    @classmethod
    def add_plus_one(cls,x,y):
        answer = x + y + cls.some_val #access class variable
        print(answer)

#call classmethod add_plus_one dircetly
#without declaring instance but accessing class variables
calc.add_plus_one(5,7)

自訂Exception
也可以定義好階層一次捕捉特定例外 
class BaseValidationError(ValueError)
    pass

class NameTooShortError(BaseValidationError)
    pass
class NameTooLongError(BaseValidationError)
    pass

def validate(name):
    if len(name) < 10:
        raise NameTooShortError(name)
    if len(name) >= 20:
       raise NameTooLongError(name)  

try:
    validate(name)
catch BaseValidationError as err:
    handle_error(err)

使用抽象於父類別避免錯誤
from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

>>> c = Concrete()
TyepError:
"無法實體化帶有抽象方法bar的抽象類別Concrete"  
namedtuple代替tuple,dict或只當成存資料的class
不可變更資料集, 並且比一般透過必須透過index的資料集多了命名, 又比class使用更少的記憶體
from collections import namedtuple

Car = namedtuple('Car',{
    'color',
    'age',
]}

>>> my_car = Car(color='red', age=10)
>>> my_car.color
'red'

>>> my_car._asdict()
OrderedDict([('color','red'),('age',10)])

>>> json.dumps(my_car._asdict())
'{"color":"red","age":10}'

# 新增欄位
>>> ElectricCar = namedtuple('ElectricCar', Car._fields + ('charge',) 
>>> ElectricCar('red', 12, 100)

# 產生新的namedtuple
>>> my_car._replace(color='blue')
>>> my_car._make(['yellow',999])
Car(color='yellow', age=999)
資料結構
  • collections.nametuple 最建議使用
  • collections.OrderedDict 記住key的插入順序
  • collections.defaultDict 找不到key會回傳預設值
  • collections.ChainMap 把多個字典組合起來逐一搜尋
  • types.MappingProxyType 製作唯讀字典
  • set  唯一的集合
  • frozenset 不可變更的set
  • collections.Count
from collections import Counter
counter = Counter()
dict_a = {'aa':1, 'bb':2}
dict_b = {'aa':1, 'cc':2}
counter.update(dict_a)
counter.update(dict_b)

>>counter 
Counter({'aa':2, 'bb':2, 'cc':2})
>>len(counter)
3
>>sum(counter)
6
  • 不要用list來做collections.deque的事情(LIFO)
  • queue.PriorityQueue比list再sort好, 有新值插入不用重新排序
from queue import PriorityQueue
q = PriorityQueue()
q.put((2, 'b'))
q.put((1, 'a'))
q.put((3, 'c'))
while not q.empty()
next_item = q.get()
print(next_item)
>> 
(1, 'a')
(2, 'b')
(3, 'c')

迴圈
  • 串列生成式
>>> [x * x for x in range(10)
     if x % 2 == 0]
[0, 4, 16, 36, 64]

>>> {x * x for x in range(-9, 10)}
set({64,1,36,0,49,9,16,81,25,4})

>>> {x:x * x for x in range(5)}
{0:0, 1:1, 2:4, 3:9, 4:16}
  • 若須使用特殊迭代功能, 可以自訂迭代器, 依照需求用不同方式撰寫

字典技巧
  • 排序字典
>>> sorted(dict_a.items(), 
          key=lambda x: x[1],
          reverse=True)
[('d',4),('c',3),('b',2),('a',1)]
  • 字典合併-override重複依照左到右
# in Python 3.5+
>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 3, 'c': 4}
>>> z = {**x, **y}
>>> z
{'c': 4, 'a': 1, 'b': 3}

# In Python 2.x
>>> z = dict(x, **y)
>>> z
{'a': 1, 'c': 4, 'b': 3}

  • 用字典當switch/case
operator_dict ={
    'add': lambda: operator.add(x , y),
    'sub': lambda: operator.sub(x , y),
    'mul': lambda: operator.mul(x , y),
    'div': lambda: operator.div(x , y),
    'mod': lambda: operator.mod(x , y),
}
def dispatch_dict(operator, x, y):
    return operator_dict.get(operator, lambda: None)()
  • print dictionary, pprint可印出集合
>>> import json
>>> json.dumps(dict, indent=4, sort_keys=True)
{
    "a":11,
    "b":22
}
>>> import pprint
>>> pprint.pprint(dict)
{"a":11, "b":22, "c": set([1, 2, 3])}

Ref:
Dan Bader著,江志良譯 , Python神乎其技 , 旗標

dbader.orgDan Bader 

沒有留言:

張貼留言