9. 类的奇淫巧技
- 希望改变对象的字符串显示
如果希望改变对象实例的打印或者显示,则可以分别实现其__str__() 或者 __repr__()方法
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) |
__repr__() 返回一个实例的代码表示形式,常见的就是直接 p = Pair(3,4)
直接看p,看的就是repr
而__str__() 则是对应的str() 或者print() 函数中使用
当然我们也可以在print中将repr中的字符串打印出来,不过需要配合format
>>> print(‘p is {0!r}’.format(p))
通过 !r来指定输出 __repr__中内容
当然,如果我们没有实现__str__() ,则会使用__repr__()进行代替输出
def __repr__(self):
return ‘Pair({0.x!r}, {0.y!r})’.format(self) |
其中0就是代表着我们传入的self,0.x就是去取第0个参数中的x属性
- 对象中的format() 函数
如果希望实现自定义的格式化函数,则可以实现format函数
比如我们希望实现带有额外参数的格式化,则可以实现__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)
现在这个Date的实例支持格式化输出了
>>> d = Date(2012, 12, 21)
>>> format(d) ‘2012-12-21’ >>> format(d, ‘mdy’) ’12/21/2012′ >>> ‘The date is {:ymd}’.format(d) ‘The date is 2012-12-21’ |
__format__ 方法提供了一个钩子,供我们使用
- 支持上下文管理协议
如果希望对象支持上下文管理协议,也就是with语句
可以实现__enter__() __exit__()两个方法,这样就可以在with开启的时候调用enter方法,结束的时候调用exit方法
这里我们利用Socket,实现支持with语句的连接
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 |
其代表了一个网络连接,with的时候建立这个链接,在退出的时候关闭链接并设置为None
上面with代码块的基本使用,如果希望多级with的使用,则需要修改enter和exit函数支持
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.connections = [] def __enter__(self): sock = socket(self.family, self.type) sock.connect(self.address) self.connections.append(sock) return sock def __exit__(self, exc_ty, exc_val, tb): self.connections.pop().close() # Example use from functools import partial conn = LazyConnection((‘www.python.org’, 80)) with conn as s1: pass with conn as s2: pass # s1 and s2 are independent sockets |
在上面的代码,我们enter方法执行的时候会加入一个锁到栈里,exit则弹出一个链接用于关闭
- 利用__slots__属性来减少内存占用
__slots__属性可以极大的减少占用内存
class Date:
__slots__ = [‘year’, ‘month’, ‘day’] def __init__(self, year, month, day): self.year = year self.month = month self.day = day |
由于存在这样的slot数组,实例在构建之后就会以一个很小的数组来构建,而不是构建为一个字典样式的,从而减少了内存占用
不过需要注意,只要使用slots之后,就无法给实例添加新的属性了,只能使用__slots__定义的那些属性名
不过需要注意的是,slot不是用于防止用户给实例增加新的属性,而是用于进行内存优化工具
- 类中属性名的起名约定
对于类中的属性和方法,如果我们想要说明他是私有的,则应该是让这个属性和方法以单下划线声明
def _internal_method(self):
pass
当然,这个函数仍然可以在外部访问到,但是我们应该遵照约定,不去访问内部的属性和方法
除了单下划钱之外,还有着双下划线的命名方式
比如我们有一个属性
class B:
def __init__(self): self.__private = 0 def __private_method(self): pass |
这时候我们使用双下划线来声明属性,那么我们属性会被重命名,比如我这个属性名为—__private,这个类名为B,这个属性会被重命名为 _B__private
这样的私有属性因此无法被继承,如果我们有个C类被B类继承了,那么这个私有属性也会被叫做 _C_private
除此外,如果我们有一个变量和保留的关键字发生了冲突,那么可以在后面追加一个单下划钱作为后缀
比如 self.lambda_ = 1.0
那么总结下,
前缀单下划线用于非公共的函数或者方法,双下划线用于对子类隐藏内部属性
后缀单下划线用于和保留关键字进行区分
- Property注解的使用
之前可以有见过 @property注解,这个注解类似是一个getter函数,比如我们有一个first_name属性
class Person:
def __init__(self, first_name):
self._first_name = first_name
我们可以在一个函数上面增加property 注解
@property
def first_name(self):
return self._first_name
声明这个是一个属性,然后我们还可以给这个属性增加setter和deleter函数,从而进行校验,比如
@first_name.setter
def first_name(self, value): if not isinstance(value, str): raise TypeError(‘Expected a string’) self._first_name = value # Deleter function (optional) @first_name.deleter def first_name(self): raise AttributeError(“Can’t delete attribute”) |
我们利用不同的属性方法,来在不同动作的时候对属性进行校验
比如setter的时候我们要求value必须是str类型,在del的时候抛出异常
>>> a = Person(‘Guido’)
>>> a.first_name # Calls the getter
‘Guido’
>>> a.first_name = 42 # Calls the setter
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
File “prop.py”, line 14, in first_name
raise TypeError(‘Expected a string’)
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: can`t delete attribute
当然我们如果想要有更好的阅读性,则可以如下定义property,
class Person:
def __init__(self, first_name): self.set_first_name(first_name) # Getter function def get_first_name(self): return self._first_name # Setter function def set_first_name(self, value): if not isinstance(value, str): raise TypeError(‘Expected a string’) self._first_name = value # Deleter function (optional) def del_first_name(self): raise AttributeError(“Can’t delete attribute”) # Make a property from existing get/set methods name = property(get_first_name, set_first_name, del_first_name) |
我们指定了get set和del这个属性的方法
这里提一嘴,在java的编写规范里,会认为所有的访问都应该通过getter和setter
但是没有必要,会让代码变得极为臃肿,
而且Properties还提供了一种思路,一种动态计算attribute的方法,会以attribute调用的方式调用一个方法
import math
class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return math.pi * self.radius ** 2 @property def diameter(self): return self.radius * 2 @property def perimeter(self): return 2 * math.pi * self.radius |
这样我们可以直接通过 .area的方式调用对应的方法
- Python中的父类方法
如果我想要调用父类的一个方法,可以使用super()方法获取到
比如
class A:
def spam(self): print(‘A.spam’) class B(A): def spam(self): print(‘B.spam’) super().spam() # Call parent spam() |
以及常用于在init方法中对父类进行初始化
class A:
def __init__(self): self.x = 0 class B(A): def __init__(self): super().__init__() self.y = 1 |
这里在说明如何进行简单的父类方法使用后,我们可以看下在多继承或者多级继承的情况下的代码问题
class Base:
def __init__(self): print(‘Base.__init__’) class A(Base): def __init__(self): Base.__init__(self) print(‘A.__init__’) class B(Base): def __init__(self): Base.__init__(self) print(‘B.__init__’) class C(A,B): def __init__(self): A.__init__(self) B.__init__(self) print(‘C.__init__’) |
如果初始化一个C类的话,会发现Base.__init__方法调用两次
但是如果将A和B类中init调用的Base.init方法改为了super()之后,会发现只调用一次
这是由于Python会存在一个类的继承顺序表,这一点可以通过类的mro获取到
>>> C.__mro__
(<class ‘__main__.C’>, <class ‘__main__.A’>, <class ‘__main__.B’>,
<class ‘__main__.Base’>, <class ‘object’>)
>>>
为了实现继承,Python会在MRO列表上从左往右查找基类
那么调用super的时候,就会同意调用super() 最终遍历完成整个mro列表
由于mro存在一些特性,我们先列出来
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类
那么就存在一个问题,在super()的时候,可能调用的不是某个类的直接下一个父类
class A:
def spam(self):
print(‘A.spam’)
super().spam()
>>> class B:
… def spam(self):
… print(‘B.spam’)
…
>>> class C(A,B):
… pass
…
>>> c = C()
>>> c.spam()
A.spam
B.spam
>>>
那么在调用super()的时候,还是需要注意一些问题,需要确保参数签名是可兼容的
- 子类中扩展父类的property
我们看下如何在子类中去扩展父类的property,我们首先有一个类,定义了property
class Person:
def __init__(self, name): self.name = name # Getter function @property def name(self): return self._name # Setter function @name.setter def name(self, value): if not isinstance(value, str): raise TypeError(‘Expected a string’) self._name = value # Deleter function @name.deleter def name(self): raise AttributeError(“Can’t delete attribute”) |
然后我们有一个子类
那么我们可以定义相同的属性
class SubPerson(Person):
@property def name(self): print(‘Getting name’) return super().name @name.setter def name(self, value): print(‘Setting name to’, value) super(SubPerson, SubPerson).name.__set__(self, value) @name.deleter def name(self): print(‘Deleting name’) super(SubPerson, SubPerson).name.__delete__(self) |
这样我们就在子类中完成了调用父类的属性方法
如果只希望扩展property的某一个方法,那么可以如下书写
class SubPerson(Person):
@Person.name.getter def name(self): print(‘Getting name’) return super().name # 或者 class SubPerson(Person): @Person.name.setter def name(self, value): print(‘Setting name to’, value) super(SubPerson, SubPerson).name.__set__(self, value) |
我们分别是完全覆盖了属性的方法和单一方法
在完全覆盖中,我们使用了setter函数,在其中我们使用super(SubPerson,SubPerson).name.__set__(self,value)获取到了类变量进行访问
- 描述器
如果想要创建一个新的实例属性,可以定义一个描述器,其实就是一个三个核心属性访问操作 get set delete 的类
class Integer:
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(‘Expected an int’) instance.__dict__[self.name] = value def __delete__(self, instance): del instance.__dict__[self.name] |
在其中,我们接受一个实例作为输入,然后操作实例底层的字典
class Point:
x = Integer(‘x’) y = Integer(‘y’) def __init__(self, x, y): self.x = x self.y = y |
在其中,我们声明了x y为描述器,然后在init的时候就会直接包装为描述器
并且对于x,y的访问,都会被 get set delete方法捕获到
而实际上,我们将数据存储到了传入的实例中
那么我们还可以看下这个描述器的更为进阶的使用
比如我们希望做一个懒加载的属性,我们就可以通过一个描述器类做到
class lazyproperty:
def __init__(self, func): self.func = func def __get__(self, instance, cls): if instance is None: return self else: value = self.func(instance) setattr(instance, self.func.__name__, value) return value |
我们可以在如下的一个类中使用
class Circle:
def __init__(self, radius): self.radius = radius @lazyproperty def area(self): print(‘Computing area’) return math.pi * self.radius ** 2 @lazyproperty def perimeter(self): print(‘Computing perimeter’) return 2 * math.pi * self.radius |
我们在调用的时候只有第一次会触发计算,之后就不会触发了,这是利用的当一个类中没有这个属性的时候,会出发上面的描述器,但是当有了之后,就不会触发了
- 父类中的初始化函数
如果我们想要在父类中创建一个公共的__init__() 函数,这个父类中的init函数可以帮助子类设置对应的属性,可以如下的书写
class Structure1:
# Class variable that specifies expected fields _fields = [] def __init__(self, *args): if len(args) != len(self._fields): raise TypeError(‘Expected {} arguments’.format(len(self._fields))) # Set the arguments for name, value in zip(self._fields, args): setattr(self, name, value) |
这时候如果我们有子类继承这个父类,那么我们只需要在子类中声明field数组即可
class Stock(Structure1):
_fields = [‘name’, ‘shares’, ‘price’] |
当然我们还可以配合设置关键字参数
class Structure2:
_fields = [] def __init__(self, *args, **kwargs): if len(args) > len(self._fields): raise TypeError(‘Expected {} arguments’.format(len(self._fields))) # Set all of the positional arguments for name, value in zip(self._fields, args): setattr(self, name, value) # Set the remaining keyword arguments,这里设置了初始length for name in self._fields[len(args):]: setattr(self, name, kwargs.pop(name)) # Check for any remaining unknown arguments if kwargs: raise TypeError(‘Invalid argument(s): {}’.format(‘,’.join(kwargs))) |
我们利用了setattr()函数来设置属性值,这个函数在使用的时候,如果我们的属性是一个描述器,也能够很好的配合使用
- 定义接口或者抽象基类
如果想要定义接口或者抽象类,确保某些特定的方法在子类实现了
使用abc模块可以做到,其中包含ABCMeta和abstract method
如果我们有一个类继承了ABCMeta,那么他就不能被实例化
如果他的类上面加上了 abstractmethod 注解,那么就要求别的类继承他的同时实现这些方法
class IStream(metaclass=ABCMeta):
@abstractmethod def read(self, maxbytes=-1): pass @abstractmethod def write(self, data): pass class SocketStream(IStream): def read(self, maxbytes=-1): pass def write(self, data): pass |
标准库中提供了很多抽象基类,比如collections中定义了很多的序列抽象基类
Numbers定义了很多和数字对象相关的基类,io库只能够定义了很多IO操作相关的基类
我们可以利用isinstance函数来进行更为通用的类型检查
if isinstance(x, collections.Sequence):
- 描述器实现类型约束
如果希望在某些属性上进行限制数据结构,最好使用描述器
我们先声明几个描述器
class Descriptor:
def __init__(self, name=None, **opts): self.name = name for key, value in opts.items(): setattr(self, key, value) def __set__(self, instance, value): instance.__dict__[self.name] = value class Typed(Descriptor): expected_type = type(None) def __set__(self, instance, value): if not isinstance(value, self.expected_type): raise TypeError(‘expected ‘ + str(self.expected_type)) super().__set__(instance, value) class Unsigned(Descriptor): def __set__(self, instance, value): if value < 0: raise ValueError(‘Expected >= 0’) super().__set__(instance, value) class MaxSized(Descriptor): def __init__(self, name=None, **opts): if ‘size’ not in opts: raise TypeError(‘missing size option’) super().__init__(name, **opts) def __set__(self, instance, value): if len(value) >= self.size: raise ValueError(‘size must be < ‘ + str(self.size)) super().__set__(instance, value) |
然后我们就可以创建实例在实际中使用
class Integer(Typed):
expected_type = int class SizedString(String, MaxSized): pass class Stock: # Specify constraints name = SizedString(‘name’, size=8) price= Integer(‘price’) def __init__(self, name, shares, price): self.name = name self.price = price |
或者通过类装饰器完成类型限制
def check_attributes(**kwargs):
def decorate(cls): for key, value in kwargs.items(): if isinstance(value, Descriptor): value.name = key setattr(cls, key, value) else: setattr(cls, key, value(key)) return cls return decorate # Example @check_attributes(name=SizedString(size=8), shares=UnsignedInteger, price=UnsignedFloat) class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price |
或者是使用元类进行使用
class checkedmeta(type):
def __new__(cls, clsname, bases, methods): # Attach attribute names to the descriptors for key, value in methods.items(): if isinstance(value, Descriptor): value.name = key return type.__new__(cls, clsname, bases, methods) # Example class Stock2(metaclass=checkedmeta): name = SizedString(size=8) shares = UnsignedInteger() price = UnsignedFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price |
我们在上面利用继承的方式,以Descriptor为基类,实现了获取数据类型限制的set方法,最后甚至可以使用装饰器的方法进行书写
def Typed(expected_type, cls=None):
if cls is None: return lambda cls: Typed(expected_type, cls) super_set = cls.__set__ def __set__(self, instance, value): if not isinstance(value, expected_type): raise TypeError(‘expected ‘ + str(expected_type)) super_set(self, instance, value) cls.__set__ = __set__ return cls @Typed(int) class Integer(Descriptor): pass |
- 实现自定义容器
如果希望实现自定义的容器,那么可以使用collections中的抽象基类为底,继承并实现其中的方法即可
class SortedItems(collections.Sequence):
def __init__(self, initial=None): self._items = sorted(initial) if initial is not None else [] # Required sequence methods def __getitem__(self, index): return self._items[index] def __len__(self): return len(self._items) # Method for adding an item in the right location def add(self, item): bisect.insort(self._items, item) items = SortedItems([5, 1, 3]) print(list(items)) print(items[0], items[-1]) items.add(2) print(list(items)) |
上面的SortItem支持序列的所有方法,包括索引 迭代 ,甚至切片
- 属性的代理访问
如果希望某个实例的属性访问到另一个实例中,也就是编程模式中的代理模式,则可以用包裹的方式,配合__getattr__() __setattr__(),比如如下代码
class Proxy:
def __init__(self, obj): self._obj = obj # Delegate attribute lookup to internal obj def __getattr__(self, name): print(‘getattr:’, name) return getattr(self._obj, name) # Delegate attribute assignment def __setattr__(self, name, value): if name.startswith(‘_’): super().__setattr__(name, value) else: print(‘setattr:’, name, value) setattr(self._obj, name, value) # Delegate attribute deletion def __delattr__(self, name): if name.startswith(‘_’): super().__delattr__(name) else: print(‘delattr:’, name) delattr(self._obj, name) |
- Classmethod模拟构造器
我们直接上代码,看下如何使用classmethod模拟构造器
class Date:
“””方法一:使用类方法””” # Primary constructor def __init__(self, year, month, day): self.year = year self.month = month self.day = day # Alternate constructor @classmethod def today(cls): t = time.localtime() return cls(t.tm_year, t.tm_mon, t.tm_mday) |
如果调用today函数,那么就直接返回当前的Date对象
Classmethod接受一个class作为第一个参数,我们也利用这个cls创建了一个实例
- __new__方法
用来直接创建一个类的实例,之后再设置属性
from time import localtime
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def today(cls): d = cls.__new__(cls) t = localtime() d.year = t.tm_year d.month = t.tm_mon d.day = t.tm_mday return d |
- 使用Mixins扩展类功能
我们可以通过扩展 def __getitem__(self, key):
def __setitem__(self, key, value):
def __delitem__(self, key):
来做到,比如我们给出一个实例
class SetOnceMappingMixin:
”’ Only allow a key to be set once. ”’ __slots__ = () def __setitem__(self, key, value): if key in self: raise KeyError(str(key) + ‘ already set’) return super().__setitem__(key, value) |
我们可以如下的使用
class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
pass d = SetOnceDefaultDict(list) d[‘x’].append(2) d[‘x’].append(3) # d[‘x’] = 23 # KeyError: ‘x already set’ |
这样我们就可以增强一些类的功能
- 实现状态模式
如果希望在python中实现状态模式,可以考虑定义一个状态对象
# Connection state base class
class ConnectionState: @staticmethod def read(conn): raise NotImplementedError() @staticmethod def write(conn, data): raise NotImplementedError() @staticmethod def open(conn): raise NotImplementedError() @staticmethod def close(conn): raise NotImplementedError() # Implementation of different states class ClosedConnectionState(ConnectionState): @staticmethod def read(conn): raise RuntimeError(‘Not open’) @staticmethod def write(conn, data): raise RuntimeError(‘Not open’) @staticmethod def open(conn): conn.new_state(OpenConnectionState) @staticmethod def close(conn): raise RuntimeError(‘Already closed’) class OpenConnectionState(ConnectionState): @+ def read(conn): print(‘reading’) @staticmethod def write(conn, data): print(‘writing’) @staticmethod def open(conn): raise RuntimeError(‘Already open’) @staticmethod def close(conn): conn.new_state(ClosedConnectionState) |
我们定义了多个状态,然后定义一个connection对象,在其中使用connectionstate
class Connection1:
“””新方案——对每个状态定义一个类””” def __init__(self): self.new_state(ClosedConnectionState) def new_state(self, newstate): self._state = newstate # Delegate to the state class def read(self): return self._state.read(self) def write(self, data): return self._state.write(self, data) def open(self): return self._state.open(self) def close(self): return self._state.close(self) |
我们声明了一些staticmethod,并且将实例属性存储在connection之中,当出现问题的时候,在外面抛出NotImplementError
- 通过字符串来调用对象方法
如果我们知道了对象拥有某个方法,以及方法名,我们可以通过getattr()来调用
class Point:
def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return ‘Point({!r:},{!r:})’.format(self.x, self.y) def distance(self, x, y): return math.hypot(self.x – x, self.y – y) p = Point(2, 3) d = getattr(p, ‘distance’)(0, 0) # Calls p.distance(0, 0) |
或者使用operator.methodcaller()
比如
import operator
operator.methodcaller(‘distance’, 0, 0)(p) |
我们可以先通过getattr()找到了这个属性,然后以函数的方式去调用
Operator.methodcaller() 调用一个可调用的对象,并且提供所有必要的参数即可
>>> d = operator.methodcaller(‘distance’, 0, 0)
>>> d(p) 5.0 |
- 访问者模式
如果我们需要针对不同类型的对象实现不同类型的操作,有两种方式,一种是通过接口,另一种是通过实现访问者模式,这个访问者模式可以如下定义实现
首先我们有一些自定义类
class Node:
pass class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass class Sub(BinaryOperator): pass class Mul(BinaryOperator): pass class Div(BinaryOperator): pass class Negate(UnaryOperator): pass class Number(Node): def __init__(self, value): self.value = value |
然后我们利用这些类去构建嵌套数据结构
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) |
之后定义一个类去实现上面不同类对应的动作
class NodeVisitor:
def visit(self, node): methname = ‘visit_’ + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError(‘No {} method’.format(‘visit_’ + type(node).__name__)) def visit_Number(self, node): return node.value def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right) def visit_Sub(self, node): return self.visit(node.left) – self.visit(node.right) def visit_Mul(self, node): return self.visit(node.left) * self.visit(node.right) def visit_Div(self, node): return self.visit(node.left) / self.visit(node.right) def visit_Negate(self, node): return -node.operand |
这样我们利用了getattr 来获取相对应的方法,利用递归来遍历所有的节点
需要注意的是,在使用的时候需要注意递归层次深度
如果我们不希望使用递归的方式来获取,则可以考虑使用生成器的逻辑
class Evaluator(NodeVisitor):
def visit_Number(self, node): return node.value def visit_Add(self, node): yield (yield node.left) + (yield node.right) def visit_Sub(self, node): yield (yield node.left) – (yield node.right) def visit_Mul(self, node): yield (yield node.left) * (yield node.right) def visit_Div(self, node): yield (yield node.left) / (yield node.right) def visit_Negate(self, node): yield – (yield node.operand) |
>>> a = Number(0)
>>> for n in range(1,100000): … a = Add(a, Number(n)) … >>> e = Evaluator() >>> e.visit(a) 4999950000 |
我们利用了yield语句,利用yield语句,我们会返回一个数据并且会暂时挂起,从而代替了递归,避免了递归层次过多导致的爆栈
基本可以这样理解
value = self.visit(node.left)
换成yield语句就是
value = yield node.left
将node.left会返回给最外层的visit方法,之后visit方法会调用这个节点对应的visit_Name() 方法
- Python中的循环引用的gc方式
这里我们首先说明,一般情况的python的gc是基于简单的引用计数,当一个对象的引用数变成0的时候才会立刻删除掉,对于循环引用这个条件用不成立
这里我们给一个循环引用的实例,一个Node类,父节点保留指针指向子节点,子节点知道自己的父节点是谁
class Data:
def __del__(self): print(‘Data.__del__’) # Node class involving a cycle class Node: def __init__(self): self.data = Data() self.parent = None self.children = [] def add_child(self, child): self.children.append(child) child.parent = self >>> a = Data() >>> del a # Immediately deleted Data.__del__ >>> a = Node() >>> del a # Immediately deleted Data.__del__ >>> a = Node() >>> a.add_child(Node()) >>> del a # Not deleted (no message) >>> |
对于这种循环引用,一般的del和垃圾回收都不好使,虽然python中提供了其他的垃圾回收器来针对循环引用,但是无法确定什么时候会触发,只能手动的触发
Import gc
gc.collect
这里可以考虑使用弱引用
import weakref
class Node: def __init__(self, value): self.value = value self._parent = None self.children = [] def __repr__(self): return ‘Node({!r:})’.format(self.value) # property that manages the parent as a weak-reference @property def parent(self): return None if self._parent is None else self._parent() @parent.setter def parent(self, node): self._parent = weakref.ref(node) def add_child(self, child): self.children.append(child) child.parent = self |
这时候我们想要访问parent,就可以看这个属性中的使用
使用了 self._parent(),这就是weakre的使用方式,本质上传回了一个对象指针,使用函数的方式进行调用,如果不存在就返回一个None
- 类支持比较操作
如果希望一个类的实例支持标准运算,但是又懒得去实现其他那一大丢的特殊方法
Python类对每个比较都实现一个特殊方法支持,比如为了支持>=,就需要定义一个 __ge__() 方法,但是如果要实现所有的比较方法就有点烦人了
我们可以利用装饰器 functools.total_ordering来简化这个处理,装饰一个类,并且定义一个__eq__() 方法,外加其他方法 lt le gt ge中的一个即可,就会自动填充其他的比较方法
比如我们有如下类
class Room:
def __init__(self, name, length, width): self.name = name self.length = length self.width = width self.square_feet = self.length * self.width @total_ordering class House: def __init__(self, name, style): self.name = name self.style = style self.rooms = list() @property def living_space_footage(self): return sum(r.square_feet for r in self.rooms) def add_room(self, room): self.rooms.append(room) def __str__(self): return ‘{}: {} square foot {}’.format(self.name, self.living_space_footage, self.style) def __eq__(self, other): return self.living_space_footage == other.living_space_footage def __lt__(self, other): return self.living_space_footage < other.living_space_footage |
我们定义了eq和lt方法,其他的比较方法他都能支持
其实本质上就是total_ordering装饰器会自动通过lambda的方式构建其他的特殊方法
class House:
def __eq__(self, other): pass def __lt__(self, other): pass # Methods created by @total_ordering __le__ = lambda self, other: self < other or self == other __gt__ = lambda self, other: not (self < other or self == other) __ge__ = lambda self, other: not (self < other) __ne__ = lambda self, other: not self == other |
- 工厂类方法
如果我们希望保证某个对象是单例的,我们想要直接获取到他的缓存引用
就比如logging模块,我们希望相同名称的logger实例只有一个
那么我们可以设置一个工厂函数,或者通过classmethod实现
# The class in question
class Spam: def __init__(self, name): self.name = name # Caching support import weakref _spam_cache = weakref.WeakValueDictionary() def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s |