前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 自定义类(特殊方法)

python 自定义类(特殊方法)

作者头像
Michael阿明
发布2021-09-06 11:22:17
5660
发布2021-09-06 11:22:17
举报
文章被收录于专栏:Michael阿明学习之路

文章目录

learn from 《流畅的python》

代码语言:javascript
复制
from array import array
import math


class Vector2D:
    typecode = 'd'  # 类属性

    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

    @classmethod  # 装饰器, 函数不需要传入 self 参数,需要cls 传入类本身
    # classmethod 最常见的用途是 定义备选构造方法
    # @staticmethod 就是定义在类中的普通函数
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(*memv)  # 构造类对象

    @staticmethod
    def hello():
        print("hello world!")

    def __iter__(self):  # 可迭代对象,才能拆包 x,y = my_vector
        return (i for i in (self.x, self.y))  # 生成器表达式

    def __repr__(self):
        class_name = type(self).__name__
        return '{}({!r},{!r})'.format(class_name, *self)
        # {!r} 获取 *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))

1. 对象表示形式

  • repr() 返回便于 开发者 理解的对象字符串形式,须实现__repr()__
  • str() 返回便于 用户 理解的对象字符串形式,须实现__str()__
  • __format()__ 会被内置的 format(), str.format() 调用
代码语言:javascript
复制
>>> brl = 1/2.43
>>> brl
0.4115226337448559
>>> format(brl, "0.4f")
'0.4115'
>>> format(brl, ".4f")
'0.4115'
>>> "1 BRL = {rate:0.2f} USD".format(rate=brl)
'1 BRL = 0.41 USD'

https://docs.python.org/3/library/string.html#formatspec

代码语言:javascript
复制
v1 = Vector2D(315687,4)
print("test str {0.x:5.3e}".format(v1))
# test str 3.157e+05

{ 变量 :格式说明符 } 包含两部分

代码语言:javascript
复制
>>> format(8, 'b') # 二进制
'1000'
>>> format(1/3, '.2%') # %百分比
'33.33%' 
  • 如果类没有定义 __format__ 方法,从 object 继承的方法会返回 str(my_object),调用 __str__()
代码语言:javascript
复制
print(format(v1)) # (315687.0, 4.0)
代码语言:javascript
复制
print(format(v1, '.3f')) # TypeError: unsupported format string passed to Vector2D.__format__

为了解决该问题,在类中添加方法:

代码语言:javascript
复制
    def __format__(self, fmt_spec=""):
        components = (format(c, fmt_spec) for c in self)
        # 使用内置的 format 把格式应用到各个分量上,构成一个可迭代的字符串
        return "({}, {})".format(*components) # 格式化字符串
代码语言:javascript
复制
print(format(v1, '.3f')) # (315687.000, 4.000)
  • 自定义极坐标表示
代码语言:javascript
复制
    def angle(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.angle())
            outer_fmt = "<{}, {}>"
        else:
            coords = self
            outer_fmt = "({}, {})"
        components = (format(c, fmt_spec) for c in coords)
        return outer_fmt.format(*components)
代码语言:javascript
复制
print(format(Vector2D(1, 1), 'p'))
print(format(Vector2D(1, 1), '.3ep'))
print(format(Vector2D(1, 1), '0.5fp'))
print(format(Vector2D(1, 1), '0.2f'))

<1.4142135623730951, 0.7853981633974483>
<1.414e+00, 7.854e-01>
<1.41421, 0.78540>
(1.00, 1.00)

2. 可散列的类

代码语言:javascript
复制
hash(v1) # TypeError: unhashable type: 'Vector2D'

为了可以散列,需要实现__hash__(), __eq__()

代码语言:javascript
复制
    def __init__(self, x, y):
        self.__x = float(x)
        self.__y = float(y)

    @property
    def x(self):
        return self.__x

    @property  #  @property 装饰器把读值方法标记为特性
    def y(self):
        return self.__y
