定制 Python 的动态行为

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

Python 的各种行为可以通过特殊的或所谓的“魔法”方法进行定制。本节将介绍这一概念。此外,还将讨论动态属性访问和绑定方法。

简介

类可以定义特殊方法。这些方法对 Python 解释器具有特殊意义。它们总是以 __ 开头和结尾。例如 __init__

class Stock(object):
    def __init__(self):
     ...
    def __repr__(self):
     ...

有几十种特殊方法,但我们只看几个具体的例子。

字符串转换的特殊方法

对象有两种字符串表示形式。

>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>

str() 函数用于创建美观的可打印输出:

>>> str(d)
'2012-12-21'
>>>

repr() 函数用于为程序员创建更详细的表示形式。

>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>

str()repr() 这两个函数在类中使用一对特殊方法来生成要显示的字符串。

class Date(object):
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    ## 与 `str()` 一起使用
    def __str__(self):
        return f'{self.year}-{self.month}-{self.day}'

    ## 与 `repr()` 一起使用
    def __repr__(self):
        return f'Date({self.year},{self.month},{self.day})'

注意:__repr__() 的约定是返回一个字符串,当将其输入到 eval() 中时,将重新创建底层对象。如果无法做到这一点,则使用某种易于阅读的表示形式代替。

数学运算的特殊方法

数学运算符涉及对以下方法的调用。

a + b       a.__add__(b)
a - b       a.__sub__(b)
a * b       a.__mul__(b)
a / b       a.__truediv__(b)
a // b      a.__floordiv__(b)
a % b       a.__mod__(b)
a << b      a.__lshift__(b)
a >> b      a.__rshift__(b)
a & b       a.__and__(b)
a | b       a.__or__(b)
a ^ b       a.__xor__(b)
a ** b      a.__pow__(b)
-a          a.__neg__()
~a          a.__invert__()
abs(a)      a.__abs__()

元素访问的特殊方法

这些是用于实现容器的方法。

len(x)      x.__len__()
x[a]        x.__getitem__(a)
x[a] = v    x.__setitem__(a,v)
del x[a]    x.__delitem__(a)

你可以在你的类中使用它们。

class Sequence:
    def __len__(self):
     ...
    def __getitem__(self,a):
     ...
    def __setitem__(self,a,v):
     ...
    def __delitem__(self,a):
     ...

方法调用

调用方法是一个两步过程。

  1. 查找:使用 . 运算符
  2. 方法调用:使用 () 运算符
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost  ## 查找
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()         ## 方法调用
49010.0
>>>

绑定方法

尚未通过函数调用运算符 () 调用的方法称为 绑定方法。它作用于其所属的实例。

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>

绑定方法常常是粗心导致的不易察觉的错误来源。例如:

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>

或者是难以调试的隐蔽行为。

f = open(filename, 'w')
...
f.close     ## 哎呀,什么都没做。`f` 仍然是打开的状态。

在这两种情况下,错误都是由于忘记加上尾随的括号导致的。例如,s.cost()f.close()

属性访问

存在另一种访问、操作和管理属性的方式。

getattr(obj, 'name')          ## 与 obj.name 相同
setattr(obj, 'name', value)   ## 与 obj.name = value 相同
delattr(obj, 'name')          ## 与 del obj.name 相同
hasattr(obj, 'name')          ## 测试属性是否存在

示例:

if hasattr(obj, 'x'):
    x = getattr(obj, 'x'):
else:
    x = None

*注意:getattr() 还有一个有用的默认值 *arg*。

x = getattr(obj, 'x', None)

练习4.9:改进对象打印输出

修改你在 stock.py 中定义的 Stock 对象,使 __repr__() 方法产生更有用的输出。例如:

>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>

看看在你进行这些更改后,读取股票投资组合并查看结果列表时会发生什么。例如:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... 查看输出结果...
>>>
✨ 查看解决方案并练习

练习4.10:使用getattr()的示例

getattr() 是读取属性的另一种机制。它可用于编写极其灵活的代码。首先,试试这个示例:

>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name','shares']
>>> for colname in columns:
        print(colname, '=', getattr(s, colname))

name = GOOG
shares = 100
>>>

仔细观察,输出数据完全由 columns 变量中列出的属性名决定。

tableformat.py 文件中,采用这个思路并将其扩展为一个通用函数 print_table(),该函数用于打印一个表格,展示任意对象列表中用户指定的属性。与之前的 print_report() 函数一样,print_table() 也应该接受一个 TableFormatter 实例来控制输出格式。它的工作方式如下:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
      name     shares
---------- ----------
        AA        100
       IBM         50
       CAT        150
      MSFT        200
        GE         95
      MSFT         50
       IBM        100

>>> print_table(portfolio, ['name','shares', 'price'], formatter)
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44
>>>
✨ 查看解决方案并练习

总结

恭喜你!你已经完成了“特殊方法”实验。你可以在LabEx中练习更多实验来提升你的技能。