本文是《fluent python》第九章的学习笔记。
对象表示形式
获取对象字符串表示形式的两个函数:
repr()
面向开发者
str()
面向用户
需要分别实现__repr__
和__str__
两个方法。
一个例子:向量
下面看一个数学中vector的例子:
from array import array
import math
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __iter__(self):
return (i for i in (self.x, self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + \
bytes(array(self.typecode, self)))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
备选构造函数即通过bytes构造Vector2d,这时可以用classmethod
这个装饰器实现,接下来介绍一下classmethod
的用法:
参考资料:Python 中的 classmethod 和 staticmethod 有什么具体用途?
- 构造前交互
classmethod的第一个参数必须是类本身,习惯上我们使用cls:
@classmethod
def foo(cls, *args):
pass
一个典型的场景如下:
class Plugin(object):
"""
Legacy plugin (v1)
"""
def __init__(self, api_interface):
self._api = api_interface
def callback(self, event_type, event_value):
"""
Event callback
"""
pass # Do nothing
class PluginV2(object):
"""
v2 plugin
"""
def __init__(self, api_version, api_interface):
self._api = api_interface
def callback(self, event_type, event_value):
"""
Event callback
"""
pass
@classmethod
def capabilities(cls, supported_versions):
if "2.0" in supported_versions:
return {"api_version": "2.0", "register_events": ["request_start", "request_end"]}
else:
raise IncompatibleVersionException("API version 2.0 is not supported")
作者:灵剑
链接:https://www.zhihu.com/question/20021164/answer/537385841
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这里我们想兼容旧版的plugin,所以需要在构造之前知道应该用哪个class来构造实例。
- 特殊构造函数(备选构造函数)
当一个对象进行序列化成数据,再从数据中恢复成对象的时候,一般将序列化的方法设定为实例方法,反序列化的方式设定为classmethod,原因是反序列化的时候并不需要首先构造一个对象的实例。
- 为其他的classmethod提供钩子
因为classmethod只能调用classmethod。
而关于classmethod
与staticmethod
之间的区别,目前来讲,基本不存在必须要用staticmethod
而不能用classmethod
的情况。
格式化显示
本节主要讲了format(my_obj, format_spec)
和str.format()
。
如果继承自object的类没有实现__format__
方法,那么会默认返回字符串str(my_object)
。
def __format__(self, fmt_spec=''):
components = (format(c, fmt_spec) for c in self)
return '({}, {})'.format(*components)
上述代码可以支持不同的fmt_spec,比如:
v = Vector2D(3.0, 4.0)
format(v)
# '(3.0, 4.0)'
format(v, '.2f')
# 没实现__format__之前会报错
不难想到,如果想format自己实现的类结构,目前已存在的格式规范微语言(Format Specification Mini-Language)肯定是不够用的,因此自定义自己的微语言(format_spec
)是比较实用的。
Vector2d中的__format__
方法,目前的实现可以支持极坐标表示:
def angel(self):
return math.atan2(self.y, self.x)
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('p'):
# 极坐标的末尾用p表示
fmt_spec = fmt_spec[:-1]
coords = (abs(self), self.angel())
outer_fmt = '<{}, {}>'
else:
coords = self
outer_fmt = '({}, {})'
components = (format(c, fmt_spec) for c in coords)
return outer_fmt.format(*components)
可哈希的Vector2d
实现__hash__
方法和__eq__
方法即可。
python 中的private和protected
很不幸,python中并没有private和protected关键字,实际上并不能阻止程序员在类外访问私有变量。那么在python中一个类的私有变量是怎么构造的呢?有两种比较有代表性的方法:
名称改写
在命名私有变量时开头加上两个下划线,python中的名称改写机制(name mangling)会把名称进行改写:
class Vector2d:
self.__x = None
self.__y = None
# ......
这样,__x
的名称会被改写成_Vector2d__x
。这时如果想改写__x
是不行的,避免了在类外部改写私有变量的情况。
但是这样的缺点是,如果这个程序员知道这个机制,那么他可以在类外部直接改写_Vector2d__x
。
约定
一些python程序员习惯上把私有变量名字前加上一个下划线,并承诺不在类外部访问这些变量。
看起来很草率hhh。
(我更倾向与第二种,因为这两种方法实际上都没有在根本上解决在类外部访问私有变量的问题,所以我更喜欢相信程序员之间的约定hhh)
覆盖类属性
感觉就是告诉我们尽可能减少硬编码hhh。