Day06 - 面向对象

Day06的课程要点记录
详细教程地址:Day6 - 面向对象学习

上节补遗 - Subprocess模块

os.system # 输出命令结果到屏幕,返回命令执行状态
os.popen("dir").read() # 会保存命令的执行结果输出

subprocess 模块主要用于替换几个旧模块:os.system, os.spawn*

常用subprocess方法示例

subprocess.run() # Python 3.5 后才出现的方法

执行命令,返回命令执行状态 , 0 or 非0
>>> retcode = subprocess.call(["ls", "-l"])    # 命令用列表单个传入,或者完整输入字符串,用`shell=True`来执行操作系统执行。该方法效果类似os.system

#执行命令,如果命令结果为0,就正常返回,否则抛异常
>>> subprocess.check_call(["ls", "-l"])
0

#接收字符串格式命令,返回元组形式,第1个元素是执行状态,第2个是命令结果 
>>> subprocess.getstatusoutput('ls /bin/ls')    # 最常用
(0, '/bin/ls')

#接收字符串格式命令,并返回结果
>>> subprocess.getoutput('ls /bin/ls')
'/bin/ls'

#执行命令,并返回结果,注意是返回结果,不是打印,下例结果返回给res
>>> res=subprocess.check_output(['ls','-l'])
>>> res
b'total 0\ndrwxr-xr-x 12 alex staff 408 Nov 2 11:05 OldBoyCRM\n'

上面那些方法,底层都是封装的subprocess.Popen

poll()    # 检查是否执行完毕,根据状态返回相应值
Check if child process has terminated. Returns returncode
wait()
Wait for child process to terminate. Returns returncode attribute.

terminate() 杀掉所启动进程
communicate() 等待任务结束
stdin 标准输入

stdout 标准输出

stderr 标准错误
pid
The process ID of the child process.

例子:
stdin, stdout等,必须要加参数subprocess.PIPE,才能read()
类似操作系统中,不同进程的程序互相传数据需要通过管道

>>> p = subprocess.Popen("df -h|grep disk",stdin=subprocess.PIPE,stdout=subprocess.PIPE,shell=True)
>>> p.stdout.read()
b'/dev/disk1 465Gi 64Gi 400Gi 14% 16901472 104938142 14% /\n'

调用subprocess.run(...)是推荐的常用方法,在大多数情况下能满足需求,但如果你可能需要进行一些复杂的与系统的交互的话,你还可以用subprocess.Popen(),语法如下:

p = subprocess.Popen("find / -size +1000000 -exec ls -shl {} \;",shell=True,stdout=subprocess.PIPE)
print(p.stdout.read())

可用参数

  • args:shell命令,可以是字符串或者序列类型(如:list,元组)
  • bufsize:指定缓冲。0 无缓冲,1 行缓冲,其他 缓冲区大小,负值 系统缓冲
  • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
  • preexec_fn:只在Unix平台下有效,用于指定一个可执行对象(callable object),它将在子进程运行之前被调用
  • close_sfs:在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出、错误管道。
    所以不能将close_fds设置为True同时重定向子进程的标准输入、输出与错误(stdin, stdout, stderr)。
  • shell:同上
  • cwd:用于设置子进程的当前目录
  • env:用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
  • universal_newlines:不同系统的换行符不同,True -> 同意使用 \n
  • startupinfo与createionflags只在windows下有效
    将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等

subprocess实现sudo 自动输入密码


import subprocess
 
def mypass():
    mypass = '123' #or get the password from anywhere
    return mypass
 
echo = subprocess.Popen(['echo',mypass()],
                        stdout=subprocess.PIPE,
                        )

sudo = subprocess.Popen(['sudo','-S','iptables','-L'],
                        stdin=echo.stdout,
                        stdout=subprocess.PIPE,
                        )

end_of_pipe = sudo.stdout

print "Password ok \n Iptables Chains %s" % end_of_pipe.read()

一、面向对象

1.1 引子

开发一款叫做<人狗大战>的游戏,需要至少2个角色:人、狗,且人和狗都有不同的技能,如人拿棍打狗, 狗可以咬人,怎么描述这种不同的角色和他们的功能呢?

def person(name, age, sex, job):
    data = {
        'name': name,
        'age': age,
        'sex': sex,
        'job': job
    }
    return data
 
def dog(name, dog_type):
    data = {
        'name': name,
        'type': dog_type
    }
    return data

上面两个方法相当于造了两个角色模板,游戏开始,需要成实际对象,怎么生成呢?

d1 = dog("李闯", "京巴")
p1 = person("孙海涛", 36, "F", "运维")
p2 = person("林海峰", 27, "F", "Teacher")

两个角色对象生成了,狗和人还有不同的功能怎么实现呢?
可以每个功能再写一个函数,想执行哪个功能,直接调用就可以了。

