""" --*第三章 语法最佳实践--类级别以上*-- """
'''3.1 子类化内置类型'''
# object内置类型是所有内置类型的共同祖先
# 也是所有没有显式指定父类的用户自定义类的共同祖先
# 当需要实现与某个内置类型具有相似行为的类时,最好的方法是将这个内置类型子类化
# 定义一个类 是dict类型的子类 新类大部分行为与普通的dict相同
# 但不允许多个键对应相同的值 若重复会引起ValueError的子类错误
class DistinctError(ValueError):
"""若添加重复值,引发这个错误"""
class distinctdict(dict):
"""不接收重复值的字典"""
def __setitem__(self, key, value):
if value in self.values():
if (
(key in self and self[key] != value) or
key not in self
):
raise DistinctError(
"""this value already exist for different key"""
)
super().__setitem__(key, value)
my = distinctdict()
my['key'] = 'value'
print(my) # {'key': 'value'}
# my['other_key'] = 'value'
# print(my)
# 报错
# in __setitem__
# """this value already exist for different key"""
# __main__.DistinctError: this value already exist for different key
# 许多类都是对内置类型的部分实现 它们作为子类速度更快
# list类型用来管理序列,一个类需要在内部处理序列,可以对list进行子类化
class Folder(list):
def __init__(self, name):
self.name = name
def dir(self, nesting=0):
offset = " " * nesting
print('%s%s/' % (offset, self.name))
for element in self:
if hasattr(element, 'dir'):
element.dir(nesting + 1)
else:
print('%s %s' % (offset, element))
tree = Folder('project')
tree.append('README.md')
print(tree.dir())
# project/
# README.md
src = Folder('src')
src.append('script.py')
tree.append(src)
print(tree.dir())
# project/
# README.md
# src/
# script.py
'''3.2 访问超类中的方法'''
# super是一个内置类 可用于访问属于某个对象的超类的属性
class Mama: # 旧的写法
def says(self):
print('do your homework')
class Sister(Mama):
def says(self):
Mama.says(self) # 调用超类的says()方法,并将self作为参数传入
print('and clean your bedroom')
print(Sister().says())
# 输出
# do your homework
# and clean your bedroom
# super()用法
class Sister(Mama):
def says(self):
super().says()
print('and clean your room')
print(Sister().says())
# 输出同上
# super第二个参数可选,若只提供第一个参数,
# 那么super返回的是一个未绑定的类型
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
def __repr__(self):
return "Pizza with " + " and ".join(self.toppings)
@classmethod
def recommend(cls):
"""推荐任意馅料(toppings)的某种披萨。"""
return cls(['spam', 'ham', 'eggs'])
class VikingPizza(Pizza):
@classmethod
def recommend(cls):
"""推荐与super相同的内容,但多加了午餐肉(spam)。"""
recommended = super(VikingPizza).recommend()
recommended.toppings += ['spam'] * 5
return recommended
# 使用super易犯的错误
# 1.混用super与显示类调用
class A:
def __init__(self):
print("A", end=" ")
super().__init__()
class B:
def __init__(self):
print("B", end=" ")
super().__init__()
class C(A, B):
def __init__(self):
print('C', end=" ")
A.__init__(self)
B.__init__(self)
print(C())
# 输出
# C A B B <__main__.C object at 0x0000000002769438>
# C类使用__init__方法调用他的基类,使得B类被调用两次
# C的实例调用了A.__init__(self),使得super(A,self).__init()调用了B.__init__()方法
# 2.不同种类的参数
# 使用super的另一个问题是初始化过程中参数传递
# 子类要与父类的签名相匹配
'''3.3 高级属性访问模式'''
# 1.描述符:允许你自定义在引用一个对象的属性时应该完成的事情
# 它是一个类,定义了另一个类的属性访问方式。一个类可以将属性委托给另一个类
# 描述符类基于3个特殊方法,这三个方法组成了描述符协议
# __set__:在设置属性时将调用这一方法,setter
# __get__:在读取属性时将调用这一方法,getter
# __delete__:对属性调用del时将调用这一方法
# 实现__get__()和__set__()的描述符被称为数据描述符
# 实现__get__()称为非数据描述符
# 属性查找时,这个协议的实际上由对象的特殊方法__getattribute__()调用。
# 每次通过点号或者getattr函数调用来执行这样的查找时,都会隐式的调用__getattribute__()
# 按下列顺序查找该属性
# [1]验证该属性是否为实例的类对象的描述符
# [2]如果不是,就查看该属性是否能在实例对象的__dict__中找到
# [3]最后,查看该属性是否为实例的类对象的非数据描述符
# 数据描述符优于__dict__查找,而__dict__查找优先于非数据描述符
class RevealAccess(object):
"""一个数据描述符,正常设定值并返回值,同时打印出记录访问信息
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
m = MyClass()
print(m.x) # 输出 Retrieving var "x" 10
m.x = 20 # 输出 Updating var "x"
# 若一个类的某个属性有数据描述符,那么每次查找这个属性时
# 都会调用描述符的__get__()方法并返回他的值
# 每次对这个属性赋值时都会调用__set__()
# 函数对象是非数据描述符
# 如果没有__dict__优先于非数据描述符,将不可能在运行时在已经构建好的实例上动态覆写特定的方法
# 现实例子-延迟求值属性
# 将类属性的初始化延迟到被实例访问时。
# 如果这些属性的初始化依赖全局应用上下文的话,这一点可能有用
# 另一使用场景是初始化代价很大,但在导入类的时候不知道是否会用到这个属性
class IninOnAccess:
def __init__(self, klass, *args, **kwargs):
self.klass = klass
self.args = args
self.kwargs = kwargs
self._initialized = None
def __get__(self, instance, owner):
if self._initialized is None:
print('initialized!')
self._initialized = self.klass(*self.args,
**self.kwargs)
else:
print('cached')
return self._initialized
class MyClass:
lazily_initialized = IninonAccess(list, 'argument')
m= MyClass()
print(m.lazily_initialized)
# initialized!
# ['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
print(m.lazily_initialized)
# cached
# ['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
# 2.property:提供一个内置的描述符类型,将一个属性链接到一组方法上(把方法变成属性)
# 接受4个可选参数:fget、fset、fdel、doc。
# 最后一个参数可以用来定义一个链接到属性的docstring,就像一个方法一样。
# eg:控制方法两种,一种是直接访问保存两个顶点的属性,另一种是利用width和height
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
def _width_get(self):
return self.x2 - self.x1
def _width_set(self, value):
self.x2 = self.x1 + value
def _height_get(self):
return self.y2 - self.y1
def _height_set(self, value):
self.y2 = self.y1 + value
width = property(
_width_get, _width_set,
doc="rectangle width measured from left"
)
height = property(
_height_get, _height_set,
doc="rectangle height measured from top"
)
def __repr__(self):
return "{}({}, {}, {}, {})".format(
self.__class__.__name__,
self.x1, self.y1, self.x2, self.y2
)
rectangle = Rectangle(10, 10, 25, 34)
print(rectangle.width, rectangle.height)
print(rectangle)
# 在使用继承过程中必须要注意,创建的属性是利用当前类的方法实时创建
# 不会使用派生类中覆写的方法
# 例如:
class MetricRectangle(Rectangle):
def _width_get(self):
return "{} meters".format(self.x2 - self.x1)
print(Rectangle(0, 0, 100, 100).width)
# 输出
# 100
# 解决这一问题,要在派生类中覆写整个property
class MetricRectangle(Rectangle):
def _width_get(self):
return "{} meters".format(self.x2 - self.x1)
width = property(_width_get, Rectangle.width.fset)
print(MetricRectangle(0, 0, 100, 100).width)
# 输出
# 100 meters
# 创建property最佳语法是使用property作为装饰器,减少类内部方法签名的数量
# 并提高代码的可读性和可维护性
class Rectangle:
def __init__(self, x1, y1, x2, y2):
self.x1, self.y1 = x1, y1
self.x2, self.y2 = x2, y2
@property
def width(self):
"""rectangle height measured from left"""
return self.x2 - self.x1
@width.setter
def width(self, value):
self.x2 = self.x1 + value
@property
def height(self):
"""rectangle height measured from top"""
return self.y2 - self.y1
@height.setter
def height(self, value):
self.y2 = self.y1 + value
print(Rectangle(0, 0, 100, 100).width)
# 输出 100