Python面向对象编程

python-version: 3.9

一、简介

面向对象思想请看: [ XJTUSE ]JAVA语言基础知识——第一章 面向对象程序设计思想

属性attributes

属性是与对象绑定的一组数据,可以只读,只写,或者读写,使用时不加括号,例如:

f = open("new_file", 'w')
# 显示模式属性
print("模式属性:", f.mode)
print("是否关闭属性:", f.closed)
模式属性: w
是否关闭属性: False

方法 method

方法是与属性绑定的一组函数,需要使用括号,作用于对象本身:

f.write('Hi.\n')
f.seek(0)
f.write('Hola!\n')
f.close()

二、使用面向对象思想对森林火灾建模

import matplotlib.pyplot as plt
import numpy as np

对森林建模

class Forest(object):
    def __init__(self, size=(150, 150), p_sapling=0.0025, p_lightning=5.e-6, name=None):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.forest_fires = np.zeros(self.size, dtype=bool)
        self.p_sapling = p_sapling
        self.p_lightning = p_lightning
        if name is not None:
            self.name = name
        else:
            self.name = self.__class__.__name__

    @property
    def num_cells(self):
        return self.size[0] * self.size[1]

    @property
    def tree_fraction(self):
        return self.trees.sum() / float(self.num_cells)

    @property
    def fire_fraction(self):
        return self.forest_fires.sum() / float(self.num_cells)

    def advance_one_step(self):
        self.grow_trees()
        self.start_fires()
        self.burn_trees()

    def grow_trees(self):
        growth_sites = self._rand_bool(self.p_sapling)
        self.trees[growth_sites] = True

    def start_fires(self):
        lightning_strikes = (self._rand_bool(self.p_lightning) & 
            self.trees)
        self.forest_fires[lightning_strikes] = True

    def burn_trees(self):
        fires = np.zeros((self.size[0] + 2, self.size[1] + 2), dtype=bool)
        fires[1:-1, 1:-1] = self.forest_fires
        north = fires[:-2, 1:-1]
        south = fires[2:, 1:-1]
        east = fires[1:-1, :-2]
        west = fires[1:-1, 2:]
        new_fires = (north | south | east | west) & self.trees
        self.trees[self.forest_fires] = False
        self.forest_fires = new_fires

    def _rand_bool(self, p):
        return np.random.uniform(size=self.trees.shape) < p

创建一个新的森林类对象:

forest = Forest()
print("树的状态:\n",forest.trees)
print("燃烧的状态: \n",forest.forest_fires)
树的状态:
 [[False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]
 ...
 [False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]]
燃烧的状态: 
 [[False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]
 ...
 [False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]]
# 使用 matshow 进行可视化:
plt.matshow(forest.trees, cmap=plt.cm.Greens)

plt.show()

Python面向对象编程-LMLPHP

模拟森林生长和火灾的过程

# 经过一段时间
forest.advance_one_step()
plt.matshow(forest.trees, cmap=plt.cm.Greens)
plt.show()

Python面向对象编程-LMLPHP

# 循环很长时间:
for i in range(500):
    forest.advance_one_step()
plt.matshow(forest.trees, cmap=plt.cm.Greens)
print(forest.tree_fraction)
0.21697777777777777

Python面向对象编程-LMLPHP

# 迭代更长时间
forest = Forest()
tree_fractions = []
for i in range(5000):
    forest.advance_one_step()
    tree_fractions.append(forest.tree_fraction)
fig = plt.figure()
ax0 = fig.add_subplot(1,2,1)
ax0.matshow(forest.trees, cmap=plt.cm.Greens)
ax1 = fig.add_subplot(1,2,2)
ax1.plot(tree_fractions)

plt.show()

Python面向对象编程-LMLPHP

三、对象

在 Python 中,几乎所有的东西都是对象。

整数是对象:

a = 257
print(type(a))
print("a内存标识:",id(a))
b = a
print("b和a是同一个对象?",b is a)
<class 'int'>
a内存标识: 2352921776272
b和a是同一个对象? True