def bark(d):
    print("dog %s:wang.wang..wang..." % d['name'])

def walk(p):
    print("person %s is walking..." % p['name'])

walk(p1)
bark(d1)

上面的功能实现的简直是完美!
但是仔细玩耍一会,你就不小心干了下面这件事

p1 = person("孙海涛",36,"F","运维")
bark(p1) #把人的对象传给了狗的方法

事实上并没出错。但显然人是不能调用狗的功能的,如何在代码级别实现这个限制呢?

def person(name, age, sex, job):
    def walk(p):
        print("person %s is walking..." % p['name'])
    data = {
        'name': name,
        'age': age,
        'sex': sex,
        'job': job,
        'walk': walk
    }
    return data

def dog(name, dog_type):
    def bark(d):
        print("dog %s:wang.wang..wang..." % d['name'])
    data = {
        'name': name,
        'type': dog_type,
 
        'bark': bark
    }
    return data

d1 = dog("李闯", "京巴")
p1 = person("孙海涛", 36, "F", "运维")
p2 = person("林海峰", 27, "F", "Teacher")
 
d1['bark'](d1)
p1['walk'](p1)

1.2

刚才只是阻止了两个完全不同的角色之间的功能混用。 但有可能同一种角色,只有部分属性是不同的。
比如cs里有警察和恐怖份子,但因为都是人,所以写一个角色叫person(), 警察和恐怖份子都可以互相射击,但警察不可以杀人质,恐怖分子可以。
这怎么实现呢?在杀人质的功能里加个判断,如果是警察,就不让杀就ok了。
没错, 这虽然解决了杀人质的问题,但其实警察和恐怖分子的区别还有很多,同时又有很多共性,如果在每个区别处都单独做判断,那得累死。
但如果就直接写两个角色,就代表相同的功能也要重写了。

二、面向过程 VS 面向对象

2.1 编程范式

编程:程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程。
程序:程序员为了得到一个任务结果而编写的一组指令的集合。
编程范式:实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。

不同的编程范式本质上代表对各种类型的任务,采取的不同的解决问题的思路。
大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。
两种最重要的编程范式分别是面向过程编程面向对象编程

2.2 面向过程编程(Procedural Programming)

Procedural programming uses a list of instructions to tell the computer what to do step-by-step. *
面向过程又被称为
top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。
基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程。
这些子过程在执行的过程中再继续分解,直到小问题足够简单,可以在一个小步骤范围内解决。
举个典型的面向过程的例子
数据库备份*,分三步:
连接数据库,备份数据库,测试备份文件可用性。

def db_conn():
    print("connecting db...")
 
def db_backup(dbname):
    print("导出数据库...",dbname)
    print("将备份文件打包,移至相应目录...")
 
def db_backup_test():
    print("将备份文件导入测试库,看导入是否成功")
 
def main():
    db_conn()
    db_backup('my_db')
    db_backup_test()
 
if __name__ == '__main__':
    main()

这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改.
举个例子:如果程序开头你设置了一个变量值为1, 但如果其它子过程依赖这个值为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改。
假如又有一个其它子程序依赖这个子过程,那就会发生一连串的影响,随着程序越来越大,这种编程方式的维护难度会越来越高。
所以一般认为,如果只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的。但如果要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便。

2.3 面向对象编程(Object-Oriented Programming)

面向对象的几个核心特性如下

2.3.1 Class 类

一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法。
类似例子中的模板

2.3.2 Object 对象

一个对象即是一个类实例化后的实例。一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同。
类似例子中的具体的人与狗

2.3.3 Encapsulation 封装

在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法。

  1. 防止数据被随意修改。
  2. 使外部程序不需要关注对象内部的构造,只需要通过此对象对外提供的接口进行直接访问。

2.3.4 Inheritance 继承

一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承。

  1. 通过父类 -> 子类的方式以最简代码实现不同角色的共同点和不同点。

2.3.5 Polymorphism 多态

多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
人类有多个人种,不同人种都是用嘴说不同语言。

三、面向对象编程(Object-Oriented Programming )介绍

无论用什么形式来编程,我们都要明确记住以下原则:

  1. 写重复代码是非常不好的低级行为
  2. 你写的代码需要经常变更

函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。
不考虑语法细节,相比靠函数拼凑出来的写法,用面向对象中的类来写最直接的改进有以下2点:

  1. 代码量少了近一半
  2. 角色和它所具有的功能可以一目了然看出来

3.1 类的基本定义

class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法
    def _init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数
        self.name = name
        self.role = role
        self.weapon = weapon
        self.life_value = life_value
        self.money = money

