注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Code@Pig Home

喜欢背着一袋Code傻笑的Pig .. 忧美.欢笑.记忆.忘却 .之. 角落

 
 
 

日志

 
 

[轻书快读] Effective Python - 59 Specific Ways to Write Better Python (3)  

2016-03-21 09:44:12|  分类: lang_python |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Item 21: Enforce Clarity with Keyword-Only Arguments

希望 ignore_overflow 和 ignore_zero_division 只能通过 keyword 的方式传入
def safe_division(number, divisor, ignore_overflow=False, ignore_zero_division):
  ...

# python 3 中可以这样
def safe_division(number, divisor, *, ignore_overflow=False, ignore_zero_division):
  ...

safe_division(1, 10**500, True, False)

>>>
TypeError: safe_division() takes 2 positional arguments but 4 were given

# python 2 的做法
def safe_division(number, divisor, **kwargs):
  ignore_overflow = kwargs.pop('ignore_overflow', False)
  ignore_zero_div = kwargs.pop('ignore_zero_division', False)
  if kwargs:
    raise TypeError('Unexpected **kwargs: %r' % kwargs)


3. Classes and Inheritance

Item 22: Prefer Helper Classes Over Bookkeeping with Dictionaries and Tuples

业务逻辑复杂一些的,就很容易出现 dict 嵌套 dict 的情况,代码变得复杂。比如:
class WeightedGradeBook(object):
  def __init__(self):
    self._grades = {}

  def add_student(self, name):
    self._grades[name] = {}

  def report_grade(self, name, subject, score, weight):
    by_subject = self._grades[name]
    grade_list = by_subject.setdefault(subject, [])
    grade_list.append((score, weight))

  def average_grade(self, name):
    by_subject = self._grades[name]
    score_sum, score_count = 0, 0
    for subject, scores in by_subject.items():
      subject_avg, total_weight = 0, 0
      for score, weight in scores:
        # ...
    return score_sum / score_count

本条目就是建议大家多用 class 来理清这些逻辑。下面是推荐做法:
import collections
Grade = collections.namedtuple('Grade', ('score', 'weight'))

class Subject(object):
  def __init__(self):
    self._grades = []

  def report_grade(self, score, weight):
    self._grades.append(Grade(score, weight))

  def average_grade(self):
    total, total_weight = 0, 0
    for grade in self._grades:
      total += grade.score * grade.weight
      total_weight += grade.weight
    return total / total_weight

class Student(object):
  def __init__(self):
    self._subjects = {}

  def subject(self, name):
    if name not in self._subjects:
      self._subjects[name] = Subject()
    return self._subjects[name]

  def average_grade(self):
    total, count = 0, 0
    for subject in self._subjects.values():
      total += subject.average_grade()
      count += 1
    return total / count

class Gradebook(object):
  def __init__(self):
    self._students = {}

  def student(self, name):
    if name not in self._students:
      self._students[name] = Student()
    return self._students[name]

book = Gradebook()
albert = book.student('Albert Einstein')
math = albert.subject('Math')
math.report_grade(80, 0.1)
# ...
print albert.average_grade()


Item 23: Accept Functions for Simple Interfaces Instead of Classes

因为 function 是 first-class 的,很多时候不需要写 Functor
def increment_with_report(current, increments):
  added_count = 0

  def missing():
    nonlocal added_count    # Stateful closure
    added_count += 1
    return 0

  result = defaultdict(missing, current)
  for key, amount in increments:
    result[key] += amount

  return result, added_count

这个是类似 C++ Functor 的版本
def increment_with_report(current, increments):
  class BetterCountMissing(object):
    def __init__(self):
      self.added = 0

    def __call__(self):
      self.added += 1
      return 0

  counter = BetterCountMissing()
  result = defaultdict(counter, current)  # Relies on __call__
  for key, amount in increments:
    result[key] += amount


Item 24: Use @classmethod Polymorphism to Construct Objects Generically

如下,我们可以从 GenericInputData、GenericWorker 上继承出更多的 XxxInputData、XxxWorker。
使用的时,将 class 传入 mapreduce() 就好。方便增加新的业务逻辑,而核心业务代码 map-reduce 并不需要修改。
class GenericInputData(object):
  def read(self):
    raise NotImplementedError

  @classmethod
  def generate_inputs(cls, config):
    raise NotImplementedError

class PathInputData(GenericInputData):
  def __init__(self, path):
    super(PathInputData, self).__init__()
    self.path = path

  def read(self):
    return open(self.path).read()

  @classmethod
  def generate_inputs(cls, config):
    data_dir = config['data_dir']
    for name in os.listdir(data_dir):
      yield cls(os.path.join(data_dir, name))

class GenericWorker(object):
  def __init__(self, input_data):
    self.input_data = input_data
    self.result = None

  def map(self):
    raise NotImplementedError

  def reduce(self):
    raise NotImplementedError

  @classmethod
  def create_workers(cls, input_class, config):
    workers = []
    for input_data in input_class.generate_inputs(config):
      workers.append(cls(input_data))
    return workers

class LineCountWorker(GenericWorker):
  def map(self):
    data = self.input_data.read()
    self.result = data.count('\n')

  def reduce(self, other):
    self.result += other.result

def execute(workers):
  threads = [Thread(target=w.map) for w in workers]
  for thread in threads: thread.start()
  for thread in threads: thread.join()

  first, rest = workers[0], workers[1:]
  for worker in rest:
    first.reduce(worker)
  return first.result

