# ====================第八章:类与对象=========================
# --------------改变对象的字符串显示----------------------
# 要改变一个实例的字符串表示,可重新定义它的__str__() 和__repr__() 方法
class User:
def __init__(self, name, age):
self.name = name
self.age = age
# __repr__() 方法返回一个实例的代码表示形式,通常用来重新构造这个实例。
def __repr__(self):
return 'User({0.name}, {0.age})'.format(self)
# __str__() 方法将实例转换为一个字符串
def __str__(self):
return '({0.name}, {0.age})'.format(self)
# 如果__str__() 没有被定义,那么就会使用__repr__() 来代替输出
# --------------自定义字符串的格式化-----------------------
# 通过__format__() 函数和字符串方法使得一个对象能支持自定义的格式化
# ---------------让对象支持上下文管理-------------------------
# 为了让一个对象兼容with 语句,需要实现__enter__() 和__exit__() 方法
# 编写上下文管理器的主要原理是你的代码会放到with 语句块中执行
# 当出现with语句的时候,对象的__enter__() 方法被触发,
# 它返回的值(如果有的话) 会被赋值给as 声明的变量。
# 然后,with 语句块里面的代码开始执行。
# 最后,__exit__() 方法被触发进行清理工作。
"""
不管with 代码块中发生什么,上面的控制流都会执行完,就算代码块中发生了异
常也是一样的。事实上, exit () 方法的第三个参数包含了异常类型、异常值和追溯
信息(如果有的话)。exit () 方法能自己决定怎样利用这个异常信息,或者忽略它
并返回一个None 值。如果exit () 返回True ,那么异常会被清空,就好像什么都
没发生一样, with 语句后面的程序继续在正常执行。
"""
# --------------------在类中封装属性名-------------------------
"""
Python 程序员不去依赖语言特性去封装数据,而是通过遵循一定的属性和方法命
名规约来达到这个效果。
第一个约定是任何以单下划线开头的名字都应该是内部实现。
使用下划线开头的约定同样适用于模块名和模块级别函数。
使用双下划线开始会导致访问名称变成其他形式,这种属性通过继承是无法被覆盖的。
大多数而言,你应该让你的非公共名称以单下划线开头。
但是,如果你清楚你的代码会涉及到子类,
并且有些内部属性应该在子类中隐藏起来,
那么才考虑使用双下划线方案。
有时候你定义的一个变量和某个保留关键字冲突,这时候可以使用单下划线作为后缀
"""
# ---------------------创建一个可管理的属性------------------------------
# 给某个实例attribute 增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证
# 自定义某个属性的一种简单方法是将它定义为一个property
class Person:
def __init__(self, name, password):
self.name = name
self.password = password
@property
def name(self):
return self._name
# @property
# def password(self):
# return self._password
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('Excepted a string')
self._name = value
@name.deleter
def name(self):
raise AttributeError("Can't delete sttribute")
p = Person('qf','pass')
print(p.name)
# p.name = 16
# 还能在已存在的get 和set 方法基础上定义property
# class Person:
# def __init__(self, name):
# self.set_name(name)
#
# def get_name(self):
# return self._name
#
# def set_name(self, value):
# if not isinstance(value, str):
# raise TypeError('Excepted a string')
# self._name = value
#
# name = property(get_name, set_name)
#
# p = Person('qf')
# p.name = 13
"""
上述代码中有三个相关联的方法,这三个方法的名字都必须一样。第一个方法是一
个getter 函数,它使得first name 成为一个属性。其他两个方法给first name 属
性添加了setter 和deleter 函数。需要强调的是只有在first name 属性被创建后,
后面的两个装饰器@first name.setter 和@first name.deleter 才能被定义。
property 的一个关键特征是它看上去跟普通的attribute 没什么两样,但是访问它
的时候会自动触发getter 、setter 和deleter 方法。
一个property 属性其实就是一系列相关绑定方法的集合
"""
# --------------------调用父类方法---------------------------
# 在子类中调用父类的某个已经被覆盖的方法
# 为了调用父类(超类) 的一个方法,可以使用super() 函数
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super.__init__()
self.y = 1
"""
对于你定义的每一个类,Python 会计算出一个所谓的方法解析顺序(MRO) 列表。
这个MRO列表就是一个简单的所有基类的线性顺序表
为了实现继承,Python 会在MRO 列表上从左到右开始查找基类,
直到找到第一个匹配这个属性的类为止。
MRO 列表的构造是通过一个C3 线性化算法来实现的:合并所有父类的MRO列表并遵循如下三条准则:
• 子类会先于父类被检查
• 多个父类会根据它们在列表中的顺序被检查
• 如果对下一个类存在两个合法的选择,选择第一个父类
只是想简单的自定义某个类的单个属性访问的话就不用去写描述器了。
这种情况下使用property 技术会更加容易。
当程序中有很多重复代码的时候描述器就很有用了(比如你想在你代码的很多地方使用描述器
提供的功能或者将它作为一个函数库特性)。
"""
# -----------------------创建新的类或实例属性---------------------------------
# 创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功能
class IntegerU:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Excepted an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
"""
一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete) 的类,
分别为get () 、set () 和delete () 这三个特殊的方法。
这些方法接受一个实例作为输入,之后相应的操作实例底层的字典。
"""
# 为了使用一个描述器,需将这个描述器的实例作为类属性放到一个类的定义中
# 描述器的一个比较困惑的地方是它只能在类级别被定义,而不能为每个实例单独定义
# Example
class Point:
x = IntegerU('x')
y = IntegerU('y')
def __init__(self, x, y):
self.x = x
self.y = y
point = Point(2, 3)
print(point.x)
# point.x = 2.33
# ---------------------简化数据结构的初始化--------------------------
# 在一个基类中写一个公用的init () 函数
class Structure:
_fields = []
def __init__(self, *args, **kwargs):
if len(args) != len(self._fields):
raise TypeError('Excepted {} arguments'.format(len(self._fields)))
for name, value in zip(self._fields, args):
setattr(self, name, value)
extra_args = kwargs.keys() - self._fields
for name in extra_args:
setattr(self, name, kwargs.pop(name))
if kwargs:
raise TypeError('Duplicate values for {}'.format(','.join(kwargs)))
# ----------------------定义接口或者抽象基类---------------------------------
# 定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法
# 使用abc 模块可以很轻松的定义抽象基类
from abc import ABCMeta, abstractclassmethod
class IStream(metaclass=ABCMeta):
@abstractclassmethod
def read(self, maxbytes=-1):
pass
@abstractclassmethod
def write(self, data):
pass
"""
抽象类的一个特点是它不能直接被实例化
抽象类的目的就是让别的类继承它并实现特定的抽象方法
"""
class SocketStream(IStream):
def read(self, maxbytes=-1):
pass
def write(self, data):
pass
# -------------------在类中定义多个构造器-------------------------
# 为了实现多个构造器,需要使用到类方法
# 类方法的一个主要用途就是定义多个构造器。它接受一个class 作为第一个参数(cls)
import time
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
a = Date(2018, 8, 8)
b = Date.today()
print(b.year)
# 可以通过new() 方法创建一个未初始化的实例