函数是对象:

def foo():
    print("hi")
print(type(foo))
print(id(foo))
# type 函数本身也是对象:
print(type(type))
print(id(type))
<class 'function'>
2352922378096
<class 'type'>
140727138868320

只有一些保留的关键词不是对象:

id(if)
  Cell In [21], line 1
    id(if)
       ^
SyntaxError: invalid syntax

四、定义class

基本形式

class 定义如下:

class ClassName(ParentClass):
    """class docstring"""
    def method(self):
        return
  • class 关键词在最前面
  • ClassName 通常采用 CamelCase 记法
  • 括号中的 ParentClass 用来表示继承关系
  • 冒号不能缺少
  • """""" 中的内容表示 docstring,可以省略
  • 方法定义与函数定义十分类似,不过多了一个 self 参数表示这个对象本身
  • class 中的方法要进行缩进
class Forest(object):
    """ Forest can grow trees which eventually die."""
    pass

其中 object 是最基本的类型。

查看帮助:

np.info(Forest)
 Forest()

Forest can grow trees which eventually die.
forest = Forest()
forest
<__main__.Forest at 0x223d5165520>

添加方法和属性

可以直接添加属性(有更好的替代方式):

forest.trees = np.zeros((150, 150), dtype=bool)
forest.trees
array([[False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       ...,
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False]])
# 再新建一个实例则没有这个属性
forest2 = Forest()
forest2.trees
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In [28], line 3
      1 # 再新建一个实例则没有这个属性
      2 forest2 = Forest()
----> 3 forest2.trees


AttributeError: 'Forest' object has no attribute 'trees'

添加方法时,默认第一个参数是对象本身,一般为 self,可能用到也可能用不到,然后才是其他的参数:

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def grow(self):
        print("the tree is growing")

    def number(self, num=1):
        if num == 1:
            print('there is 1 tree.')
        else:
            print('there are', num, 'trees.')

forest = Forest()

forest.grow()
forest.number(12)
the tree is growing
there are 12 trees.

五、特殊方法

Python 使用 __ 开头的名字来定义特殊的方法和属性,它们有:

  • __init__()
  • __repr__()
  • __str__()
  • __call__()
  • __iter__()
  • __add__()
  • __sub__()
  • __mul__()
  • __rmul__()
  • __class__
  • __name__

构造方法__init__()

之前说到,在产生对象之后,我们可以向对象中添加属性。事实上,还可以通过构造方法,在构造对象的时候直接添加属性

class Leaf(object):
    """
 A leaf falling in the woods.
 """
    def __init__(self, color='green'):
        self.color = color
l1 = Leaf()
print(l1.color)
l2 = Leaf("red")
print(l2.color)
green
red

回到森林的例子:

import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self):
        # 构造方法中定义了两个属性 trees 和 fires
        self.trees = np.zeros((150,150), dtype=bool)
        self.fires = np.zeros((150,150), dtype=bool)

forest = Forest()

print(forest.trees)
print(forest.fires)
[[False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]
 ...
 [False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]]
[[False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]
 ...
 [False False False ... False False False]
 [False False False ... False False False]
 [False False False ... False False False]]

修改属性的值:

forest.trees[0,0]=True
forest.trees
array([[ True, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       ...,
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False]])

改变它的属性值不会影响其他对象的属性值:

forest2 = Forest()

forest2.trees
array([[False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       ...,
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False],
       [False, False, False, ..., False, False, False]])

事实上,__new__() 才是真正产生新对象的方法,__init__() 只是对对象进行了初始化,所以:

leaf = Leaf()

相当于

my_new_leaf = Leaf.__new__(Leaf)
Leaf.__init__(my_new_leaf)
leaf = my_new_leaf

表示方法 __repr__()__str__()

class Leaf(object):
    """
 A leaf falling in the woods.
 """
    def __init__(self, color='green'):
        self.color = color
    def __str__(self):
        "This is the string that is printed."
        return "A {} leaf".format(self.color)
    def __repr__(self):
        "This string recreates the object."
        return "{}(color='{}')".format(self.__class__.__name__, self.color)

