# 变量保存的是对象的引用
- 每个对象都有标识、类型和值。标识指向内存空间地址、类型指向`__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中抽象基类和接口概念非常相近,但是它们还是有一些不同之处,例如,
- 接口需要被实现的子类完成接口中指定的所有方法,而抽象基类不是,抽象基类则没有这么严格的要求
- 接口需要所有方法都是抽象方法,而抽象基类中有抽象方法,也有自己实现的方法
正是因为抽象基类和接口的不同之处使得接口之所以称为接口、抽象基类之所以称为抽象基类。