# 变量保存的是对象的引用 - 每个对象都有标识、类型和值。标识指向内存空间地址、类型指向`__calss__`,值为值。 - 简单的赋值不创建副本 - 对`+=` 或 `*=` 所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新的对象;如果左边的变量绑定的是可变对象,会就地修改值 - 为现有的变量赋予新值不会修改之前绑定的变量。这叫重新绑定:现有变量绑定了其他对象。如果变量是之前那个对象的最后一个引用,对象会被当做垃圾回收。 - 函数的参数以别名的形式传递,这意味着,函数可能会修改通过参数传入的可变对象。这一行为无法避免,除非创建副本,或者使用不可变对象,例如传入元组而不是传入列表。 - 使用可变类型作为函数参数的默认值有危险,因为如果就地修改了参数值,默认值也就变了,这会影响以后使用默认值的调用。 # 对象表示形式 面向对象语言获取对象的字符串表示形式的标准方式。 - `repr()` :为开发者,返回对象字符串表示形式,`__repr__`特殊方法 - `str()` :为用户,返回对象字符串表示形式,`__str__`特殊方法 另外两种特殊方法:`__bytes__` , `__format__` 。`__bytes__` 方法与`__str__` 方法类似,`bytes()` 函数调用它获取其对象的字节序列的表示形式。`__format__` 方法会被内置的 `format()` 函数和 `str.format()` 方法调用,使用特殊的格式代码显示对象的字符串表示形式。 ```python 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))) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): return math.hypot(self.x, self.y) def angle(self): return math.atan2(self.y, self.x) def __bool__(self): return bool(abs(self)) def __format__(self, format_spec): if format_spec.endswith('p'): format_spec = format_spec[:-1] coords = (abs(self), self.angle()) outer_fmt = '<{}, {}>' else: coords = self outer_fmt = '({}, {})' components = (format(c, format_spec) for c in coords) return outer_fmt.format(*components) ``` # 对象可散列 可散列对象才可以作为字典的键或者集合的值,要想实现对象的可散列,那么首先要将对象的属性变为只读属性,任何直接操作对象属性都是非法的,其次是在对象类中实现`__hash__` 和 `__eq__` 方法,这样对象就变为一个可散列的对象。 ```python from array import array import math class Vector2d: typecode = 'd' def __init__(self, x, y): self.__x = float(x) self.__y = float(y) @property def x(self): return self.__x @property def y(self): return self.__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))) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(*memv) def __eq__(self, other): return tuple(self) == tuple(other) def __hash__(self): return hash(self.x) ^ hash(self.y) def __abs__(self): return math.hypot(self.x, self.y) def angle(self): return math.atan2(self.y, self.x) def __bool__(self): return bool(abs(self)) def __format__(self, format_spec): if format_spec.endswith('p'): format_spec = format_spec[:-1] coords = (abs(self), self.angle()) outer_fmt = '<{}, {}>' else: coords = self outer_fmt = '({}, {})' components = (format(c, format_spec) for c in coords) return outer_fmt.format(*components) ``` 一个任意维度的向量类。 ```python from array import array from functools import reduce import reprlib import math import numbers import operator import itertools class Vector: typecode = 'd' shortcut_name = "xyzt" def __init__(self, components): self._components = array(self.typecode, components) def __iter__(self): return iter(self._components) def __repr__(self): class_name = type(self).__name__ components = reprlib.repr(self._components) components = components[components.find('['):-1] return '{}({})'.format(class_name, components) def __str__(self): return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(self._components)) def __eq__(self, other): return len(self) == len(other) and all(a == b for a, b in zip(self, other)) def __hash__(self): hashes = (hash(x) for x in self._components) return reduce(operator.xor, hashes, 0) def __abs__(self): return math.sqrt(sum(x*x for x in self)) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) def __len__(self): return len(self._components) def __getitem__(self, index): if isinstance(index, slice): return type(self)(self._components[index]) elif isinstance(index, numbers.Integral): return self._components[index] else: msg = 'Vector indices must be integers' raise TypeError(msg) def __getattr__(self, name): cls = type(self) if len(name) == 1: pos = cls.shortcut_name.find(name) if 0 <= pos < len(self._components): return self._components[pos] msg = "{.__name__!r} object has no attribute {!r}" raise AttributeError(msg.format(cls, name)) def __setattr__(self, name, value): cls = type(self) if len(name) == 1: if name in cls.shortcut_name: error = "Readonly attribute {attr_name!r}" elif name.islower(): error = "Can't set attribute 'a' to 'z' in {cls_name!r}" else: error = "" if error: msg = error.format(cls_name=cls.__name__, attr_name=name) raise AttributeError(msg) super().__setattr__(name, value) def angle(self, n): r = math.sqrt(sum(x*x for x in self[n:])) a = math.atan2(r, self[n-1]) if (n == len(self) and (self[-1] < 0)): return math.pi * 2 - a else: return a def angles(self): return (self.angle(n) for n in range(1, len(self))) def __format__(self, format_spec=''): if format_spec.endswith('h'): format_spec = format_spec[:-1] coords = itertools.chain([abs(self)], self.angles()) outer_fmt = '<{}>' else: coords = self outer_fmt = '<{}>' components = (format(c, format_spec) for c in coords) return outer_fmt.format(', '.join(components)) ``` # 接口 接口(Interface)是对象公开方法的一种集合,在Java中通常以interface关键字来定义,接口虽然实现过程中和**类**相似,但是却具有不同的概念。具体而言,类与接口主要有以下几点不同之处: - 类实现了对象的属性和方法,而接口指定了使用该接口需要实现哪些方法 - 类可以实例化,而接口不可以被实例化 - 类中的方法可以是实现,接口中的方法都是抽象方法 > **抽象方法**:抽象方法的概念是父类中只负责声明该方法,但不具体实现这个方法,实现部分由继承该类的子类负责实现。 如果觉得上述描述有点云里雾里、对接口的概念依然不是非常清楚,不妨来试想一个场景:当你开发一个项目或者服务,你需要给上下游的组件提供接口,让别人来调用你的程序接口(Application Programming Interface,API),上下游组件该怎么样才能达到想要的目的和你的组件无缝衔接?需要通过按照你接口中规定的抽象方法来实现,例如,你提供一个访问网络请求的接口,你不会去实现host、username、password的注册和发送请求,这些需要调用的用户去实现,你只需要规定:“调用者必须实现指定方法才能实现调用”即可。 # 抽象基类 虽然Python中抽象基类和接口概念非常相近,但是它们还是有一些不同之处,例如, - 接口需要被实现的子类完成接口中指定的所有方法,而抽象基类不是,抽象基类则没有这么严格的要求 - 接口需要所有方法都是抽象方法,而抽象基类中有抽象方法,也有自己实现的方法 正是因为抽象基类和接口的不同之处使得接口之所以称为接口、抽象基类之所以称为抽象基类。