__str__() 是使用 print 函数显示的结果:

leaf = Leaf()
print(leaf)
A green leaf

__repr__() 返回的是不使用 print 方法的结果

leaf
Leaf(color='green')

回到森林的例子:

import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150)):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.fires = np.zeros((self.size), dtype=bool)

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__
forest = Forest()
print(forest)
forest
Forest





Forest(size=(150, 150))

__name____class__ 为特殊的属性:

print("forest所属的类:", forest.__class__)
print("forest所属的类的名字:", forest.__class__.__name__)
forest所属的类: <class '__main__.Forest'>
forest所属的类的名字: Forest

六、属性

只读属性

只读属性,顾名思义,指的是只可读不可写的属性,之前我们定义的属性都是可读可写的,对于只读属性,我们需要使用 @property 修饰符来得到:

class Leaf(object):
    def __init__(self, mass_mg):
        self.mass_mg = mass_mg

    # 这样 mass_oz 就变成属性了
    @property
    def mass_oz(self):
        return self.mass_mg * 3.53e-5

这里 mass_oz 就是一个只读不写的属性(注意是属性不是方法),而 mass_mg 是可读写的属性:

leaf = Leaf(200)

print ("{0:.5f}".format(leaf.mass_oz))
0.0071

可以修改 mass_mg 属性来改变 mass_oz

leaf.mass_mg = 150

print("{0:.5f}".format(leaf.mass_oz))
0.00529
# 是只读属性,不可写:
leaf.mass_oz = 0.001
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In [60], line 2
      1 # 是只读属性,不可写:
----> 2 leaf.mass_oz = 0.001


AttributeError: can't set attribute

回到 forest 的例子,我们希望加入几个只读属性:

import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150)):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.fires = np.zeros((self.size), dtype=bool)

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__

    @property
    def num_cells(self):
        """Number of cells available for growing trees"""
        return np.prod(self.size)

    @property
    def tree_fraction(self):
        """
        Fraction of trees
        """
        num_trees = self.trees.sum()
        return float(num_trees) / self.num_cells

    @property
    def fire_fraction(self):
        """
        Fraction of fires
        """
        num_fires = self.fires.sum()
        return float(num_fires) / self.num_cells

# 查看属性:
forest = Forest()

forest.num_cells
22500

生成一个较小的森林:

small_forest = Forest((10, 10))
small_forest.num_cells
# 初始状态下,树和火灾的比例都是 0:
print(small_forest.tree_fraction)
print(small_forest.fire_fraction)
0.0
0.0

可读写的属性

对于 @property 生成的只读属性,我们可以使用相应的 @attr.setter 修饰符来使得这个属性变成可写的:

class Leaf(object):
    def __init__(self, mass_mg):
        self.mass_mg = mass_mg

    # 这样 mass_oz 就变成属性了
    @property
    def mass_oz(self):
        return self.mass_mg * 3.53e-5

    # 使用 mass_oz.setter 修饰符
    @mass_oz.setter
    def mass_oz(self, m_oz):
        self.mass_mg = m_oz / 3.53e-5
leaf = Leaf(200)
print ("{0:.5f}".format(leaf.mass_oz))
leaf.mass_mg = 150
print ("{0:.5f}".format(leaf.mass_oz))
# 修改 mass_oz 属性:
leaf.mass_oz = 0.01
print ("{0:.5f}".format(leaf.mass_mg))
0.00706
0.00529
283.28612
# 一个等价的替代如下:
class Leaf(object):
    def __init__(self, mass_mg):
        self.mass_mg = mass_mg

    def get_mass_oz(self):
        return self.mass_mg * 3.53e-5

    def set_mass_oz(self, m_oz):
        self.mass_mg = m_oz / 3.53e-5

    mass_oz = property(get_mass_oz, set_mass_oz)

