简说Python,号主老表,Python终身学习者,数据分析爱好者,从18年开始分享Python知识,原创文章227篇,写过Python、SQL、Excel入门文章,也写过Web开发、数据分析文章,老表还总结整理了一份2022Python学习资料和电子书资源,关注后私信回复:2022 即可领取。
零、写在前面
写完今天这一篇,Python小知识这块就完了,一共四篇,也就是我过了一遍《零压力学Python》后记录下来的一些重要的点,希望对初学者或者复习Python基础的读者有所帮助,再多的话我就不说了,一切都在知识里面,加油。
本系列前三篇
一、面相对象三大特性
(1)封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。
封装在平时用的比较多,在编写一个大项目的时候,我们会自觉地根据功能分类,这里类就是一种封装,再细点,类里的函数也是封装,当我们使用的时候,只用类名,函数名,而不接触具体的类体和函数体,这样的好处是显而易见的,对自己,项目代码更加易读,可维护性更高,同时不怕功能代码被串改,对别人,合作伙伴不需要知道底层实现的,更容易理解代码含义(函数,方法取名时自己就要注意了)。
(2)继承
简单的说,继承就是在一个现有类型的基础上,通过增加新的方法或者重定义已有方法(下面会讲到,这种方式叫重写)的方式,产生一个新的类型。继承是面向对象的三个基本特征--封装、继承、多态的其中之一,我们在使用Python编写的每一个类都是在继承,同JAVA语言中,java.lang.Object类是所有类最根本的基类(或者叫父类、超类),如果我们新定义的一个类没有明确地指定继承自哪个基类,那么Python就会默认为它是继承自Object类的。
''' data : 2018.12.08 author : 老表 goal : 继承简单例子 ''' class allStr(): ''' 父类 ''' all_words = "欢迎关注简说Python!" other_words = "人生苦短,我选Python!" def all_print(self): ''' 打印其他的话 ''' print(self.other_words) # 调用自己的类变量 class myStr(allStr): ''' 子类 ''' def my_print(self): ''' 打印我的话 ''' print(allStr.all_words) # 调用父类变量 # 初始化类对象 my_str = myStr() # 调用类方法 my_str.my_print() # 调用父类的方法 my_str.all_print() # 企图外部直接修改父类变量 my_str.all_words = "你好,我是老表,欢迎置顶公众号:简说Python!" # 调用类方法 my_str.my_print() ''' result : 欢迎关注简说Python! 人生苦短,我选Python! 欢迎关注简说Python! '''
看上面的结果我们可以看出,我们不能直接在外部修改父类变量,在第三篇中有介绍,怎么修改类变量,也就是__init__方法的使用。
(3)多态
多态是指一个程序中同名的不同方法共存的情况。这些方法同名的原因是它们的终于功能和目的都同样,可是因为在完毕同一功能时,可能遇到不同的详细情况。所以须要定义含不同的详细内容的方法,来代表多种详细实现形式。
多态包括:重载和重写
重载
在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)
重写
在子类中定义某方法与其父类有同样的名称和参数和返回值,我们说该方法被重写 (Overriding)。
两者易混淆,牢记区别:有继承关系的是重写,没有的是重载。
""" author : 老表 data : 2018.12.09 goal : 多态实例 """ class AllStr: """ 父类 """ all_words = "你好,简说Python!" my_words = "欢迎关注:简说Python!" def print_str(self): print(self.all_words) class MyStr(AllStr): """ 子类 """ def print_str(self): """ 重写父类的方法 """ print(self.my_words) def print_me(self): """ 属于自己的方法 """ print("人生苦短,快学Python!") def print_me(self, your_words): """ 重载自己类里的方法 """ print(your_words) # 初始化一个对象 my_str = MyStr() # 调用重写父类的方法 my_str.print_str() # 调用自己特有的方法 # my_str.print_me() 重载后,之前的方法不可调用 # 调用子类自己重载的方法 your_words = "我置顶了 简说Python!" my_str.print_me(your_words) ''' result : 欢迎关注:简说Python! 我置顶了 简说Python! '''
需要注意的是,在Python里面重载是不被推崇的,或者说没有,为什么呢?因为对于Python这么优雅的语言来说,重载是没必要的,从重载的定义来看,在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,Python本身就不限制变量的数据类型,这是一点,如果传人变量个数不确定,还可以用*args
,传递多个变量,想多少个就多少个,这是其二,最后,如果两个函数的功能确实有很大的不同,那么就没必要硬取两个相同的函数名了,直接取不同的函数名加以区分其实是更好的。
二、生命游戏
介绍
生命游戏是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。
基本规律
对于网格中的每个位置,计算有多少个邻接位置中有活细胞,包括对角邻接位置,因此一个方块的周围最多有八个活细胞(数值为1的方块),最少为零,规则就是,如果这个方块周围的活细胞数等于三,就繁殖,也就是值变为1,如果这个方块周围的活细胞数少于两个或者大雨三个,则该方块中细胞死亡,值变为0。
(1)Matrix2D类代码实现
新建一个matrix2d.py
文件,把下面代码封装到里面,一个专门用于处理二维数组的类。
""" 二维矩阵类 """ class Matrix2D: """ 通用的二维矩阵类 """ def __init__(self, rows, cols): """ 初始化矩阵row行,col列 """ self.grid = [[0]*cols for _ in range(rows)] self.rows = rows self.cols = cols def get_cell(self, r, c): """ 获取单元格(r,c)的值 """ return self.grid[r][c] def set_cell(self, n, **args): """ 设置某个位置的值 """ for r, c in args: self.grid[r][c] = n def inc_cells(self, **args): """ 将任意的单元格 +1 """ for r, c in args: self.grid[r][c] += 1 def set_all_cells(self, n=0): """ 将所有单元格值都设置为 n """ for i in range(self.rows): for j in range(self.cols): self.grid[i][j] = n
(2)主函数
""" 生命游戏 """ from lifemat import Matrix2D rows = 5 cols = 5 # 存储图符号的二维数组 life_mat = Matrix2D(rows, cols) # 存储具体数据的二维数组 nc_mat = Matrix2D(rows, cols) # 初始化 life_mat.set_cells(1, (1, 3), (2, 1), (2, 3), (3, 2), (3, 3)) # 创建边界字符串 border_str = ' _ ' * cols def get_mat_str(a_mat): """ 处理打印字符串 """ disp_str = '' for i in range(rows): lst = [get_chr(a_mat, i, j) for j in range(cols)] disp_str += ''.join(lst) + '\n' return disp_str def get_chr(a_mat, r, c): """ 设置图符号 """ return ' 1 ' if a_mat.get_cell(r, c) > 0 else ' 0 ' def do_generation(): """ 打印当前状态并生成下个状态 """ # 打印当前生命矩阵状态 print(border_str + '\n' + get_mat_str(life_mat)) # 把数据全部置0 nc_mat.set_all_cells(0) # 根据图符号矩阵life_mat来给nc_mat赋值 for i in range(rows): for j in range(cols): if life_mat.get_cell(i, j): # 环绕图像,使有限的二维数组变成没有边界的生命游戏 im = (i - 1) % rows ip = (i + 1) % rows # 当前行号-/+ 1 jm = (j - 1) % cols jp = (j + 1) % cols # 当前列号-/+ 1 # 设置数据量为 1 ,表示有活细胞 nc_mat.inc_cells((im, jm), (im, j), (im, jp), (i, jm), (i, jp), (ip, jm), (ip, j), (ip, jp)) # 根据邻居数量矩阵按规则生成下一代 for i in range(rows): for j in range(cols): n = nc_mat.get_cell(i, j) if n < 2 or n > 3: # 死亡现象 life_mat.set_cells(0, (i, j)) elif n == 3: # 繁殖现象 life_mat.set_cells(1, (i, j)) import time n = 100 for i in range(n): # 循环调用迭代 do_generation() # 设置时间间隔 time.sleep(1)
运行效果
生命游戏运行效果
录了20s有兴趣可以慢慢看~
建议自己先把逻辑思路理清,然后把代码复现一遍,肯定有很大收获哦~
《零压力学Python》里说,学习新编程语言的时候,如果能使用它编写出生命游戏,就说明掌握了这门语言,编完后,我觉得是有很大道理的,不说所有,能弄懂这个程序,至少可以说明你的基础过关了。
三、装饰器
简单说明是什么有什么用
简单的称为装饰其他函数的函数。
我先说一下我的装饰器的理解,然后再上一些例子。
装饰器就是一个函数,和一般函数一样,装饰器可以有返回值,参数,代码段,这个函数里面还包含了一个或多个函数,对,函数的嵌套,同样里面的函数和一般函数也是一样的,可以拥有一切普通函数该拥有的,简单来说,装饰器就是把函数当做普通变量来用,哪大家会好奇,装饰器到底有什么用呢?
书上是这样说的”装饰器给函数名重新赋值,使其指向原始函数的包装板,包装板不仅具备原始函数的所有功能,还添加了新功能“,这样一理解,可以这样转化,装饰器就是用来丰富函数功能的,那是嘛时候会起作用呢?
调试的时候,特别是对于大程序的调试,我不可能在一个模块里几百个函数一个个调试,这个时候来个装饰器就很好了,或者说我想验证某个东西,但不希望在原始函数添加,这个时候装饰器就是一把利器了,下面让我们随这几个例子来更好的学习装饰器吧。
实例学习
1.最简单的例子
def my_decorator(f): """ 装饰器,将一个函数作为参数传递进来,进行包装,然后返回 """ def wrapper(): print("I am doing extra stuff.") f() print("Doing more extra stuff.") return wrapper def hello(): print("简说Python 你好!") new_hello = my_decorator(hello) new_hello() ''' result: I am doing extra stuff. 简说Python 你好! Doing more extra stuff. '''
2.中等简单
def my_decorator(f): """ 装饰器,将一个函数作为参数传递进来,进行包装,然后返回 """ def wrapper(): print("I am doing extra stuff.") f() print("Doing more extra stuff.") return wrapper ''' 说明一下:新语法,@+装饰器的名称,相当于 def hello(): print("Hi,简说Python!") hello = my_decorator(hello) ''' @my_decorator def hello(): print("Hi,简说Python!") hello() ''' result: I am doing extra stuff. Hi,简说Python! Doing more extra stuff. '''
3.复杂案例
from time import time def diagnostics(f): def wrapper(*args, **kwargs): """ 这个包装函数带参数, *args处理多个参数, **kwargs可以处理具名参数 """ print("Executed", f.__name__, "at", time()) value = f(*args, **kwargs) print("Exited", f.__name__, "at", time()) print("Arguments:",args) print("Value returned:", value, "\n") return value return wrapper @diagnostics def print_nums(): """ 不带参数,没有返回值 """ for i in range(4): print(i, end="\t") @diagnostics def add_nums(a, b): """ 带参数,有返回值 """ return a+b # 调用经过装饰器装饰过的函数 print_nums() print("-"*50) add_nums(2, 3) print("-"*50) ''' result: Executed print_nums at 1544353643.8159559 0 1 2 3 Exited print_nums at 1544353643.8159559 Arguments: () Value returned: None -------------------------------------------------- Executed add_nums at 1544353643.8159559 Exited add_nums at 1544353643.8159559 Arguments: (2, 3) Value returned: 5 -------------------------------------------------- '''
以上就是装饰器的基本讲解了,细细品读,理清其中思绪,就能很好的理解和掌握了,希望对大家有所帮助。
灵活应用这些基本操作,让你的工作学习事半功倍。