符合python风格的类设计

本文是《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 有什么具体用途?

  1. 构造前交互

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来构造实例。

  1. 特殊构造函数(备选构造函数)

当一个对象进行序列化成数据,再从数据中恢复成对象的时候,一般将序列化的方法设定为实例方法,反序列化的方式设定为classmethod,原因是反序列化的时候并不需要首先构造一个对象的实例。

  1. 为其他的classmethod提供钩子

因为classmethod只能调用classmethod。

而关于classmethodstaticmethod之间的区别,目前来讲,基本不存在必须要用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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值