l = Leaf(12)
print(l.get_mass_oz())
l.set_mass_oz(0.02)
print(l.get_mass_oz())
0.0004236
0.02

七、森林火灾模拟

随机生长

  • 在原来的基础上,先让树生长,即定义 grow_trees() 方法
  • 定义方法之前,先指定两个属性:
    • 每个位置随机生长出树木的概率
    • 每个位置随机被闪电击中的概率
  • 为了方便,定义一个辅助函数来生成随机 bool 矩阵,大小与森林大小一致
  • 按照给定的生长概率生成生长的位置,将 trees 中相应位置设为 True
import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150), p_sapling=0.0025, p_lightning=5.0e-6):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.fires = np.zeros((self.size), dtype=bool)
        # The probability of a tree growing
        self.p_sapling = p_sapling
        # The probability of lightning striking
        self.p_lightning = p_lightning

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__

    @property
    def num_cells(self):
        """Number of cells available for growing trees"""
        return np.prod(self.size)

    @property
    def tree_fraction(self):
        """
        Fraction of trees
        """
        num_trees = self.trees.sum()
        return float(num_trees) / self.num_cells

    @property
    def fire_fraction(self):
        """
        Fraction of fires
        """
        num_fires = self.fires.sum()
        return float(num_fires) / self.num_cells

    def _rand_bool(self, p):
        """
        Random boolean distributed according to p, less than p will be True
        """
        return np.random.uniform(size=self.trees.shape) < p

    def grow_trees(self):
        """
        Growing trees.
        """
        growth_sites = self._rand_bool(self.p_sapling)
        self.trees[growth_sites] = True

forest = Forest()
print (forest.tree_fraction)

forest.grow_trees()
print (forest.tree_fraction)
0.0
0.0026666666666666666

火灾模拟

1、定义 start_fires()

  • 按照给定的概率生成被闪电击中的位置

  • 如果闪电击中的位置有树,那么将其设为着火点

2、定义 burn_trees()

  • 如果一棵树的上下左右有火,那么这棵树也会着火

3、定义 advance_one_step()

  • 进行一次生长,起火,燃烧
import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150), p_sapling=0.0025, p_lightning=5.0e-6):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.fires = np.zeros((self.size), dtype=bool)
        # The probability of a tree growing
        self.p_sapling = p_sapling
        # The probability of lightning striking
        self.p_lightning = p_lightning

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__

    @property
    def num_cells(self):
        """Number of cells available for growing trees"""
        return np.prod(self.size)

    @property
    def tree_fraction(self):
        """
        Fraction of trees
        """
        num_trees = self.trees.sum()
        return float(num_trees) / self.num_cells

    @property
    def fire_fraction(self):
        """
        Fraction of fires
        """
        num_fires = self.fires.sum()
        return float(num_fires) / self.num_cells

    def _rand_bool(self, p):
        """
        Random boolean distributed according to p, less than p will be True
        """
        return np.random.uniform(size=self.trees.shape) < p

    def grow_trees(self):
        """
        Growing trees.
        """
        growth_sites = self._rand_bool(self.p_sapling)
        self.trees[growth_sites] = True
    def start_fires(self):
        """
        Start of fire.
        """
        lightning_strikes = (self._rand_bool(self.p_lightning) & 
            self.trees)
        self.fires[lightning_strikes] = True

    def burn_trees(self):
        """
        Burn trees.
        """
        fires = np.zeros((self.size[0] + 2, self.size[1] + 2), dtype=bool)
        fires[1:-1, 1:-1] = self.fires
        north = fires[:-2, 1:-1]
        south = fires[2:, 1:-1]
        east = fires[1:-1, :-2]
        west = fires[1:-1, 2:]
        new_fires = (north | south | east | west) & self.trees
        self.trees[self.fires] = False
        self.fires = new_fires

    def advance_one_step(self):
        """
        Advance one step
        """
        self.grow_trees()
        self.start_fires()
        self.burn_trees()
