Ljohn

Ljohn's blogs

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

引子



假设有一个项目:人狗大战,需要创建2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述这种不同的角色和他们的功能呢?

根据之前所学,用下面代码实现

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:面向对象引子:人狗大战

# 首先通过如下方法,创建两个角色
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,
'dog_type':dog_type
}
return data

#定义函数创建几个方法,通过调用方法实现狗bark,人walk
def bark(d):
print("Dog %s: 2018年旺旺旺旺!!" %d['name'])

def walk(p):
print("Person %s: 行走在运维开发的路上!!"%p['name'])

#生成人和狗的两个实际对象
d1 = dog("孙伟","哈士奇")
p1 = person("王昆",19,"Fmale","开发")
p2 = person("王海涛",21,"Male","运维")

walk(p1),bark(d1) #简直完美,实现了人走,狗叫。
# 一不小心使用bark调用了p1 那就尴尬
bark(p2)

所以,我们要限制方法调用

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:人狗大战第二版,限制方法调用(walk,bark)

def person(name,age,sex,job):
def walk(p):
print("Person %s: 行走在运维开发的路上!!" % 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: 2018年旺旺旺旺!!" % d['name'])
data = {
'name': name,
'type': dog_type,
'bark':bark
}

return data

#生成人和狗的两个实际对象
d1 = dog("孙伟","哈士奇")
p1 = person("王昆",19,"Fmale","开发")
p2 = person("王海涛",21,"Male","运维")

d1['bark'](p1) #把人的对象传给了狗的方法
p1['walk'](d1) #把狗的对象传给了人的方法

好了,完美解决了问题。但是如果是再来个复杂的问题,程序又得重写。显然我们不建议这么做下去。

面向过程 VS 面向对象

变成范式:

编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,实现一个任务的方式有很多种不同的方式,对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程,然后还有一个函数式编程。

面向过程编程(Procedural Programming)

面向过程编程,就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改,随着程序越来越大,这种编程方式的维护难度会越来越高。
但是面向过程依然是有可取之处的,如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护的,那还是用面向对象最方便了。

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

Object-Oriented Programming(OOP)编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率。另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

面向对象编程介绍

无论用什么形式来编程,我们都要明确记住以下原则:
1. 写重复代码是非常不好的低级行为
2. 你写的代码需要经常变更

面向对象的核心特性

  1. Class 类
  2. Object 对象
  3. Encapsulation 封装
  4. Inheritance 继承
  5. Polymorphism 多态


 

Class 类 和 Object 对象

类:一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法。
对象:一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性。
首先要定义类,然后将类实例化,最后通过这个实例来调用类中的功能
这里类名要用大驼峰规范来命名(下划线开头也可以)

# class 是定义类,Dog1是类名,括号中object 暂时知道必填就好,后面会介绍
class Dog1(object):
print("Hello,I am a dog1.")
# 上面定义好了类,下面我来将类实例化
d = Dog1() # 但是会把print 打印出来

# 定义一个Dog2函数,把print 写到函数,避免被直接调用执行
class Dog2(object):
def sayhi(self):
print("Hello,I am a dog2.")
d = Dog2() # 这是这里就不会直接打印了
d.sayhi() # 这样来调用执行类里面的sayhi

说明:
d = Dog() 这步叫实例化。先去实例化,然后 d.sayhi() 再去调用它的功能,但是,没有传参数,接下来我们通过下面的例子实现

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:改写人狗大战,类调用的方式

class Dog(object):
# 参数要写在 __init__ 这个函数里,这个叫构造函数或构造方法
def __init__(self,name,dog_tpye):
self.name = name # 将传入的输出传给self
self.type = dog_tpye
# 下面的函数叫做类的方法
def sayhi(self):
print("Hello,I am a dog,My name is",self.name)
def bark(self):
print(self.name,"2018年旺旺旺旺!!" )

class Person(object):
def __init__(self,name,age,sex,job):
self.name = name
self.age = age
self.sex = sex
self.job = job

def walk(self):
print(self.name,"行走在运维开发的大路上!!昂首挺胸!!")
#实例化
d1 = Dog('刘承梅',"比熊")
d1.sayhi() # 调用类方法
d1.bark()
p1 = Person('刘健',18,"Fmale","运维开发")
p1.walk()

说明:
  ● self,就是实例本身,实例化时,python会自动把这个实例本身通过self参数传进去。例如,上面的d1 就是self。通俗一点,self就是调用当前方法的对象。
  ● 实例化后产生的对象,就叫实例,是这个类的实例。
  ● d1 = Dog('刘承梅',"比熊") 这个就是进行实例化,产生了Dog这个类的一个实例d1,而self就是这个实例本身
  ● __init__叫做构造函数,或者叫构造方法,也就是初始化的方法
  ● sayhi() 函数叫做类方法,我们可以根据需要写多个方法

'''一个完整的我们写一个给狗吃东西的函数food,把食物通过参数传入
再写一个给狗改名字的函数,这里牵涉到修改对象的属性值,也就是初始化的内容可以后期修改
再写一个自报名字的函数,看看改名的效果'''

class Dog(object):
def __init__(self,name,d_type,color):
self.name = name
self.type = d_type
self.color = color
def brak(self):
print("Dog %s: 2018 年狗年旺旺~~~!" %self.name)
def eat(self,food):
print("%s 正在吃 %s" %(self.name,food))
def rename(self,new_name):
"给狗狗改名字"
print("%s 改名字 %s" %(self.name,new_name))
self.name = new_name # 这里改变了对象的属性
def say_name(self):
"报名字"
print("我的名字: %s" % self.name)

d1 = Dog('旺财','中华田园犬','黑色')
d1.brak()
d1.eat("鸡骨头") # 把 鸡骨头 传给了food
d1.say_name() # 当前的名字
d1.rename("大黑") # 改名字
d1.say_name() # 再看看名字是不是改变了

总结:
类 --> 实例化 --> 实例(对象): 类经过实例化后变成了实例也就是对象
  ● __init__:构造函数
  ● self.name = name :属性,或者叫成员变量、字段
  ● def bark(self) :方法,或者叫动态属性

Encapsulation 封装

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

私有属性

'''
之前使用的属性并不是公有属性,而是叫成员属性,这些属性是只属于它的对象的。公有属性最后讲。
私有属性在类的外部不不可访问的。我们来增加一个私有属性life_value,然后写一个函数来操作life_value这个私有属性。
定义私有属性使用self.__属性名
'''

class Dog(object):
def __init__(self,name,d_type,color,life_value):
self.name = name
self.type = d_type
self.color = color
self.__life_value = life_value # 定义为私有属性
def be_hit(self,damage):
"造成伤害,扣除life_value"
print("%s 受到了 %d点 伤害" %(self.name,damage))
print("当前的生命值:%d, 伤害:%d, 剩余生命值:%d"
%(self.__life_value,damage,self.__life_value-damage))
self.__life_value -= damage

d1 = Dog('小梅','博美','白色',100)
print("name",d1.name) # 公有属性可以正常获取到
#print(d1.__life_value) # 这句会报错,类的外部是获取不到私有属性的
d1.be_hit(10) # 这里是通过类内部的方法打印的私有属性的数值
d1.be_hit(40)

既然可以通过内部方法访问私有属性,我们可以将私有属性写到函数里return,提供一个给外部访问的方法,但是不能修改。另外其实也是有方法可以强制访问的。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:在类内部提供方法来访问私有属性

class Dog(object):
def __init__(self,name,d_type,color,life_value):
self.name = name
self.type = d_type
self.color = color
self.__life_value = life_value # 定义为私有属性
def be_hit(self,damage):
"造成伤害,扣除life_value"
print("%s 受到了 %d点 伤害" %(self.name,damage))
print("当前的生命值:%d, 伤害:%d, 剩余生命值:%d"
%(self.__life_value,damage,self.__life_value-damage))
self.__life_value -= damage
def get_life_value(self):
"提供方法,返回私有属性值的值"
return self.__life_value

d1 = Dog('小梅','博美','白色',100)
print(d1.get_life_value()) # 通过内部提供的方法来访问私有属性
print(d1._Dog__life_value) # 其实也可以强制访问到私有属性,并这个是可以修改私有属性值的

注意:对象名._类名__私有属性名 : 强制访问私有属性,可读写

公有属性

公有属性,所有属于这个类的对象都可以访问的属性,才叫公有属性。
之前的例子中,类Dog定义了2个对象d1 和 d2 ,但是d1里的属性是只属于d1的,无法通过d2来访问,这些都是叫成员
属性,在类里直接定义的属性,既公有属性。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:公有属性及成员属性练习

class Dog(object):
called = "" # 在类里面定义公有属性
def __init__(self,name,d_type,color):
self.name = name
self.type = d_type
self.color = color
d1 = Dog("旺财","中华田园犬","黑色")
d2 = Dog("小梅","博美","白色")
print(d1.called)
print(d2.called)

Dog.called = "" # 通过类更改类公有属性
print(d1.called) # 通过类修改,所有对象都会变
print(d2.called)

d1.called = "看们狗" # 通过对象更改,其实这句是新建了d1里的成员属性
# 同时有成员属性和公有属性,则使用成员属性
print(d1.called) # 这里打印的是刚才新建的成员属性
print(d2.called) # 这里还是原来的公有属性

Dog.called = "dog" # 再通过类更改公有属性
print(d1.called) # 这里没有变化,因为成员属性还在,并且没变
print(d2.called) # 这里变化了,因为这里只有公有属性

del d1.called # 把成员属性从内存中清除
print(d1.called) # 现在全部是公有属性值了
print(d2.called)

d1.called = "看们狗"
d2.called = "导盲犬" # 现在全部都有成员属性了
print(d1.called)
print(d2.called)
print(Dog.called) # 直接通过类而不是对象获取的一定是公有属性

这里公有属性和成员属性的情况和之前学的全局变量和局部变量是一样的效果。

类中def定义的所有函数我们也可以理解为是公有的。那么也可以通过定义成员的方法来替换原来的公有的方法:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Ljohn
# Description:
class Dog(object):
called = ""
def __init__(self,name,d_type,color):
self.name = name
self.type = d_type
self.color = color
def bark(self):
print("Dog: %s 2018 旺旺旺~~!" %self.name)

"""
在类外面再定义一个函数,这里无法使用self传入变量了
如果要传入参数,那么下面调用的时候也得带参数传入,这样调用也就变了
"""
def bark():
"自己再定义一个bark"
print("TEST...")
d1 = Dog('旺财','中华田野犬','')
d2 = Dog('小七','拉布拉多','')
d1.bark() # 正常调用类中的方法
d2.bark()

d1.bark = bark # 通过这个方法来实现给d1一个自己的成员方法
d1.bark() # 现在调用的不是公有方法,而是d1自己的成员方法
d2.bark()

del d1.bark # 清除d1的bark方法后,d1有可以正常调用类的bark方法了
d1.bark()
d2.bark()

析构方法 和 构造方法

方法和函数:在类里面定义的函数就是这个类的方法,所以方法和函数这两个词有时候会混用,主要看你是在描述什么东西

构造方法:之前在为类传入参数的时候用到了构造函数,构造函数其实就是在生成1个实例的时候自动运行的函数,所以通过构造函数我们可以实现在生成实例的时候自动把参数传递给self

析构方法:和构造方法差不多,就是在一个实例被销毁的时候自动运行的函数

class test(object):
def __init__(self): #构造函数
print("init in the test")
def __del__(self): #析构函数
print("del in the test")
input("准备实例化对象")
obj = test()
input("准备销毁对象")
del obj
input("执行完毕")

把一些收尾的工作写在析构函数里,在你销毁这个对象的时候就自动执行,比如关闭所有的客户连接、关闭所有打开的文件等等。具体怎么用得到了以后用的时候才知道了。

Inheritance 继承

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
  ● 要同时继承多个类有2种方法:多重继承和多级继承
  ● 继承概念的实现方式主要有2种:实现继承、接口继承。
抽象类:仅定义将由子类创建的一般属性和方法。
OOP开发范式大致为划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
简单继承例子

class People(object):
def talk(self):
print("Hello World")
class Chinese(People): #继承父类People
pass #什么都不写,直接继承父类的方法
h1 = Chinese() #实例化一个chinese 的对象
h1.talk() #虽然Chinese 没有talk方法,但继承了People的talk方法

子类也可以有自己的方法,还可能重构父类的方法:

class People(object):
def talk(self):
print("Hello World")
class Chinese(People): #继承父类People
"给自己编写一个方法"
def greatWall(self): #定义一个新方法
print("长城在中国!")
def talk(self): #重构父类的方法
print("你好,世界")
h1 = Chinese() #实例化一个Chinese的对象
h1.talk() #调用的是重构后的新方法
h1.greatWall() #调用子类中定义的新方法

再加上类的参数:
先只写上父类的构造函数,子类不写。子类就是完全继承父类构造函数。

class People(object):
def __init__(self,name,age,sex): #父类里有构造函数
self.name = name
self.age = age
self.sex = sex
def talk(self):
print("Hello,my name is %s" %self.name)
class Chinese(People):
pass #子类没有构造函数,就直接继承父类的构造函数
h1 = Chinese("刘德华",23,"Fmale") # 实例化的,需要根据父类的构造函数,传递参数。
h1.talk()

再来看子类的属性,上面是完全继承父类的属性,那么就不用写构造函数。
也可以完全无视父类的属性,那么直接重构自己的构造函数就好了。
复杂一点情况,继承父类的属性,但是还要有自己的额外属性

class People(object):
def __init__(self,name,age,sex): #父类里有构造函数
self.name = name
self.age = age
self.sex = sex
def talk(self):
print("Hello,my name is %s" %self.name)

class Chinese(People):
def __init__(self,name,age,sex,kungfu):
# People.__init__(self,name,age,sex) # 调用父类的构造函数,实现继承
super(Chinese,self).__init__(name,age,sex) # 和上面那句效果一样,用这种新式类写法
self.kungfu = kungfu # 再增加自己重构的属性
def talk(self): #重构父类的方法,但是要保留父类里的部分
People.talk(self) #方法也可以和属性一样,实现继承和重构
print("你好,我叫%s"%self.name)

h1 = Chinese("刘德华",23,"Fmale","武当") # 实例化的,需要根据父类的构造函数,传递参数。
h1.talk()
print(h1.__dict__) #可以查看对象所有的属性和值,以字典的形式

子类要继承父类属性并且有自己的特有属性,需要先继承,再重构。通过父类的名字调用执行父类的构造函数,实现继承,然后可以在后面写上自己需要重构的代码。
函数也是可以用这个方法来调用父类的方法,添加自己的代码,实现在父类的基础上重构自己特有的部分
__dict__ 属性:可以查看对象所有的属性和值,以字典的形式。

多继承

class Chinese(People): 这个是继承的语法,括号中的变量可以传入多个类,用逗号隔开就实现了多继承。比如:class Chinese(People,Human):
下面也是在多继承的时候,才会有区别的内容

新式类和经典类

python3里已经没有这个问题了,并且现在都是使用新式类的写法。不过还是举个例子说明一下继承的顺序,这个是多继承的情况下会遇到的问题。

定义一个基类A(父类),然后是A的两个子类B和C,最后来个孙子类D,D要继承B和C。每个类里都定义一个属性n,写在构造方法里

# Description:多继承
class A(object):
pass
def __init__(self):
self.n = "A"
class B(A):
pass
def __init__(self):
self.n = "B"
class C(A):
pass
def __init__(self):
self.n = "C"
class D(B,C):
pass
# def __init__(self):
# self.n = "D"
d1 = D()
print(d1.n)

如果D有构造方法,那么结果一定是D,然后依次将上面的构造方法也注释掉,看看D的继承顺序。结果是B-C-A。这个叫广度查找。

经典类依次的顺序为B-A-C,在python2里才会有深度查找的效果。在python3里还是广度查找
  ● 新式类,广度查找 B-C-A
  ● 经典类,深度查找 B-A-C

语法上的区别,都用新式类就好了,经典类知道一下,看到的时候别不认识。

定义类的语法:
class A: # 经典类写法,python2里和下面的写法有区别。python3里不必显示声明,这么写也是新式类了。
class A(object): # 新式类写法,咱就这么写

调用父类方法的语法:
Human.__init__(self,name,age) # 经典类写法,这个是子类构造函数里实现先继承的那句代码
super(Chinese,self).__init__(name,age) # 新式类写法,咱就这么写
不单是构造函数,其他函数也一样,尽量都super,不是多继承的话两个都一样。但是绝对不要混用。

Polymorphism 多态

多态,简单点说:"一个接口,多种实现",指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员

说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。

举例:Pyhon 很多语法都是支持多态的,比如 len(),sorted(), 你给len传字符串就返回字符串的长度,传列表就返回列表长度。

讲了那么多,到底是要通过多态做什么?就是要,通过父类调子类,python不直接支持,但是可以间接支持。

class People(object): # 先定义一个基类
def talk(self): # 基类的talk方法,我们不希望被调用,写一个会抛出错误的代码
"如果基类的这个方法被调用,就抛出一个错误"
raise NotImplementedError("Subclass must implement abstract method")
class Chinese(People): # 这个是子类
def talk(self): # 重构talk方法
print("你好,世界")
class American(People):
def talk(self):
print("Hello World")
# 如果调用了基类的方法,会根据raise里定义的,抛出一个错误。去掉下面的注释测试一下
# p1 = People() # 实例化一个基类
# p1.talk() # 调用基类的talk方法

# 实例化2个对象
c1 = Chinese()
a1 = American()
# 通过子类调用自己的方法当然没问题。要用多态就是要使用统一的接口来实现这2条命令
c1.talk()
a1.talk()

# 多态是要用父类调用子类
#People.talk(c1) # 这样是最好的,真正的直接实现多态的方法,但是Python不支持
#People.talk(a1)

# 间接支持多态的方法,定义一个函数作为统一的接口
def People_talk(obj):
obj.talk()
# 用新定义的接口,调用不同的子类,每次用的都是这个子类里重构的那个方法
People_talk(c1) # 传入一个c1对象,实际就是执行c1.talk()
People_talk(a1) # 传入一个a1对象,实际就是执行a1.talk()

间接支持多态,新定义一个函数,用参数传入一个对象。然后再函数中调用这个对象的一个方法。

主动抛出一个错误,上面在基类里使用了一个raise命令,可以实现主动触发一个错误,可以定义这个错误的类型和消息。这里的作用就是验证这个方法没有被调用到,貌似一般都是用一句print来验证的,这个也很高级。

面向对象补充

学了那么多面向对象,但是什么时候用呢?毕竟在python里我们使用面向过程的方法也是一样可以实现的。总结3种优势
  1、代码重用(根据一个模板来创建某些东西的时候)
  2、纵向扩展
  3、横向扩展

代码重用,一大优势,大家都知道。我们来简单总结一下剩下两个优势

纵向扩展:对一个对象有多个不同的操作,比如连接一个服务器、执行一条命令、上传一个文件、断开与服务器的连接。把这种对同一个对象执行的不同的操作写在一个类里,每一种操作就是类里的一个函数

横向扩展:原本有很多个函数都需要传公共的参数的时候,可以都写到一个类里。比如有很多个操作都需要和服务器交互,那么就都会需要地址、端口、密码这些参数,然后不同的方法又需要不同的其他参数。每次定义函数以及之后调用函数都会重复的引用这几个重复的参数。

面向过程与面向对象比较
# 面向过程定义3个函数,其中都会用到3个一样的参数

def f1(host,port,pwd,arg1):
pass
def f2(host,port,pwd,arg1,arg2):
pass
def f3(host,port,pwd,arg1,arg2,arg3):
pass
# 调用的时候也要反复的来引用这些参数
f1(1,2,3,4)
f2(1,2,3,4,5)
f3(1,2,3,4,5,6)
# 面向对象来做同样的事情,重复的参数写到构造方法里
class Foo(object):
def __init__(self,host,port,pwd):
self.host = host
self.port = port
self.pwd = pwd
def f1(arg1):
pass
def f2(arg1,arg2):
pass
def f3(arg1,arg2,arg3):
pass
# 调用的时候先把重复的参数写在一个对象里,然后可以分别调用这个对象的不同的方法
obj = Foo(1,2,3)
obj.f1(4)
obj.f2(4,5)
obj.f3(4,5,6)

类中的其他方法

静态方法

类中的函数我们叫方法,默认在类中定义的函数都是保存在类中,要调用这个方法需要通过对象。叫做实例方法,就是必须是实例化之后才能使用的方法,之前都是这种实例方法。

静态方法,就是一种普通函数,保存在类中,可以通过类来调用。使用装饰器@staticmethod定义静态方法。

class Foo(object):
def f1(self): # 这里的self参数是必须的
print("test in f1")
@staticmethod # 这个是静态方法
def f2(): # 这里的参数不是必须的了
print("test in f2")
Foo.f2() # 并没有创建对象,直接通过类调用了静态方法,类似函数了
obj.f2() # 当然由于有继承,通过对象也能够调用类中的静态方法
obj = Foo()
obj.f1()

上面的例子中,f1是实例方法,f2就是静态方法。这里的f1不要这么用,因为这个方法里并没有用到对象里面的任何东西。但是这么写,要调用f1必须得先实例化一个对象,但是其实并不需这个对象,而且还浪费空间。所以这种情况下,按照f2那样定义成一个静态方法会更好。

在别的纯面向对象的语言里,也是提供静态方法的。通过这种静态方法,可以让我们直接就能执行这个方法了。
另外除了静态方法和实例方法,还有一个类方法,这个貌似和静态方法在python里差不多,下面直接看看区别。

静态方法 和 类方法的区别

类方法可以将类作为参数传入,在继承的时候,子类会优先调用子类的属性和方法。

静态方法,无法传入类和对象,所以无论在哪个级别,永远都是调用基类的属性和方法

class Father(object):
test = "This is in Father"
@classmethod
def test_classmethod(cls):
print(cls.test) # 类方法,可以将类作为参数
@staticmethod
def test_staticmethod():
print(Father.test) # 静态方法,没有参数传入,只能用自己的类名,调用自己这个类的属性

class Son(Father):
test = "This is in Son"

Father.test_classmethod()
Father.test_staticmethod()
Son.test_classmethod() # 继承后的子类的类方法的结果不同了
Son.test_staticmethod()

类方法和静态方法的作用就是可以通过类来调用类中的属性和方法,而不需要先进行实例化。在还没有实例化生成对象的时候,只能调用类中的这两种方法。

类的组合关系

类与类之间除了可以是继承关系,还可以有组合关系。比如有两个类,学生和老师,那么可以在这之上在定义一个学校成员类,把学生和老师公有的属性和方法放在学校成员类里,作为父类。而学生和老师就作为两个子类继承父类。这是之前讲的继承,类与类之间是一种属于的关系。

现在又有一个类,课程类。课程不属于任何人,但是老师教授的课程,学生学习的课程。我们希望可以通过老师来获得他所教授的课程,也就是获得课程类的属性和方法。所以课程和老师之间又需要有这某种关系,这是就需要用到组合关系。

假设一个老师只教授一门课程,先搞清楚组合关系,不考虑教多门课程
如果没有组合关系,我们大概可以把课程的属性作为老师的属性,如下:

class Teacher(object):
def __init__(self,name,age,course_name,period,title):
"名字,年龄,课程名,课时,职称"
self.name = name
self.age = age
self.course_name = course_name
self.period = period
self.title = title

当然我们还会有学生类,这会用到这些属性,除了职称。通过继承关系我们可以把名字和年龄作为一个父类,让老师和学生继承,但是课程和课时就无法利用继承关系实现代码的重复使用了。

class Course(object):
def __init__(self,name,period):
"课程名、课时"
self.name = name
self.period = period
class People(object):
def __init__(self,name,age):
"名字,年龄"
self.name = name
self.age = age
class Teacher(People):
def __init__(self,name,age,title):
"名字,年龄,职称"
super(Teacher,self).__init__(name,age)
self.title = title
class Student(People):
def __init__(self,name,age):
super(Student,self).__init__(name,age)
t1 = Teacher("Lucy",23,"教授")
print(t1.name,t1.age,t1.title)

上面只是把继承关系写好了,单独把课程定义为了一个类,但是并没有把课程组合和别的类组合起来。

方法一:先把课程实例化,将实例化后的课程作为People类的一个属性传入

class Course(object):
def __init__(self,name,period):
"课程名,课时"
self.name = name
self.period =period
class People(object):
def __init__(self,name,age,course):
"名字,年龄,课程"
self.name = name
self.age = age
self.course = course
class Teacher(People):
def __init__(self,name,age,course,title):
super(Teacher,self).__init__(name,age,course)
self.title = title # 教师比学生多一个职称属性
class Student(People):
def __init__(self,name,age,course):
super(Student,self).__init__(name,age,course)

c1 = Course("Python",17) # 先实例化一个课程
t1 = Teacher("Ljohn",23,c1,"教授") # 课程是教师的一个属性
print(t1.course.name,t1.course.period) # 通过教师实例来调用课程类的属性

方法二:不用实例化课程,而是在People类的构造方法里完成课程类的实例化。这样需要在实例化教师的时候,将课程类的属性一起传入

class Course(object):
def __init__(self,name,period):
"课程名,课时"
self.name = name
self.period =period
class People(object):
def __init__(self,name,age,course_name,period):
self.name = name
self.age = age
self.course = Course(course_name,period) # 在构造函数里完成实例化class Teacher(People):
"名字,年龄,课程,课时,职称"
def __init__(self,name,age,course_name,period,title):
super(Teacher, self).__init__(name,age,course_name,period) 
self.title =title
class Student(People):
def __init__(self,name,age,course_name,period):
super(Student, self).__init__(name,age,course)
t1 = Teacher("Ljohn",23,"python",17,"教授") # 需要将课程类的属性在实例化的时候一起传入
print(t1.course.name,t1.course.period) # 调用方法都是一样的

组合同样也是为了减少重复代码。把另一个类的实例作为自己的属性完成实例化(方法一),或者在实例化的时候同时完成了另一个类的实例化来用作自己的属性(方法二)。之后就可以通过一个类来调用被组合的类的属性和方法了。

上面只举例了调用一个被组合的类的属性,要使用方法也是一样的。另外例子中老师和学生都是会上多门课的,这里只要引入数组的概念。这是之前学过的概念,关键是要把所有的知识点融会贯通。

作业:选课系统

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

posted on 2018-02-25 23:27  ljohnmail  阅读(212)  评论(0编辑  收藏  举报