类和对象
- 对象作为程序的基本单位,将程序和数据封装于其中,以提高程序的重用性,灵活性和扩展性
- 类是创建对象的模板,而对象是类的实例化,一个类可以创建多个对象
- 类是对实体的抽象,是泛指,比如:动植物
- 对象:是类的一个实例,是特例,比如猫狗
1.1 类的创建
Python语言中,使用class关键字来创建类,其创建方式如下:
class ClassName(bases):
# class documentation string 类文档字符串,对类进行解释说明
class_suite
class是关键字,bases是要继承的父类,默认继承object类
class documentation string是类文档字符串,一般用于类的注释说明
class_suite是类体,主要包含属性和方法
类、属性和方法的命名约定惯例如下:
类名表示实例的抽象,命名时首字母大写;
属性使用名词作为名字,比如name、age、weight等;
方法名一般指对属性的操作,其命名规则一般采用动词加属性名称形式,如updataName、updataAge、updataWeight等。 举例如下图:
class People: # 类名
name='张三'
def undate_name(self,name): # 方法名
self.name=name
在Python3.x中,如果没有显示指明要继承的父类,则默认继承object类。
class A:
pass
class A():
pass
class A(object):
pass
Python3.x中,无需继承时,例子中三种类的创建效果一样,自动继承object类
新式类添加了一些内置属性和方法:
__name__:属性的名字
__doc__:属性的文档字符串
__get__(object):获取对象属性值的方法
__set__(object,value):设置对象属性值的方法
__delete__(object,value):删除对象属性的方法
1.2 对象的创建
类创建完之后,就应该创建该类的实例或对象了,该过程称之为实例化。当一个对象被创建后,就包含标识、属性和方法这三个方面的对象特性了。其中,对象标识用于区分不同的对象,属性和方法与类中的成员变量和成员函数相对应
people=People("李四",20,"50kg") # 实例化一个对象
如例子所示:对象标识符为people,属性为括号中内容,方法为类中方法
1.3 类的属性
Python语言中,属性分为类级别和实例级别两种,实例级别的属性值默认共享类级别的属性值,除非显式进行赋值操作
class A():
age=10
obj2=A()
Obj3=A()
如例子所示,存在三个实例,分别是类实例A和对象实例obj2,obj3
在情形1中,obj2和obj3这两个对象实例共享类实例A的属性age:
# 情形1
print(obj2.age,obj3.age,A.age)
在情形2中,显示修改了对象实例obj2.属性age:
# 情形2
obj2.age+=2
print(obj2.age,obj3.age,A.age)
在情形3中,修改了类实例A的属性age
# 情形3
A.age+=3
print(obj2.2age,obj3.age,A.age)
结果:
情景1:
10 10 10
情景2:
12 10 10
情景3:
12 13 13
在情形2中已经修改了对象实例obj2的属性值age,其属性值和类实例A的属性值已经独立。而对象实例obj3的属性从来没有修改过,所以它还是和类实例A的属性值保持一致。
Python语言对于属性的设置采用“类.属性 = 值”或“实例.属性 = 值”的形式。如上例中obj2.age += 2等价于obj2.age = obj2.age + 2,该语句包含了属性获取及属性设置两个操作。
Python语言中的属性操作遵循三个规则:
(1)属性的获取是按照从下到上的顺序来查找属性;
(2)类和实例是两个完全独立的对象;
(3)属性设置是针对实例本身进行的。
类的定义由属性和方法组成,属性是对数据的封装,方法则是对类行为的封装.
属性按使用范围分为公有属性和私有属性,使用范围取决于属性名称
类的属性如下表所示
类的属性 | ||
属性类别 | 介绍 | 备注 |
公有属性 | 类中和类外调用的属性 | 若无特别声明,变量默认为公有属性 |
私有属性 | 不能被类以外的函数调用的属性 | 命名以双下划线"__"开始的成员变量就是私有属性,可以通过instance._ClassName__attribute方式访问 |
内置属性 | 由系统在定义类的时候默认添加的 | 命名一般由前后各两个下划线"__"组成,如__doc__等 |
内置属性如下表所示:
内置类属性 | |
属性名 | 作用 |
__name__ | 类的名字 |
__doc__ | 类的文档字符串 |
__bases__ | 所有父类构成的元组 |
__dict__ | 类的属性 |
__module__ | 类定义所在的模块 |
__class__ | 实例对应的类(仅新式类中) |
内置实例属性 | |
属性名 | 作用 |
__class__ | 实例对象所属的类名 |
__dict__ | 实例对象的属性 |
1.4 类的方法
类方法包含公有方法,私有方法,类方法和静态方法
类的方法 | ||
方法类别 | 介绍 | 备注 |
公有方法 | 不能被类直接调用,需要使用实例化对象调用 | — |
私有方法 | 不能被外部的类和方法调用,其定义和私有属性的定义相同 | 需要在方法的前面加上双下划线"__" |
类方法 | 能被类所调用,也能被对象所调用 | 被classmethod函数调用或被@classmethod装饰器修饰的方法 |
静态方法 | 相当于"全局方法",可以被类直接调用,也可以被所有实例化对象共享. | 该方法通过调用staticmethod方法或被@staticmethod装饰器修饰来声明,不需要”self“语句 |
def Play(self): # 公有方法
self.__Study()
def __Study(self): # 私有方法
print("私有方法")
def Breath(self): # 类方法
print("类方法1-Breath_A")
Breath_A=classmethod(Breath_A)
def Cry(): # 静态方法
print("静态方法1-Cry_A")
Cry_A=staticmethod(Cry)
@classmethod # 第二种方式定义类方法
def Breath_B(self):
print("类方法2-Breath_B")
@staticmethod # 第二种方式定义静态方法
def Cry_B():
print("静态方法2-Cry_B")
类方法和静态方法原理上有以下区别:
- 静态方法不能使用self的方式调用
- 静态方法调用时会预先将类中用到的属性和方法进行加载,而类方法则是随调随用.因此,类方法相比静态方法具有不占资源的优势,但是速度不及静态方法
- 静态方法调用类中的属性时需要使用”类名.属性“的格式
1.5 内部类
内部类:类的内部定义的类,主要目的是为了更好抽象现实世界.
一般情况下不使用内部类,这样会使程序结构复杂,但是理解内部类有助于理解模块的调用
面例子中,People类中又定义了Father类和Mother类两个内部类。创建内部类的实例化对象,可以通过外部类的实例化对象调用内部类完成,如Lisi = Zhangsan.Father();也可以直接使用外部类名调用内部类,如Liming = People.Mother()。
class People():
code=0
class Father():
code=1
class Mother():
code=2
zhangsan=People()
lisi=zhangsan.Father() # 第一种实例化方法
print(lisi.code) # 输出结果:1
liming=People.Mother() # 第二种实例化方法
print(liming.code) # 输出结果:2
从例子可以看出,内部类调用有两种方式
- 直接使用外部类调用内部类
- 先对外部类进行实例化,然后再实例化内部类
1.6 魔术方法
在Python语言中,所有以双下划线“_”包起来的方法,都统称为“魔术方法”。
这些方法在实例化时会自动调用,
比如“str()”、“init()”、“del()”等。
使用魔术方法可以构造出非常优美的代码,比如将复杂的逻辑封装成简单的API等。
魔术方法中的“init()”方法一般叫做构造函数,用于初始化类的内部状态和参数。
如果不提供,Python语言会给出一个默认的“init()”方法
魔术方法中的“__ del __()”函数叫做析构函数,用于释放对象占用的资源。“del()”函数是可选的,
如果不提供,Python语言会在后台提供默认析构函数。
魔术方法中,有些可以实现属性访问控制的功能,如“getattr(self,name)”,
“setattr(self,name,value)”方法等。
魔术方法举例:
class People():
name="人"
def __init__(self,n="非洲人"):
self.name=n
print("我是构造函数",self.name) # 重写构造函数
def __del__(self):
print("我是析构函数",self.name) # 重写析构函数
zhangsan=People()
lisi=People("欧美人")
zhangsan.__del__() # 调用析构函数
print(zhangsan)
del zhangsan
# print(zhangsan) 出现错误,因为del后,对象就不存在了
如例子所示,对于这些魔术方法,在创建对象时可以自动执行。当对象自动执行析构函数“A.del()”后,对象仍然存在,但是在调用“del A”,后,对象就已经被回收删除,无法再次使用。
1.7 类间依赖关系
用实例方法执行某个功能时,如果需要使用另一个类的实例的方法来完成,则称这两个类之间存在关联关系.
class Person():
def play(self,tools):
tools.run()
print("很开心,能玩游戏了")
class Computer():
def run(self):
print("电脑开机,可以运行")
class Phone():
def run(self):
print("手机开机,可以运行")
c=Computer()
phone=Phone()
p=Person()
p.play(phone)
结果:
手机开机,可以运行
很开心,能玩游戏了
1.8 类间关联关系
一个类的属性类型是另外一个类的类型,则称这两个类之间存在关联关系。根据属性是单值或多值,关联关系又分为一对一关联、一对多关联等。一对一关联举例如下。
class Boy:
def __init__(self,name,girlFriend=None):
# 在初始化的时候可以给一个对象的属性设置成另一个类的对象
self.girlFriend=girlFriend # 一个男孩有一个女朋友
def meal(self):
if self.girlFriend:
print(f"带着他的女朋友{self.girlFriend.name}去吃饭")
else:
print("我单身,我快乐")
class Girl:
def __init__(self,name):
self.name=name
g=Girl("小红")
b=Boy("小明",g)
b.meal()
例子中代表的是一对一关联,Boy类中的属性girlFriend是Girl类的实例,这两个类之间存在一对一关联关系
一对多关联:
class School:
def __init__(self,name):
self.teach_list=[] # 这里要装多个对象
self.name=name
def zhaopin(self,teach):
self.teach_list.append(teach)
def shangke(self):
for t in self.teach_list:
t.work()
class Teacher:
def __init__(self,name):
self.name=name
def work(self):
print(f"{self.name}在上课")
lnh=School("Old Boy")
t1=Teacher("Mr.Wu")
t2=Teacher("TaiBai")
t3=Teacher("NeZha")
lnh.zhaopin(t1)
lnh.zhaopin(t2)
lnh.zhaopin(t3)
lnh.shangke()
1.9 继承关系
类继承是在已有类基础上构建新类的机制,该新建类也成为子类.子类可以增加新的属性或功能,也可以继承父类的功能.继承所描述的是"is-a"关系.
class People:
name='people'
def Study(self):
print("Study...")
def __init__(self):
print("This is a People.")
class Japan(People):
def __init__(self):
print("Japanese.")
def Study(self):
print("Japan Study.")
class China(People):
name="China"
def Study(self):
print("China Study.")
def __init__(self):
print("子类构造函数")
People.__init__(self)
# 为防止出现歧义,尽量在类定义时候避免多继承
class Ren(China,Japan):
pass
lisi=Japan()
zhangsan=China()
liming=Ren()
print(lisi.name)
print(zhangsan.name)
print(liming.name)
lisi.Study()
zhangsan.Study()
liming.Study()
print(Ren.name)
输出结果
Japanese.
子类构造函数
This is a People.
子类构造函数
This is a People.
people
China
China
Japan Study.
China Study.
China Study.
China
例子中,首先定义了一个People类,接着从该类派生出两个子类Japan和China,然后同时以这两个类为父类,派生出类Ren。一般来说,为防止出现歧义,尽量在类定义时候避免多继承。
在Python语言中使用继承机制时,有以下几点需要注意:
(1)子类拥有父类的属性和方法。
(2)子类可以创建自己属性和方法。
(3)子类可以对父类的方法进行覆盖实现。
(4)子类可重新定义父类中的属性。
(5)一个父类可由多个子类继承,一个子类也可继承多个父类。
(6)如果父类定义了__init__()方法,子类也定义了自己的__init__()方法并且还要使用父类的__init__()方法,
子类需要显式调用父类的__init__()方法。如果子类需要扩展父类的初始化行为,可以添加__init__()方法参数。
(7)当继承的多个父类中有相同的属性或方法时,会继承最前面的父类的属性或方法。
继承机制的出现,导致父类和子类相同行为出现的可能,以及在实际运行中的动态结果,此为多态。
多态(是从定义角度出发):同一类事物的多种形态,如猫的多种形态:白猫、黑猫、花猫等。
多态性(是从使用角度出发):同一种调用方式,不同的执行效果。具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
多态性依赖于继承机制,并且要定义统一的接口。
在程序语言中,多态实质就是定义了一个函数接口,在这个函数中定义了所有类中通用的功能。根据不同对象的相同调用接口,就得到不同的结果。下面例子是多态的实现。
class Animal():
def run(self):
raise AttributeError("子类必须实现这个方法")
class Cat(Animal):
def run(self):
print("猫在跑")
class Pig(Animal):
def run(self):
print("猪在跑")
class Dog(Animal):
def run(self):
print("狗在跑")
a=Cat()
b=Pig()
c=Dog()
a.run()
b.run()
c.run()
输出结果:
猫在跑
猪在跑
狗在跑
这种写法最大的问题就是接口不统一,有没有一种统一接口的写法呢?如下所示:
def func(n):
n.run()
a = Cat()
b = Pig()
c = Dog()
func(a)
func(b)
func(c)
例子中演示了多态性定义和调用的两种方式。也体现了多态依赖的两个步骤。首先,多态来自同一个父类的继承;其次,为这些不同的子类定义相同的调用接口。
使用多态性有如下优点:
(1)以不变应万变,增加了程序的灵活性。不论对象千变万化,调用方式都相同。
(2)增加了程序的可扩展性。对于子类来说,使用者无需更改调用代码。
2 类的基本使用
你想我猜
class Nxwc(object):
"""description of class"""
def __init__(self):
self.kv=KeyValueSet()
self.score=0
# A.
#def setup(self,sentences):
# #
# for sentence in sentences:
# self.append(sentence)
#def append(self,sentence):
# cut_pos=sentence.find(' ')
# first_word,rest=sentence[0:cut_pos],sentence[cut_pos+1:].strip()
# self.kv.set(first_word,rest)
#def guess(self,first_word):
# ret=self.kv.get(first_word)
# if ret is None:
# return 1,None
# else:
# return 0,set
# B.
#def setup(self,sentences):
# for sentence in sentences:
# cut_pos=sentence.find(' ')
# self.kv.set(sentence[0:cut_pos],sentence[cut_pos+1:].strip())
#def guess(self,first_word):
# value=self.kv.get(first_word)
# return 0,value if value else (1,None)
# C.
#def setup(self,sentences):
# for sentence in sentences:
# cut_pos=sentence.find(' ')
# first_word,rest=sentence[0:cut_pos],sentence[cut_pos+1:].strip()
# self.kv.set(first_word,rest)
#def guess(self,first_word):
# value=self.kv.get(first_word)
# err=0 if value else 1
# return err,value
# D.
def setup(self,sentences):
for sentence in sentences:
first_word,rest=self.parse_sentence(sentence)
self.kv.set(first_word,rest)
def parse_sentence(self,sentence):
cut_pos=sentence.find(' ')
return sentence[0:cut_pos],sentence[cut_pos+1:].strip()
def guess(self,first_word):
value=self.kv.get(first_word)
return 0,value if value else (1,None)
def run(self):
self.score=0
for first_word in self.kv.keys():
ret=input("猜一猜下半句是什么? {} ->:".format(first_word))
err, value=self.guess(first_word)
if value==ret:
print('你太厉害了,这都能猜到! +10分')
self.score+=10
else:
self.score-=2
print('哈哈,肯定猜不到得啦:{} -> {},扣除2分!'.format(first_word,value))
print('结束了,你本次得分:',self.score)
class KeyValueSet:
'''
Set/Get/keys接口
'''
def __init__(self)->None:
self.dict={}
def set(self,key,value):
self.dict[key]=value
def get(self,key):
return self.dict.get(key)
def keys(self):
return self.dict.keys()
if __name__=='__main__':
sentences=[
"hello world",
'monkey king',
'tomorrow is another day',
"good bye"
]
game=Nxwc()
game.setup(sentences)
game.run()
你想我猜扩展(一)
在上面的基础上。通过类的集成,可以扩展原有的类。例如继承KeyValueSet容器类,扩展功能,支持类似redis的 hset/hget/hkeys 扩展接口
class HashKeyValueSet(KeyValueSet):
def __init__(self)-> None:
super().__init__()
def hset(self,hash_key,key,value):
# A.
hash_set=self.dict.get(hash_key,{key:value})
hash_set[key]=value
self.set(hash_key,hash_set)
# B.
#self.set(hash_key,{key:value})
# C.
#hash_set=self.get(hash_key)
#if hash_set is None:
# hash_set={key:value}
#else:
# hash_set[key]=value
#self.set(hash_key,hash_set)
def hget(self,hash_key,key):
hash_set=self.get(hash_key)
if hash_set is None:
return None
else:
return hash_set.get(key)
def hkeys(self,hash_key):
hash_set=self.get(hash_key)
if hash_set is None:
return []
else:
return hash_set.keys()
if __name__=='__main__':
hashset=HashKeyValueSet()
hashset.hset('puzzle','hello','world!')
hashset.hset('puzzle','monkey','king!')
hashset.hset('puzzle','tomorrow','is another day')
hashset.hset('puzzle','good','bye!')
keys=hashset.hkeys('puzzle')
for key in keys:
ret=input('猜一下下半句是什么? {}->:'.format(key))
value=hashset.hget('puzzle',key)
if ret==value:
print('你太厉害了,这都能猜得到!')
else:
print('哈哈,肯定猜不到得啦:{}->{}'.format(key,value))
你想我猜扩展(二)
还可以通过类的组合,使用组合而非继承实现类的扩展,支持类似 redis 的 hset/hget/hkeys 扩展接口
相比于上面,下面代码使用组合的方式复合KeyValueSet的功能,从而实现HashKeyValueSet。
class HashKeyValueSet2:
def __init__(self,kvset)->None:
super().__init__()
self.kvset=kvset
def hset(self,hash_key,key,value):
#A.
#self.kvset.set(hash_key,{key:value})
#B.
hash_set=self.kvset.get(hash_key)
if hash_set is None:
hash_set={key:value}
else:
hash_set[key]=value
self.kvset.set(hash_key,hash_set)
#C.
#hash_set=self.kvset.get(hash_key)
#if hash_set is None:
# hash_set={}
# hash_set[key]=value
# self.kvset.set(hash_key,hash_set)
#else:
# hash_set[key]=value
# self.kvset.set(hash_key,hash_set)
def hget(self,hash_key,key):
hash_set=self.kvset.get(hash_key)
if hash_set is None:
return None
else:
return hash_set.get(key)
def hkeys(self,hash_key):
hash_set=self.kvset.get(hash_key)
if hash_set is None:
return []
else:
return hash_set.keys()
def test():
hashset=HashKeyValueSet2(KeyValueSet())
hashset.hset('puzzle','hello','world!')
hashset.hset('puzzle','monkey','king!')
hashset.hset('puzzle','tomorrow','is another day')
hashset.hset('puzzle','good','bye!')
keys=hashset.hkeys('puzzle')
for key in keys:
ret=input('猜一下下半句是什么? {}->:'.format(key))
value=hashset.hget('puzzle',key)
if ret==value:
print('你太厉害了,这都能猜得到!')
else:
print('哈哈,肯定猜不到得啦:{}->{}'.format(key,value))
if __name__=='__main__':
test()