import matplotlib.pyplot as plt
from matplotlib import cm

forest = Forest()

for i in range(100):
    forest.advance_one_step()
# 使用 matshow() 显示树木图像:
plt.matshow(forest.trees, cmap=cm.Greens)

plt.show()

Python面向对象编程-LMLPHP

# 查看不同着火概率下的森林覆盖率趋势变化:
forest1 = Forest()
forest2 = Forest(p_lightning=5e-4)

tree_fraction1 = []
tree_fraction2 = []

for i in range(2500):
    forest1.advance_one_step()
    forest2.advance_one_step()
    tree_fraction1.append(forest1.tree_fraction)
    tree_fraction2.append(forest2.tree_fraction)

plt.plot(tree_fraction1,"r-",label = r"$forest1 = 5\times 10^{-6}$")
plt.plot(tree_fraction2,"b-",label = r"$forest2 = 5\times 10^{-4}$")
plt.xlabel('Number of iterations')
plt.ylabel('Forest coverage rate')
plt.legend()
plt.show()

Python面向对象编程-LMLPHP

八、继承

一个类定义的基本形式如下:

class ClassName(ParentClass):
    """class docstring"""
    def method(self):
        return
  • class 关键词在最前面
  • ClassName 通常采用 CamelCase 记法
  • 括号中的 ParentClass 用来表示继承关系
  • 冒号不能缺少
  • """""" 中的内容表示 docstring,可以省略
  • 方法定义与函数定义十分类似,不过多了一个 self 参数表示这个对象本身
  • class 中的方法要进行缩进

在里面有一个 ParentClass 项,用来进行继承,被继承的类是父类,定义的这个类是子类。 对于子类来说,继承意味着它可以使用所有父类的方法和属性,同时还可以定义自己特殊的方法和属性。

假设我们有这样一个父类:

class Leaf(object):
    def __init__(self, color="green"):
        self.color = color
    def fall(self):
        print ("Splat!")
leaf = Leaf()
print(leaf.color)
leaf.fall()
green
Splat!

现在定义一个子类,继承自 Leaf

class MapleLeaf(Leaf):
    def change_color(self):
        if self.color == "green":
            self.color = "red"

继承父类的所有方法:

mleaf = MapleLeaf()

print (mleaf.color)
mleaf.fall()
# 但是有自己独有的方法,父类中没有:
mleaf.change_color()
print (mleaf.color)
green
Splat!
red

如果想对父类的方法进行修改,只需要在子类中重定义这个方法即可

class MapleLeaf(Leaf):
    def change_color(self):
        if self.color == "green":
            self.color = "red"
    def fall(self):
        self.change_color()
        print("Plunk!")
mleaf = MapleLeaf()

print (mleaf.color)
mleaf.fall()
print (mleaf.color)
green
Plunk!
red

多重继承

多重继承,指的是一个类别可以同时从多于一个父类继承行为与特征的功能,Python 是支持多重继承的:

class Leaf(object):
    def __init__(self, color='green'):
        self.color = color

class ColorChangingLeaf(Leaf):
    def change(self, new_color='brown'):
        self.color = new_color

class DeciduousLeaf(Leaf):
    def fall(self):
        print ("Plunk!")

class MapleLeaf(ColorChangingLeaf, DeciduousLeaf):
    pass

在上面的例子中, MapleLeaf 就使用了多重继承,它可以使用两个父类的方法:

leaf = MapleLeaf()

leaf.change("yellow")
print (leaf.color)

leaf.fall()
yellow
Plunk!

如果同时实现了不同的接口,那么,最后使用的方法以继承的顺序为准,放在前面的优先继承:

class Leaf(object):
    def __init__(self, color='green'):
        self.color = color

class ColorChangingLeaf(Leaf):
    def change(self, new_color='brown'):
        self.color = new_color    
    def fall(self):
        print ("Spalt!")

class DeciduousLeaf(Leaf):
    def fall(self):
        print ("Plunk!")

class MapleLeaf(ColorChangingLeaf, DeciduousLeaf):
    pass

