简介
Python 的各种行为可以通过特殊的或所谓的“魔法”方法进行定制。本节将介绍这一概念。此外,还将讨论动态属性访问和绑定方法。
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):
...
调用方法是一个两步过程。
.
运算符()
运算符>>> 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)
修改你在 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
... 查看输出结果...
>>>
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中练习更多实验来提升你的技能。