9. 类的奇淫巧技

  1. 希望改变对象的字符串显示

如果希望改变对象实例的打印或者显示,则可以分别实现其__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属性

  1. 对象中的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__ 方法提供了一个钩子,供我们使用

  1. 支持上下文管理协议

如果希望对象支持上下文管理协议,也就是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则弹出一个链接用于关闭

  1. 利用__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不是用于防止用户给实例增加新的属性,而是用于进行内存优化工具

  1. 类中属性名的起名约定

对于类中的属性和方法,如果我们想要说明他是私有的,则应该是让这个属性和方法以单下划线声明

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

那么总结下,

前缀单下划线用于非公共的函数或者方法,双下划线用于对子类隐藏内部属性

后缀单下划线用于和保留关键字进行区分

  1. 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的方式调用对应的方法

  1. 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()的时候,还是需要注意一些问题,需要确保参数签名是可兼容的

  1. 子类中扩展父类的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)获取到了类变量进行访问

  1. 描述器

如果想要创建一个新的实例属性,可以定义一个描述器,其实就是一个三个核心属性访问操作 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

我们在调用的时候只有第一次会触发计算,之后就不会触发了,这是利用的当一个类中没有这个属性的时候,会出发上面的描述器,但是当有了之后,就不会触发了

  1. 父类中的初始化函数

如果我们想要在父类中创建一个公共的__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()函数来设置属性值,这个函数在使用的时候,如果我们的属性是一个描述器,也能够很好的配合使用

  1. 定义接口或者抽象基类

如果想要定义接口或者抽象类,确保某些特定的方法在子类实现了

使用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):

  1. 描述器实现类型约束

如果希望在某些属性上进行限制数据结构,最好使用描述器

我们先声明几个描述器

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

  1. 实现自定义容器

如果希望实现自定义的容器,那么可以使用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支持序列的所有方法,包括索引 迭代 ,甚至切片

  1. 属性的代理访问

如果希望某个实例的属性访问到另一个实例中,也就是编程模式中的代理模式,则可以用包裹的方式,配合__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)

  1. 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创建了一个实例

  1. __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

  1. 使用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’

这样我们就可以增强一些类的功能

  1. 实现状态模式

如果希望在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

  1. 通过字符串来调用对象方法

如果我们知道了对象拥有某个方法,以及方法名,我们可以通过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

  1. 访问者模式

如果我们需要针对不同类型的对象实现不同类型的操作,有两种方式,一种是通过接口,另一种是通过实现访问者模式,这个访问者模式可以如下定义实现

首先我们有一些自定义类

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() 方法

  1. 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

  1. 类支持比较操作

如果希望一个类的实例支持标准运算,但是又懒得去实现其他那一大丢的特殊方法

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

  1. 工厂类方法

如果我们希望保证某个对象是单例的,我们想要直接获取到他的缓存引用

就比如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

发表评论

邮箱地址不会被公开。 必填项已用*标注