leaf = MapleLeaf()
leaf.fall()
Spalt!
class MapleLeaf(DeciduousLeaf, ColorChangingLeaf):
    pass
leaf = MapleLeaf()
leaf.fall()
Plunk!

事实上,这个顺序可以通过该类的 __mro__ 属性或者 mro() 方法来查看:

MapleLeaf.__mro__
(__main__.MapleLeaf,
 __main__.DeciduousLeaf,
 __main__.ColorChangingLeaf,
 __main__.Leaf,
 object)

考虑更复杂的例子:

class A(object):
    pass

class B(A):
    pass

class C(A):
    pass

class C1(C):
    pass

class B1(B):
    pass

class D(B1, C):
    pass
D.mro()
[__main__.D, __main__.B1, __main__.B, __main__.C, __main__.A, object]

九、super()函数

super(CurrentClassName, instance) 

返回该类实例对应的父类对象。

class Leaf(object):
    def __init__(self, color="green"):
        self.color = color
    def fall(self):
        print ("Splat!")

class MapleLeaf(Leaf):
    def change_color(self):
        if self.color == "green":
            self.color = "red"
    def fall(self):
        self.change_color()
        super(MapleLeaf, self).fall()

这里,我们先改变树叶的颜色,然后再找到这个实例对应的父类,并调用父类的 fall() 方法:

mleaf = MapleLeaf()

print (mleaf.color)
mleaf.fall()
print (mleaf.color)

green
Splat!
red

回到我们的森林例子,这里我们将森林 Forest 作为父类,并定义一个子类 BurnableForest

import numpy as np

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150), p_sapling=0.0025):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.p_sapling = p_sapling

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__

    @property
    def num_cells(self):
        """Number of cells available for growing trees"""
        return np.prod(self.size)

    @property
    def tree_fraction(self):
        """
        Fraction of trees
        """
        num_trees = self.trees.sum()
        return float(num_trees) / self.num_cells

    def _rand_bool(self, p):
        """
        Random boolean distributed according to p, less than p will be True
        """
        return np.random.uniform(size=self.trees.shape) < p

    def grow_trees(self):
        """
        Growing trees.
        """
        growth_sites = self._rand_bool(self.p_sapling)
        self.trees[growth_sites] = True    

    def advance_one_step(self):
        """
        Advance one step
        """
        self.grow_trees()

  • 将与燃烧相关的属性都被转移到了子类中去。
  • 修改两类的构造方法,将闪电概率放到子类的构造方法上,同时在子类的构造方法中,用 super 调用父类的构造方法。
  • 修改 advance_one_step(),父类中只进行生长,在子类中用 super 调用父类的 advance_one_step() 方法,并添加燃烧的部分。
class BurnableForest(Forest):
    """
    Burnable forest support fires
    """    
    def __init__(self, p_lightning=5.0e-6, **kwargs):
        super(BurnableForest, self).__init__(**kwargs)
        self.p_lightning = p_lightning        
        self.fires = np.zeros((self.size), dtype=bool)

    def advance_one_step(self):
        """
        Advance one step
        """
        super(BurnableForest, self).advance_one_step()
        self.start_fires()
        self.burn_trees()

    @property
    def fire_fraction(self):
        """
        Fraction of fires
        """
        num_fires = self.fires.sum()
        return float(num_fires) / self.num_cells

    def start_fires(self):
        """
        Start of fire.
        """
        lightning_strikes = (self._rand_bool(self.p_lightning) & 
            self.trees)
        self.fires[lightning_strikes] = True

    def burn_trees(self):
        """
        Burn trees.
        """
        fires = np.zeros((self.size[0] + 2, self.size[1] + 2), dtype=bool)
        fires[1:-1, 1:-1] = self.fires
        north = fires[:-2, 1:-1]
        south = fires[2:, 1:-1]
        east = fires[1:-1, :-2]
        west = fires[1:-1, 2:]
        new_fires = (north | south | east | west) & self.trees
        self.trees[self.fires] = False
        self.fires = new_fires

