目录 | 上一节 (7.4 装饰器) | 下一节 (8 测试和调试)

7.5 装饰方法

本节讨论一些与方法定义结合使用的内置装饰器。

预定义的装饰器

在类定义中,有许多预定义的装饰器用于指定特殊类型的方法。

  1. class Foo:
  2. def bar(self,a):
  3. ...
  4. @staticmethod
  5. def spam(a):
  6. ...
  7. @classmethod
  8. def grok(cls,a):
  9. ...
  10. @property
  11. def name(self):
  12. ...

让我们逐个查看吧。

静态方法

@staticmethod 用于定义所谓的静态类方法( static class method,来自于 C++/Java)。静态方法是一个函数,这个函数是类的一部分,但不是在实例上进行操作。

  1. class Foo(object):
  2. @staticmethod
  3. def bar(x):
  4. print('x =', x)
  5. >>> Foo.bar(2)
  6. x=2
  7. >>>

静态方法有时用于实现类的内部支持代码,例如,用于帮助管理已创建的实例(内存管理,系统资源,持久化,锁等等)。有时也用于某些设计模式(这里暂不讨论)。

类方法

@classmethod 用于定义类方法(class methods)。类方法是一种将 对象而不是实例作为第一个参数的方法。

  1. class Foo:
  2. def bar(self):
  3. print(self)
  4. @classmethod
  5. def spam(cls):
  6. print(cls)
  7. >>> f = Foo()
  8. >>> f.bar()
  9. <__main__.Foo object at 0x971690> # The instance `f`
  10. >>> Foo.spam()
  11. <class '__main__.Foo'> # The class `Foo`
  12. >>>

类方法常用作定义替代构造函数(constructor)的工具。

  1. import time
  2. class Date:
  3. def __init__(self,year,month,day):
  4. self.year = year
  5. self.month = month
  6. self.day = day
  7. @classmethod
  8. def today(cls):
  9. # Notice how the class is passed as an argument
  10. tm = time.localtime()
  11. # And used to create a new instance
  12. return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)
  13. d = Date.today()

类方法可以和继承等特性一起使用以解决棘手的问题。

  1. class Date:
  2. ...
  3. @classmethod
  4. def today(cls):
  5. # Gets the correct class (e.g. `NewDate`)
  6. tm = time.localtime()
  7. return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)
  8. class NewDate(Date):
  9. ...
  10. d = NewDate.today()

练习

练习 7.11:实践中的类方法

report.pyportfolio.py 文件中, Portfolio 类的创建稍微有点混乱。例如,report.py 程序具有如下代码:

  1. def read_portfolio(filename, **opts):
  2. '''
  3. Read a stock portfolio file into a list of dictionaries with keys
  4. name, shares, and price.
  5. '''
  6. with open(filename) as lines:
  7. portdicts = fileparse.parse_csv(lines,
  8. select=['name','shares','price'],
  9. types=[str,int,float],
  10. **opts)
  11. portfolio = [ Stock(**d) for d in portdicts ]
  12. return Portfolio(portfolio)

portfolio.py 文件中定义的 Portfolio 具有一个奇怪的初始化:

  1. class Portfolio:
  2. def __init__(self, holdings):
  3. self.holdings = holdings
  4. ...

坦白说,因为代码分散在各文件中,所以责任链稍微有点混乱。如果 Portfolio 类应该包含 Stock 类的实例列表,那么你应该修改该类以使其更清晰。示例:

  1. # portfolio.py
  2. import stock
  3. class Portfolio:
  4. def __init__(self):
  5. self.holdings = []
  6. def append(self, holding):
  7. if not isinstance(holding, stock.Stock):
  8. raise TypeError('Expected a Stock instance')
  9. self.holdings.append(holding)
  10. ...

如果想要从 CSV 文件中读取投资组合数据,那么你也许应该为此创建一个类方法:

  1. # portfolio.py
  2. import fileparse
  3. import stock
  4. class Portfolio:
  5. def __init__(self):
  6. self.holdings = []
  7. def append(self, holding):
  8. if not isinstance(holding, stock.Stock):
  9. raise TypeError('Expected a Stock instance')
  10. self.holdings.append(holding)
  11. @classmethod
  12. def from_csv(cls, lines, **opts):
  13. self = cls()
  14. portdicts = fileparse.parse_csv(lines,
  15. select=['name','shares','price'],
  16. types=[str,int,float],
  17. **opts)
  18. for d in portdicts:
  19. self.append(stock.Stock(**d))
  20. return self

要使用新的 Portfolio 类,你可以这样编写代码:

  1. >>> from portfolio import Portfolio
  2. >>> with open('Data/portfolio.csv') as lines:
  3. ... port = Portfolio.from_csv(lines)
  4. ...
  5. >>>

请对 Portfolio 类进行修改,然后修改 report.py 的代码以使用类方法。

目录 | 上一节 (7.4 装饰器) | 下一节 (8 测试和调试)