上面的__init__()叫做初始化方法(或构造方法), 在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作。
所以这里写的__init__(self,name,role,weapon,life_value=100,money=15000)就是要在创建一个角色时给它设置这些属性,那么这第一个参数self是什么呢?
每当初始化一个角色,就需要调用这个类一次:

r1 = Role('Alex','police','AK47’)  # 生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法
r2 = Role('Jack','terrorist','B22’)  # 生成一个角色

上面创建角色时,我们并没有给__init__传值,程序也没未报错,是因为,类在调用它自己的__init__(…)时自己帮你给self参数赋值了

r1 = Role('Alex','police','AK47’)   # 此时self 相当于 r1 ,  Role(r1,'Alex','police','AK47’)
r2 = Role('Jack','terrorist','B22’)  # 此时self 相当于 r2, Role(r2,'Jack','terrorist','B22’)

执行r1 = Role('Alex','police','AK47’)时,python的解释器其实干了两件事:

  1. 在内存中开辟一块空间指向r1这个变量名
  2. 调用Role这个类并执行其中的__init__(…)方法,相当于Role.__init__(r1,'Alex','police',’AK47’)
    这么做是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,就可以直接r1.name, r1.weapon 来调用。
    所以,为实现这种关联,在调用init方法时,就必须把r1这个变量也传进去,否则init不知道要把那3个参数跟谁关联呀。
  3. 所以这个__init__(…)方法里的self.name = name, self.role = role等等的意思就是要把这几个值 存到r1的内存空间里。

Day06 - 面向对象_第1张图片
实例示意图

根据上图得知,其实 self就是实例本身!你实例化时python会自动把这个实例本身通过 self参数传进去。

总结:

  1. 上面的这个r1 = Role('Alex','police','AK47’)动作,叫做类的“实例化”, 就是把一个虚拟的抽象的类,通过这个动作,变成了一个具体的对象了, 这个对象就叫做实例
  2. 刚才定义的这个类体现了面向对象的第一个基本特性封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容。

3.2

四、面向对象的特性

4.1 封装

封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

4.1.1 类 --> 实例化 --> 实例对象
__init__  # 构造函数
self.name = name  # 属性、成员变量、字段
def sayhi()  # 方法、动态属性
4.1.2 私有属性:

__private_attr_name = value (只能内部访问)

def get_heart(self): # 对外部提供只读访问接口
return self.__heart

r1._Role__heart # 强制访问私有属性

4.1.3 公有属性

在类里定义的属性,为公有属性

例子中的nationality

实例指向公有属性

4.1.4 析构方法

def del(self):
print("del......run......")

4.2 继承

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。
继承是指:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式主要有2类:实现继承、接口继承。
Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法);
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法。
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

4.2.1 继承示例
class SchoolMember(object):
    """学校成员基类"""
    member = 0
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex
        self.enroll()
 
    def enroll(self):
        """注册"""
        print("A new school member [%s] has been enrolled" % self.name)
        SchoolMember.member += 1
 
    def tell(self):
        print("-----%s info-----" % self.name)
        for k, v in self.__dict__.items():
            print('\t', k, ':', v)
 
    def __del__(self):
        print("%s has been kicked off" % self.name)
        SchoolMember.member -= 1
 
 
class School(object):
    def branch(self, address):
        self.address = address
        print("Openning a new branch school in %s" % self.address)
 
 
class Teacher(SchoolMember, School):  # 多继承
    """讲师类"""
    def __init__(self, name, age, sex, salary, course):
        # SchoolMember.__init__(self, name, age, sex)  # 经典类写法
        super(Teacher, self).__init__(name, age, sex)  # 新式类写法
        self.salary = salary
        self.course = course
 
    def teach(self):
        print("Teacher [%s] is teaching [%s]" % (self.name, self.course))
 
 
class Student(SchoolMember):
    def __init__(self, name, age, sex, tuition, course):
        SchoolMember.__init__(self, name, age, sex)
        self.tuition = tuition
        self.course = course
        self.amount = 0
 
    def pay_tuition(self, amount):
        print("student [%s] has paid [%s]" % (self.name, amount))
        self.amount += amount
 
t1 = Teacher('Alex', 32, 'Male', 1000000, 'Python')
s1 = Student('Will', 32, 'Male', 7000, 'PY_Net')
s2 = Student('Simth', 22, 'Male', 11000, 'PY_S14')
 
print(SchoolMember.member)
del s2
print(SchoolMember.member)
 
t1.tell()
s1.tell()
t1.branch("Shanghai")
4.2.2 经典类 vs 新式类
  1. 写法
    class Person(object): # new style
    super(Person, self).()
    class Person: # classical style
    Person.init()
  2. 多继承时,继承顺序的区别
    广度查询、深度查询
    Python2 中经典类为深度查询,新式类为广度查询
    Python3 中经典类和新式类均为广度查询

4.3 多态

多态是为了实现接口重用。
多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
Pyhon不直接支持多态,但可以间接实现。

通过Python模拟的多态

class Animal:
    def __init__(self, name):  # Constructor of the class
        self.name = name
 
    def talk(self):  # Abstract method, defined by convention only
        raise NotImplementedError("Subclass must implement abstract method")
 
 
class Cat(Animal):
    def talk(self):
        return 'Meow!'
 
 
class Dog(Animal):
    def talk(self):
        return 'Woof! Woof!'
 
animals = [Cat('Missy'),
           Dog('Lassie')]
 
for animal in animals:
    print(animal.name + ': ' + animal.talk())

五、补充

5.1 什么是面向对象编程

  • 之前使用的是函数式编程
  • 现在是类 + 对象

5.2 什么是类,什么是对象?又有什么关系?

class 类:

    def 函数1():
        pass
    def 函数2():
        pass

    # obj是对象,实例化的过程
    obj = 类()
    obj.函数1()

乍看之下,面向对象不好,函数编程好
有时候,函数编程能实现功能,但是麻烦;而面向对象非常简单的就能实现。

5.3 什么时候适用面向对象?

  • 根据一个模板创建某些东西时
  • 如果多个函数需要传入多个共同参数时
  • 应用场景
class SSH:
    def __init__(self, host, port, username, passwd):
        self.host = host
        ...

    def connection(self):
        # 去创建连接
        self.conn = 和服务器创建的连接对象()

    def close(self):
        # 关闭
        self.conn.close

    def upload(self):
        self.conn 使用连接上传文件

    def cmd(self):
        self.conn 使用连接执行命令

obj = SHH(......)
obj.connection()
obj.upload()
obj.close()

obj = SHH(......)
obj.connection()
obj.cmd()
obj.upload()
obj.cmd()
obj.close()

5.4 self就是调用当前方法的对象

class Foo:
    # 静态字段 使用场景为:每个对象中保存相同的属性时。
    # 公有属性
    country = '中国'
    def __init__(self, name, amount):
        # 普通字段
        # 普通属性
        self.NAME = name
        self.AMOUNT = amout

     def bar(self):
        pass

obj1 = Foo('Alex', 100000)
obj1.bar()

obj2 = Foo('Eric', 10000)
obj2.bar()
5.5

封装:
类中封装了字段、方法
对象中封装了普通字段的值

class F1:
    def __init__(self, n):
        self.N = n
        print('F1')

class F2:
    def __init__(self, arg1):
        self.a =arg1
        print('F2')

class F3:
    def __init__(self, arg2):
        self.b =arg2
        print('F3')

o1 = F1('Alex')
o2 = F2(o1)
o3 = F3(o2)
###### Print Alex ######
# o3 = F3(o2)
o3.b => o2
# o2 = F2(o1)
o3.b.a => o1
# o2 = F1('Alex')
o3.b.a.N

继承:

class F1(object):
    def __init__(self):
        print('F1')
 
    def a1(self):
        print("F1a1")
 
    def a2(self):
        print("F1a2")
 
class F2(F1):
    def __init__(self):
        print('F2')
 
    def a1(self):
        self.a2()
        print("F2a1")
 
    def a2(self):
        print("F2a2")
 
class F3(F2):
    def __init__(self):
        print('F3')
 
    def a2(self):
        print("F3a2")

obj1 = F3()
obj1.a1()
# F3a2
# F2a1

5.6

字段:

  • 普通字段(保存在对象中)
  • 静态字段(保存在类中)

方法:

  • 普通方法(保存在类中,调用者为对象,至少有一个self参数)
class F1:
    def __init__(self, name,......)
        self.name = name
        ...

    def a1(self):
        print(self,name,......)

obj = F1('Alex')
obj,a1()
class F1:
    def a1(self):
        print('Alex')
 
obj = F1()
obj.a1()
  • 静态方法(保存在类中,调用者为类,无需创建对象,可以有任意个参数)
class F1:
    @staticmethod
    def a1(self):
        print('Alex')

F1.a1()

六、作业:选课系统

角色:学校、学员、课程、讲师
要求:

  1. 创建北京、上海 2 所学校
  2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开
  3. 课程包含,周期,价格,通过学校创建课程
  4. 通过学校创建班级, 班级关联课程、讲师
  5. 创建学员时,选择学校,关联班级
  6. 创建讲师角色时要关联学校,
  7. 提供两个角色接口
    6.1 学员视图, 可以注册, 交学费, 选择班级,
    6.2 讲师视图, 讲师可管理自己的班级, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩
    6.3 管理视图,创建讲师, 创建班级,创建课程
  8. 上面的操作产生的数据都通过pickle序列化保存到文件里

你可能感兴趣的:(Day06 - 面向对象)