date: 2019-01-21
抽象是隐藏多余细节的艺术。在面向对象的概念中,抽象的直接表现形式通常为类(Class
)。虽然Python
是解释型语言,但是它从设计之初开始就是一门面向对象的语言。
面向对象的抽象程度比函数要高,因为一个类既包含数据,又包含操作数据的方法。数据封装、继承和多态是面向对象的三大特点。
类(Class
)和实例(Instance
)是面向对象最基本,也是最重要的概念。
INDEX == True
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
@classmethod
def new_student(cls, name, score):
student = cls(name, score)
return student
@staticmethod
def set_index():
return INDEX == False
@staticmethod
def get_score():
return Foo.print_score()
关键字class
后面跟着类名,类名通常是大写字母开头,紧接着是object
,表示该类从哪个类继承下来。object
是所有类的基类。
使用__init__(self, ...)
来初始化对象,在创建类实例时候会自动调用。类方法中第一个参数一定是self
,指代实例本身。诸如self.name
和self.score
是这个类的属性(Property)。print_score(self)
称为对象的方法(Method)。
tom = Student('Tom Simpson', 15);tom.print_score()
这样定义类实例与调用类方法。
- 实例方法
上述Student().print_score()
就是实例方法。实例方法只能被实例调用。
- 类方法
上述Student().new_student()
是类方法,可以进行修改初始化的类等作用。
- 静态方法
上述Student().set_index(), Student().get_score()
是静态方法,能够进行环境变量的修改和调用类方法。
动态语言允许在程序运行过程动态地给类(Class
)加上功能。
class Student(object):
pass
def set_score(self, score):
self.score = score
Student.set_score = set_score
给类(Class
)添加方法,对所有实例均可调用;对某个实例(Instance
)添加方法,对另一个实例并不起作用。
但是当需要限制实例的属性时候,不能让其这样动态添加,就需要使用__slots__
属性。对于主要是用来当成简单的数据结构的类而言,你可以通过给类添加__slots__
属性来极大的减少实例所占的内存。
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
当你定义__slots__
后,Python
就会为实例使用一种更加紧凑的内部表示。 实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。 在__slots__
中列出的属性名在内部被映射到这个数组的指定小标上。 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__
中定义的那些属性名。
当试图绑定不存在于__slots__
中的属性时,就会得到AttributeError
的错误。
在类中,存在属性和方法。从以上的类定义来看,外部代码可以自由地修改一个实例的属性,这对类或实例是不安全的。
Python不依赖语言特性来封装私有数据,而是通过遵循一定的属性和方法命名规约来达到这个效果。例如在属性的名称前加上两个下划线__
,实例的变量就变成一个私有变量(Private),只有内部可以访问,外部不能访问。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
>>> >>> bart._Student__name
'Bart Simpson'
双下划线开头的实例变量仍然可以通过_Student__name
来访问__name
变量。
Python 还使用单下划线_
开头的属性和方法定义为内部实现。
在类定义中定义私有变量时候,为了能够管理私有变量,可以在类中定义setter
和getter
函数。
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
>>> s = Student()
>>> s.set_score(60) # ok!
>>> s.get_score()
60
>>> s.set_score(9999)
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
但是上述的调用略显复杂,没有直接用属性简单。并且还需要对实例进行除了访问和修改之外的其他逻辑处理(比如类型检测和合法性验证),上面的方法就不大适用了。
class Student(object):
def __init__(self, score):
self.score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@score.deleter
def score(self):
raise AttributeError("Can't delete attribute")
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
>>> del s.score
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can`t delete attribute
>>>
上述代码中有三个相关联的方法,这三个方法的名字都必须一样。 第一个方法是一个getter
函数,它使得score
成为一个属性。 其他两个方法给score
属性添加了setter
和deleter
函数。 需要强调的是只有在score
属性被创建后, 后面的两个装饰器@score.setter
和@score.deleter
才能被定义。
在实现一个property
的时候,底层数据会存储在self._score
中。在__init__()
方法中设置了self.score
,就自动调用setter
方法,这个方法里面会进行参数的检查,否则就是直接访问self._score
了。
还能在已存在的get
和set
方法基础上定义property
class Student(object):
def __init__(self, score):
self.score = score
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
def del_score(self):
raise AttributeError("Can't delete attribute")
name = property(get_score, set_score, del_score)
为了调用父类的方法,可以使用super()
函数。
class A:
def spam(self):
print('A.spam')
class B(A):
def spam(self):
print('B.spam')
super().spam()
super()
常见用法就是调用__init__()
方法来确保父类被正确初始化。
class Base:
def __init__(self):
print('Base.__init__')
class A(Base):
def __init__(self):
super().__init__()
print('A.__init__')
class B(Base):
def __init__(self):
super().__init__()
print('B.__init__')
class C(A,B):
def __init__(self):
super().__init__()
print('C.__init__')
>>> c = C()
Base.__init__
B.__init__
A.__init__
C.__init__
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>
从上面的代码中,可以看出Python处理继承关系时候,会计算出一个所谓的方法解析顺序(MRO)列表。这个列表就是上述 C.__mro__
。其遵循三条原则:
-
子类会先于父类被检查
-
多个父类会根据它们在列表中的顺序被检查
-
如果对下一个类存在两个合法的选择,选择第一个父类
看下面这段代码
>>> class A:
>>> def spam(self):
>>> print('A.spam')
>>> super().spam()
>>> a = A()
>>> a.spam()
A.spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in spam
AttributeError: 'super' object has no attribute 'spam'
>>>
>>> class B:
... def spam(self):
... print('B.spam')
...
>>> class C(A,B):
... pass
...
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>
在类A定义完后直接调用这个类A会报错,而在多继承情况下,类A的super().spam()
确没有报错,这个跟类C的MRO列表有关。类A的super().spam()
实际调用的是与其完全无关的类B的spam()
方法。
要改变一个实例的字符串表示,可重新定义它的__str__(self)
和__repr__(self)
方法。
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x!s}, {0.y!s})'.format(self)
为了自定义字符串的格式化,我们需要在类上面定义__format__()
方法。
_formats = {
'ymd' : '{d.year}-{d.month}-{d.day}',
'mdy' : '{d.month}/{d.day}/{d.year}',
'dmy' : '{d.day}/{d.month}/{d.year}'
}
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
为了让一个对象兼容 with 语句,你需要实现__enter__(self)
和__exit__(self)
方法。
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb):
self.sock.close()
self.sock = None
from functools import partial
conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
# conn.__enter__() executes: connection open
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
# conn.__exit__() executes: connection closed
- 元类
Python
处处皆对象,Python
中有“名字空间”的概念,这类似于其他编程语言中的作用域。在Python
中,名字空间底层由一个dict
实现,变量名就是字典中的键,而变量引用的对象就是字典中键对应的值。
Python
中最为特殊的是,类型也是一个对象,包括Object
在内的所有类型,都是对象实例。
类型的对象的类型叫做元类,元类默认是一个type
类对象,或者某个继承自type
的类对象。最后,type
类对象的类型是type
自身。
正常情况下,我们经常使用object
来派生一个类,即:
class Foo(object):
pass
实际上,Python
最终是调用了type
来创建类,即我们常用来判断对象类型的type
来创建类。
它的创建方式如下:
Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象
其中:
-
第一个参数是类名;
-
第二个参数是元祖类型,代表所有的父类;
-
第三个参数是字典,定义属性和方法。
元类(metaclass
)是用来创建类(对象)的可调用对象,即控制类的创建行为。
如下举例:
- 定义一个元类
class PrefixMetaclass(type): # 从 type 继承,表示其是用来创建类的
def __new__(cls, name, bases, attrs):
'''
cls:当前准备创建的类
name:类的名字
bases:类的父类集合
attrs:类的属性和方法,是一个字典
'''
# 给所有属性和方法前面加上前缀 my_
_attrs = (('my_' + name, value) for name, value in attrs.items())
_attrs = dict((name, value) for name, value in _attrs) # 转化为字典
_attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
return type.__new__(cls, name, bases, _attrs) # 返回创建后的类
- 定制类
class Foo(metaclass=PrefixMetaclass):
name = 'foo'
def bar(self):
print('bar')
- 使用
>>> f = Foo()
>>> f.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'name'
>>> f.my_name
'foo'
>>> f.bar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> f.my_bar()
bar
>>> f.echo('hello metaclass')
'hello metaclass'
可以看到,Foo
原来的属性 name
已经变成了 my_name
,而方法 bar
也变成了 my_bar
。
type
与Object
的关系:
在Python
的世界中,Object
是父子关系的顶端,所有的数据类型的父类都是它;type
是类型实例关系的顶端,所有对象都是它的实例的。
它们两个的关系可以这样描述:Object
是type
的实例(type
是Object
的父类),type
是Object
的子类,因此type
也是type
自身的实例。
- 抽象基类
抽象基类主要定义了基本类和最基本的抽象方法,可以为子类定义共有的接口,且不需要具体实现。
抽象基类提供了逻辑和实现解耦的能力。
能以最精简的方式展示代码之间的逻辑关系,让模块之间依赖清晰简单;
针对抽象基类,只需要关注其方法和描述,而不需要考虑过多的其他逻辑。
Python
使用 abc
模块实现抽象基类:
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self, maxbytes=-1):
pass
@abstractmethod
def write(self, data):
pass
它的一个特点就是不能直接被实例化。抽象基类目的就是让别的类继承它并实现特定的抽象方法。
还有实现特定的接口,例如检测类型是否为特定类型的接口:
def serialize(obj, stream):
if not isinstance(stream, IStream):
raise TypeError('Expected an IStream')
pass
- 抽象类和接口类的区别和联系
-
抽象类: 规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。
-
接口类:与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则。
-
区别和关联:
-
- 接口是抽象类的变体,接口中所有的方法都是抽象的,而抽象类中可以有非抽象方法,抽象类是声明方法的存在而不去实现它的类;
-
- 接口可以继承,抽象类不行;
-
- 接口定义方法,没有实现的代码,而抽象类可以实现部分方法;
-
- 接口中基本数据类型为
static
而抽象类不是。
- 接口中基本数据类型为
Python类使用两个下划线定义的函数统称为魔术方法。这些方法有特殊的用途,有的不需要我们自己定义,有的则通过一些简单的定义可以实现比较神奇的功能。
下面只做基本的汇总,展开叙述后续再补充。
- 基本定制
C.__init__(self[, arg1, ...]) 构造器(带一些可选的参数)
C.__new__(self[, arg1, ...]) 构造器(带一些可选的参数);通常用在设置不变数据类型的子类
C.__del__(self) 解构器
C.__str__(self) 可打印的字符输出;内建str()及print 语句
C.__repr__(self) 运行时的字符串输出;内建repr() ‘‘ 和 操作符
C.__unicode__(self)b Unicode 字符串输出;内建unicode()
C.__call__(self, *args) 表示可调用的实例
C.__nonzero__(self) 为object 定义False 值;内建bool() (从2.2 版开始)
C.__len__(self) “ ” 长度(可用于类);内建len()
- 对象值比较
C.__cmp__(self, obj) 对象比较;内建cmp()
C.__lt__(self, obj) and 小于/小于或等于;对应<及<=操作符
C.__le__(self,obj)
C.__gt__(self, obj) and 大于/大于或等于;对应>及>=操作符
C.__ge__(self,obj)
C.__eq__(self, obj) and 等于/不等于;对应==,!=及<>操作符
C.__ne__(self,obj)
- 属性操作
C.__getattr__(self, attr) 获取属性;内建getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val) 设置属性
C.__delattr__(self, attr) 删除属性
C.__getattribute__(self, attr) 获取属性;内建getattr();总是被调用
C.__get__(self, attr) (描述符)获取属性
C.__set__(self, attr, val) (描述符)设置属性
C.__delete__(self, attr) (描述符)删除属性
- 数值计算
C.__*add__(self, obj) 加;+操作符
C.__*sub__(self, obj) 减;-操作符
C.__*mul__(self, obj) 乘;*操作符
C.__*div__(self, obj) 除;/操作符
C.__*truediv__(self, obj) True 除;/操作符
C.__*floordiv__(self, obj) Floor 除;//操作符
C.__*mod__(self, obj) 取模/取余;%操作符
C.__*divmod__(self, obj) 除和取模;内建divmod()
C.__*pow__(self, obj[, mod]) 乘幂;内建pow();**操作符
C.__*lshift__(self, obj) 左移位;<<操作符
C.__*rshift__(self, obj) 右移;>>操作符
C.__*and__(self, obj) 按位与;&操作符
C.__*or__(self, obj) 按位或;|操作符
C.__*xor__(self, obj) 按位与或;^操作符
C.__neg__(self) 一元负
C.__pos__(self) 一元正
C.__abs__(self) 绝对值;内建abs()
C.__invert__(self) 按位求反;~操作符
C.__complex__(self, com) 转为complex(复数);内建complex()
C.__int__(self) 转为int;内建int()
C.__long__(self) 转为long;内建long()
C.__float__(self) 转为float;内建float()
C.__oct__(self) 八进制表示;内建oct()
C.__hex__(self) 十六进制表示;内建hex()
C.__coerce__(self, num) 压缩成同样的数值类型;内建coerce()
C.__index__(self)g 在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等
- 序列操作
C.__len__(self) 序列中项的数目
C.__getitem__(self, ind) 得到单个序列元素
C.__setitem__(self, ind,val) 设置单个序列元素
C.__delitem__(self, ind) 删除单个序列元素
C.__getslice__(self, ind1,ind2) 得到序列片断
C.__setslice__(self, i1, i2,val) 设置序列片断
C.__delslice__(self, ind1,ind2) 删除序列片断
C.__contains__(self, val) f 测试序列成员;内建in 关键字
C.__*add__(self,obj) 串连;+操作符
C.__*mul__(self,obj) 重复;*操作符
C.__iter__(self) 创建迭代类;内建iter()
- 映射操作
C.__len__(self) mapping 中的项的数目
C.__hash__(self) 散列(hash)函数值
C.__getitem__(self,key) 得到给定键(key)的值
C.__setitem__(self,key,val) 设置给定键(key)的值
C.__delitem__(self,key) 删除给定键(key)的值
C.__missing__(self,key) 给定键如果不存在字典中,则提供一个默认值