# 测试父类
forest = Forest()

forest.grow_trees()

print (forest.tree_fraction)

# 测试子类:
burnable_forest = BurnableForest()
burnable_forest.grow_trees()
burnable_forest.start_fires()
burnable_forest.burn_trees()
print (burnable_forest.tree_fraction)
0.0027555555555555554
0.002533333333333333
import matplotlib.pyplot as plt

%matplotlib inline

forest = Forest()
forest2 = BurnableForest()

tree_fractions = []

for i in range(2500):
    forest.advance_one_step()
    forest2.advance_one_step()
    tree_fractions.append((forest.tree_fraction, forest2.tree_fraction))

plt.plot(tree_fractions)

plt.show()

Python面向对象编程-LMLPHP

__str____repr__self.__class__ 会根据类型不同而不同:

forest
Forest(size=(150, 150))
forest2
BurnableForest(size=(150, 150))
print (forest)
print(forest2)
Forest
BurnableForest

十、接口

Python 中,鸭子类型(duck typing)是一种动态类型的风格。所谓鸭子类型,来自于 James Whitcomb Riley 的“鸭子测试”:

假设我们需要定义一个函数,这个函数使用一个类型为鸭子的参数,并调用它的走和叫方法。

在鸭子类型的语言中,这样的函数可以接受任何类型的对象,只要这个对象实现了走和叫的方法,否则就引发一个运行时错误。换句话说,任何拥有走和叫方法的参数都是合法的。

先看一个例子,父类:

class Leaf(object):
    def __init__(self, color="green"):
        self.color = color
    def fall(self):
        print ("Splat!")

子类:

class MapleLeaf(Leaf):
    def fall(self):
        self.color = 'brown'
        super(MapleLeaf, self).fall()

新的类:

class Acorn(object):
    def fall(self):
        print ("Plunk!")

这三个类都实现了 fall() 方法,因此可以这样使用:

objects = [Leaf(), MapleLeaf(), Acorn()]

for obj in objects:
    obj.fall()
Splat!
Splat!
Plunk!

这里 fall() 方法就一种鸭子类型的体现。

不仅方法可以用鸭子类型,属性也可以:

import numpy as np
from scipy.ndimage.measurements import label

class Forest(object):
    """ Forest can grow trees which eventually die."""
    def __init__(self, size=(150,150), p_sapling=0.0025):
        self.size = size
        self.trees = np.zeros(self.size, dtype=bool)
        self.p_sapling = p_sapling

    def __repr__(self):
        my_repr = "{}(size={})".format(self.__class__.__name__, self.size)
        return my_repr

    def __str__(self):
        return self.__class__.__name__

    @property
    def num_cells(self):
        """Number of cells available for growing trees"""
        return np.prod(self.size)

    @property
    def losses(self):
        return np.zeros(self.size)

    @property
    def tree_fraction(self):
        """
        Fraction of trees
        """
        num_trees = self.trees.sum()
        return float(num_trees) / self.num_cells

    def _rand_bool(self, p):
        """
        Random boolean distributed according to p, less than p will be True
        """
        return np.random.uniform(size=self.trees.shape) < p

    def grow_trees(self):
        """
        Growing trees.
        """
        growth_sites = self._rand_bool(self.p_sapling)
        self.trees[growth_sites] = True    

    def advance_one_step(self):
        """
        Advance one step
        """
        self.grow_trees()

class BurnableForest(Forest):
    """
    Burnable forest support fires
    """    
    def __init__(self, p_lightning=5.0e-6, **kwargs):
        super(BurnableForest, self).__init__(**kwargs)
        self.p_lightning = p_lightning        
        self.fires = np.zeros((self.size), dtype=bool)

    def advance_one_step(self):
        """
        Advance one step
        """
        super(BurnableForest, self).advance_one_step()
        self.start_fires()
        self.burn_trees()

    @property
    def losses(self):
        return self.fires

    @property
    def fire_fraction(self):
        """
        Fraction of fires
        """
        num_fires = self.fires.sum()
        return float(num_fires) / self.num_cells

    def start_fires(self):
        """
        Start of fire.
        """
        lightning_strikes = (self._rand_bool(self.p_lightning) & 
            self.trees)
        self.fires[lightning_strikes] = True

    def burn_trees(self):    
        pass