代码语言:javascript
复制
v1.__x = 100  # 值不可以变更
print(v1) # (315687.0, 4.0)
# v1.x = 100 # AttributeError: can't set attribute
  • 添加 __hash__()
代码语言:javascript
复制
    def __hash__(self):
        return hash(self.x) ^ hash(self.y)
代码语言:javascript
复制
v2 = Vector2D(10, 20)
v3 = Vector2D(10, 20)
print(hash(v1))  # 315683
print(hash(v2))  # 30
print(v2 is v3)  # False
print(hash(v3))  # 30
print(set([v1, v2, v3])) # {Vector2D(315687.0,4.0), Vector2D(10.0,20.0)}

3. 私有属性的利弊

  • 如果子类跟父类有相同的属性,子类会覆盖父类
  • __ or _开头的属性将会被存在 实例的 __dict__ 属性内,且加上前缀 _类名
代码语言:javascript
复制
print(v1.__dict__)
# {'_Vector2D__x': 315687.0, '_Vector2D__y': 4.0, '__x': 100}
print(v1._Vector2D__x)  # 315687.0

名称改写是一种安全措施,不能保证万无一失:它的目的是避免意外访问,不能防止故意做错事

Python 解释器不会对使用 单个下划线 的属性名做特殊处理,不过这是很多 Python 程序员严格遵守的约定,他们不会在类外部访问这种属性。

代码语言:javascript
复制
print(v1._Vector2D__x)  # 315687.0
v1._Vector2D__x = 100
print(v1._Vector2D__x)  # 100
print(v1)  # (100., 4.0)

不能真正的实现 私有和不可变

4. __slots__ 类属性节省空间

代码语言:javascript
复制
class Vector2d: 
	__slots__ = ('__x', '__y')

等号右侧可以是可迭代的对象,里面存储所有实例属性的名称的字符串,从而避免使用消耗内存的 __dict__ 属性

  • 在类中定义 __slots__ 属性之后,实例不能再有 __slots__ 中所列名称之外的其他属性
  • 为了 让对象支持弱引用,必须有 __weakref__属性。用户定义的类中 默认就有 __weakref__ 属性。 可是,如果类中定义了 __slots__ 属性,而且想把实例作为 弱引用 的目标,那么要把 __weakref__ 添加到 __slots__
  • 一般不要把 __dict__ 加入到 __slots__
  • 每个子类都要定义 __slots__ 属性,因为解释器会忽略继承的 __slots__ 属性

5. 覆盖类属性

代码语言:javascript
复制
print(v1.typecode)  # d
print(v2.typecode)  # d
print(bytes(v1))  # b'd\x00\x00\x00\x00\x00\x00Y@\x00\x00\x00\x00\x00\x00\x10@'
print(Vector2D.typecode)  # d

v1.typecode = 'f'
print(v1.typecode)  # f
print(bytes(v1))  # b'f\x00\x00\xc8B\x00\x00\x80@'
print(Vector2D.typecode)  # d
v1.typecode = 'd'
print(v1.typecode)  # d

Vector2D.typecode = 'f'
print(Vector2D.typecode)  # f
print(v1.typecode)  # d
print(v2.typecode)  # f
  • typecode 是类属性,一旦实例对象赋值 typecode后,实际是创建了新的实例属性
  • 如果为不存在的 实例属性 赋值,会 新建 实例属性,类属性不受影响,但是实例属性会遮盖同名类属性

还可以订制类的数据属性:

代码语言:javascript
复制
class anOtherVec(Vector2D):
    typecode = 'f' # 只是修改子类的数据类型

v4 = anOtherVec(3,4)
print(bytes(v4))
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/07/24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 1. 对象表示形式
  • 2. 可散列的类
  • 3. 私有属性的利弊
  • 4. __slots__ 类属性节省空间
  • 5. 覆盖类属性
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档