def mapreduce(worker_class, input_class, config):
  workers = worker_class.create_workers(input_class, config)
  return execute(workers)

with TemporaryDirectory() as tmpdir:
  write_test_files(tmpdir)
  config = {'data_dir': tmpdir}
  result = mapreduce(LineCountWorker, PathInputData, config)


Item 25: Initialize Parent Classes with super

如何解决 diamond inheritance(菱形继承) 问题?python 2.2 引入了 mro。
class MyBaseClass(object):
  def __init__(self, value):
    self.value = value

class TimesFive(MyBaseClass):
  def __init__(self, value):
    super(TimesFive, self).__init__(value)
    self.value *= 5

class PlusTwo(MyBaseClass):
  def __init__(self, value):
    super(PlusTwo, self).__init__(value)
    self.value += 2

class GoodWay(TimesFive, PlusTwo):
  def __init__(self, value):
    super(GoodWay, self).__init__(value)

foo = GoodWay(5)
print 'Should be 5 * (5 + 2) = 35 and is', foo.value

>>>
Should be 5 * (5 + 2) = 35 and is 35

看看 MRO
from pprint import pprint
pprint(GoodWay.mro())

>>>
[<class '__main__.GoodWay'>,
 <class '__main__.TimesFive'>,
 <class '__main__.PlusTwo'>,
 <class '__main__.MyBaseClass'>,
 <class 'object'>]

调用顺序
GoodWay.__init__()
 => TimesFive.__init__()
    => PlusTwo.__init__()
       => MyBaseClass.__init__()

# Python 3 又改进了一下 super
class Explicit(MyBaseClass):
  def __init__(self, value):
    super(__class__, self).__init__(value * 2)

class Implicit(MyBaseClass):
  def __init__(self, value):
    super().__init__(value * 2)


Item 26: Use Multiple Inheritance Only for Mix-in Utility Classes

啥是 mixin?没有 data member,只有 helper method 的辅助类。
class ToDictMixin(object):
  def to_dict(self):
    return self._traverse_dict(self.__dict__)

  def _traverse_dict(self, instance_dict):
    output = {}
    for key, value in instance_dict.items():
      output[key] = self._traverse(key, value)
    return output

  def _traverse(self, key, value):
    if isinstance(value, ToDictMixin):
      return value.to_dict()
    elif isinstance(value, dict):
      return self._traverse_dict(value)
    elif isinstance(value, list):
      return [self._traverse(key, i) for i in value]
    elif hasattr(value, '__dict__'):
      return self._traverse_dict(value.__dict__)
    else:
      return value

class BinaryTree(ToDictMixin):
  def __init__(self, value, left=None, right=None):
    self.value = value
    self.left  = left
    self.right = right


tree = BinaryTree(10, left=BinaryTree(7, right=BinaryTree(9)), right=BinaryTree(13, left=BinaryTree(11)))
print(tree.to_dict())

>>>
{'left': {'left': None,
          'right': {'left': None, 'right': None, 'value': 9},
          'value': 7},
 'right': {'left': {'left': None, 'right': None, 'value': 11},
           'right': None,
           'value': 13},
 'value': 10}


Item 27: Prefer Public Attributes Over Private Ones

Python 中 private attribute 的本质,其是就是改了个名字。
class MyFoo(MyBase):
  pass

class Foo(object):
  def __init__(self):
    self.__value = 10

f = Foo()
print f.__dict__
print f._Foo__value

>>>
{'_Foo__value': 10}
10


Item 28: Inherit from collections.abc for Custom Container Types

如果想完整实现 list, dict 的所有行为,可以继承自 collections.abc.* 下面的虚基类
它们会告诉你,还缺了啥接口。
# from collections import Sequence  # python 2
from collections.abc import Sequence

class BadType(Sequence):
  pass

foo = BadType()

>>>
TypeError: Can’t instantiate abstract class BadType with abstract methods __getitem__, __len__


class DummyType(Sequence):
  def __getitem__(self, i):
    return 10

  def __len__(self):
    return 1

d = DummyType()
print d[10]
print len(d)

>>>
10
1


4. Metaclasses and Attributes

Item 29: Use Plain Attributes Instead of Get and Set Methods

搞 getter/setter 对于 python 来说,有点冗余
class Foo(object):
  def __init__(self):
    self._value = 0

  def get_value(self):
    return self._value

  def set_value(self, v):
    self._value = v

f = Foo()
f.set_value(10)
print f.get_value()

>>>
10

其是直接访问属性就好
class Foo(object):
  def __init__(self):
    self.value = 0

f = Foo()
f.value = 10
print f.value

>>>
10

如果需要在 getter/setter 时,处理一些其它逻辑,可以用 @property
class Foo(object):
  def __init__(self):
    self._value = 0

  @property
  def value(self):
    return self._value

  @value.setter
  def value(self, v):
    self._value = v+1

f = Foo()
print f.value
f.value = 10
print f.value

>>>
0
11


Item 30: Consider @property Instead of Refactoring Attributes

对于 attribute 的访问,如果需要涉及到一些逻辑的,可以用 @property 包裹起来,后期重构代码需要改的地方比较少。
其是这就和 C++ 中的 setter/getter 一个道理。

class Foo(object):
  def __init__(self):
    self._value = 0

  @property
  def value(self):
    return self._value

  @value.setter
  def value(self, v):
    self._value = v

如果要修改 value.setter 的行为,之前所有调用 setter 的地方,不需要修改
  @value.setter
  def value(self, v):
    self._value = v + 1
  评论这张
 
阅读(205)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017