class SlowBurnForest(BurnableForest):
    def burn_trees(self):
        """
        Burn trees.
        """
        fires = np.zeros((self.size[0] + 2, self.size[1] + 2), dtype=bool)
        fires[1:-1, 1:-1] = self.fires
        north = fires[:-2, 1:-1]
        south = fires[2:, 1:-1]
        east = fires[1:-1, :-2]
        west = fires[1:-1, 2:]
        new_fires = (north | south | east | west) & self.trees
        self.trees[self.fires] = False
        self.fires = new_fires

class InstantBurnForest(BurnableForest):
    def burn_trees(self):
        # 起火点
        strikes = self.fires
        # 找到连通区域
        groves, num_groves = label(self.trees)
        fires = set(groves[strikes])
        self.fires.fill(False)
        # 将与着火点相连的区域都烧掉
        for fire in fires:
            self.fires[groves == fire] = True
        self.trees[self.fires] = False
        self.fires.fill(False)

C:\Users\26969\AppData\Local\Temp\ipykernel_56208\1569177306.py:2: DeprecationWarning: Please use `label` from the `scipy.ndimage` namespace, the `scipy.ndimage.measurements` namespace is deprecated.
  from scipy.ndimage.measurements import label
# 测试
forest = Forest()
b_forest = BurnableForest()
sb_forest = SlowBurnForest()
ib_forest = InstantBurnForest()

forests = [forest, b_forest, sb_forest, ib_forest]

losses_history = []

for i in range(1500):
    for fst in forests:
        fst.advance_one_step()
    losses_history.append(tuple(fst.losses.sum() for fst in forests))

import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize=(10,6))

plt.plot(losses_history)
plt.legend([f.__str__() for f in forests])

plt.show()

Python面向对象编程-LMLPHP

十一、类属性和方法

类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

实例如下:

class JustCounter:
    __secretCount = 0  # 私有变量
    publicCount = 0    # 公开变量
 
    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print (self.__secretCount)
 
counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount)  # 报错,实例不能访问私有变量
1
2
2



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In [143], line 14
     12 counter.count()
     13 print (counter.publicCount)
---> 14 print (counter.__secretCount)  # 报错,实例不能访问私有变量


AttributeError: 'JustCounter' object has no attribute '__secretCount'

类的方法

在类的内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。

self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self

类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。self.__private_methods

私有方法实例:

class Site:
    def __init__(self, name, url):
        self.name = name       # public
        self.__url = url   # private
 
    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)
 
    def __foo(self):          # 私有方法
        print('这是私有方法')
 
    def foo(self):            # 公共方法
        print('这是公共方法')
        self.__foo()
 
x = Site('菜鸟教程', 'www.runoob.com')
x.who()        # 正常输出
x.foo()        # 正常输出
x.__foo()      # 报错
name  :  菜鸟教程
url :  www.runoob.com
这是公共方法
这是私有方法



---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In [144], line 20
     18 x.who()        # 正常输出
     19 x.foo()        # 正常输出
---> 20 x.__foo()      # 报错


AttributeError: 'Site' object has no attribute '__foo'

类的专有方法:

  • init : 构造函数,在生成对象时调用
  • del : 析构函数,释放对象时使用
  • repr : 打印,转换
  • setitem : 按照索引赋值
  • getitem: 按照索引获取值
  • len: 获得长度
  • cmp: 比较运算
  • call: 函数调用
  • add: 加运算
  • sub: 减运算
  • mul: 乘运算
  • truediv: 除运算
  • mod: 求余运算
  • pow: 乘方
12-13 06:46