本次内容主要参考廖雪峰老师官方网站内容,有不清楚遗漏地方请参考廖雪峰老师官网内容。
1. 类、实例和类的方法
面向对象编程通俗理解:
- 面向过程:自己靠双手按照流程解决问题
- 面向对象:找个对象,让她做,自己轻松了
- 对象是对客观事物的抽象,类是对对象的抽象
- 对象是类的实例,类是对象的模板
- 类的描述:类名,属性,方法
- 类名:类的名字
- 属性:类的属性,可以认为是在类里对象里定义的变量
- 方法:类的方法,是def 定义的,可以理解为函数
(1)类:类是创建实例的模板,定义一个类是通过class关键字;
(2)实例:是根据类创建出来的一个个具体的对象,各个实例拥有的数据都互相独立,互不影响;
(3)方法:就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
封装数据的函数(访问数据的函数)是和类本身关联起来的,称为类的方法;
每个对象都拥有相同的方法,但各自的数据可能不同
# 定义类Student
class Student(object):
pass
# 创建实例,根据student类创建出student的实例
bart = Student()
# 给实例变量绑定属性
bart.name = 'bank'
# 在创建实例时,通过__init__方法把必需的属性绑定上去,第一个参数永远是self,它表示创建的
# 实例本身,可以把各种属性绑定到self
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
# 类内部定义访问数据的函数,封装数据的函数和类本身关联起来就称为类的方法
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print("%s,%s" % (self.name, self.score))
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.print_score(), lisa.get_grade())
print(bart.print_score(), bart.get_grade())
结果如下:
Lisa,99
None A
Bart,59
None C
2. 属性的访问限制
#如果要让内部属性不被外部访问,就在属性的名称前加上两个下划线__,实例的变量名如果以__开头
#,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
bart = Student('Bart Simpson', 59)
print(bart.__name)
# 结果如下:(外部访问内部属性就会报错如下)
# AttributeError: 'Student' object has no attribute '__name'
# 如果外部代码要获取name和score,需要给Student类增加get_name和get_score这样的方法:
def get_name(self):
return self.__name
def get_score(self):
return self.__score
# 如果又要允许外部代码修改score,就需要再给Student类增加set_score方法
def set_score(self, score):
self.__score = score
需注意:在Python中,变量名类似__xxx__的,是特殊变量,特殊变量是可以直接访问的
,不是private变量,所以不能用__name__、__score__这样的变量名作为属性命名。
一个下划线开头的实例变量名,比如_name这样的实例变量外部是可以访问的,但是尽量不要随意访问
# 小习题:
# 将student类的gender字段对外隐藏,且使外部又可以访问和修改
class Student(object):
def __init__(self, name, gender):
self.__name = name
self.__gender = gender
def get_gender(self):
return self.__gender
def set_gender(self, gender):
self.__gender = gender
# 测试:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
print('测试失败!')
else:
bart.set_gender('female')
if bart.get_gender() != 'female':
print('测试失败!')
else:
print('测试成功!')
# 答案:测试成功!
3. 继承和多态
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
# 继承
class Animal(object):
def run(self):
print("animal is runing.....")
# Animal是父类,Cat/Dog是Animal子类,即:object -> Animal -> Dog/Cat
class Cat(Animal):
pass
class Dog(Animal):
pass
a = Animal()
dog = Dog()
print(dog.run())
cat = Cat()
print(cat.run())
# 多态
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
b=Dog()
print(b.run())
c=Cat()
print(c.run())
print(isinstance(b, Dog))
print(isinstance(b, Animal))
print(isinstance(c, Cat))
print(isinstance(c, Animal))
print(isinstance(a, Dog))
# 在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
# 但反过来就不行
结果如下:
animal is runing…
animal is runing…
++++++++++++++++++++++++++++++++++++
Dog is running…
Cat is running…
++++++++++++++++++++++++++++++++++++
True
True
True
True
False
#####小插曲之——获取对象信息
(1)基本类型判断用:type()
(2)若判断一个对象是否是函数:types()模块中定义的常量
(3)若判断class类型:isinstance()函数,当然isinstance函数也可以判断其他基本类型,还可以判断一个变量是否是某些类型中的一种,切记:总是优先使用isinstance()判断类型。
(4)若要获得一个对象的所有属性和方法使用:dir()函数,还可以配合getattr()、setattr()以及hasattr()函数使用,直接操作一个对象的状态
# type()及isinstance()使用
print(type(123),'\t',type('asd'),'\t',type(abs))
print(type('123')==str,'\t',type(123)==type('123'),'\t',type('asd')==type('123'))
print(isinstance([1, 2, 3], (list, tuple)))
# types模块使用
import types
def fn():
pass
print(type(fn),'\t',type(fn)==types.FunctionType,'\t',type(abs)==types.BuiltinFunctionType)
# 结果如下:
# <class 'int'> <class 'str'> <class 'builtin_function_or_method'>
# True False True
# True
# <class 'function'> True True
################################################################################
# dir的使用及获取具体属性及方法
class myobject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
print(dir(myobject)) #获取myobject类的所有属性及方法
obj = myobject()
print(hasattr(obj, 'x'), '\t', hasattr(obj, 'y')) # 判断是否含有某属性
setattr(obj, 'y', 20) # 设置一个属性
print(hasattr(obj, 'y'), '\t', getattr(obj, 'y')) # 获取某属性
print(hasattr(obj, 'power'),'\t',getattr(obj, 'power')) #获取对象的方法
print(obj.power())
# 结果如下:
# ['__class__', '__delattr__', '__dict__', '__dir__', 。。。。。']
# True False
# True 20
# True <bound method myobject.power of <__main__.myobject object at 0x00000000028CE278>>
# 81
4. 实例属性和类属性
实例属性属于各个实例所有,互不干扰;
类属性属于类所有,所有实例共享一个属性;
不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
# 类属性\实例属性
class Student(object):
name = 'Student' #类属性
s=Student()
print(s.name) # 因为实例并没有name属性,所以会继续查找class的name属性
print(Student.name) # 打印类的name属性
s.name = 'Michael' # 给实例绑定name属性
print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
del s.name # 如果删除实例的name属性
print(s.name) # 由于实例的name属性没有找到,类的name属性就显示出来了
结果如下:
Student
Student
Michael
Student
# --------小练习-------
class Student(object):
count = 0 # 类属性
def __init__(self, name):
self.name = name
Student.count += 1
# 测试:
if Student.count != 0:
print('测试失败!')
else:
bart = Student('Bart')
if Student.count != 1:
print('测试失败!')
else:
lisa = Student('Bart')
if Student.count != 2:
print('测试失败!')
else:
print('Students:', Student.count)
print('测试通过!')
5. 动态绑定方法到类
创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性
# 定义一个Student类
class Student(object):
pass
s = Student
s.name = 'jeck' # 动态给实例绑定一个属性
print(s.name)
# 打印出:jeck
def set_age(self, age): # 定义一个函数作为实例方法
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 动态给实例绑定一个方法
s.set_age(26) # 调用实例方法
print(s.age)
# 打印出:26
# 动态绑定是在程序运行的过程中动态给class加上方法
Student.set_age = set_age # 给class动态绑定一个方法
s2 = Student() # 实例化
s2.set_age(30) # 调用实例方法
print(s2.age)
# 打印出:30
# 动态绑定过程中限制所绑定实例的属性
# 定义class的时候,定义一个特殊的__slots__变量,限制该class实例能添加的属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student()
s.name = 'jaspuer'
s.age = 26
s.score = 100 # 此句报错,由于class定义时score没有被放入__slots__中
# 注意:__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用
6. 高级特性
####(1)@property装饰器
Python内置的@property装饰器就是负责把一个方法变成属性调用
既能检查参数正确性,又可以用类似属性这样简单的方式来访问类的变量
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0~100')
self._score = value
s = Student()
s.score = 60 # 实际转化为s.set_score(60)
print(s.score) # 实际转化为s.get_score()
# 结果为:60
# 定义只读属性
class Student(object):
@property
def birth(self):
return self._birth
# birth是可读写属性
@birth.setter
def birth(self, value):
self._birth = value
# age就是一个只读属性
@property
def age(self):
return 2015 - self._birth
7. 三种定义类方法的方式
常规方式, @classmethod修饰方式, @staticmethod修饰方式
class A(object):
def foo(self, x):
print("executing foo(%s,%s)" % (self, x))
print('self:', self)
@classmethod
def class_foo(cls, x):
print("executing class_foo(%s,%s)" % (cls, x))
print('cls:', cls)
@staticmethod
def static_foo(x):
print("executing static_foo(%s)" % x)
普通的类方法foo()需要通过self参数隐式的传递当前类对象的实例
@classmethod修饰的方法class_foo()需要通过cls参数传递当前类对象
@staticmethod修饰的方法定义与普通函数是一样的
self和cls的区别不是强制的,只是PEP8中一种编程风格,slef通常用作实例方法的第一参数,cls通常用作类方法的第一参数。即通常用self来传递当前类对象的实例,cls传递当前类对象。
示例:
class cal:
cal_name = '计算器'
def __init__(self,x,y):
self.x = x
self.y = y
@property #在cal_add函数前加上@property,使得该函数可直接调用,封装起来
def cal_add(self):
return self.x + self.y
@classmethod
#在cal_info函数前加上@classmethon,则该函数变为类方法,
#该函数只能访问到类的数据属性,不能获取实例的数据属性
def cal_info(cls): #python自动传入位置参数cls就是类本身
print('这是一个%s'%cls.cal_name) #cls.cal_name调用类自己的数据属性
@staticmethod #静态方法 类或实例均可调用
def cal_test(a,b,c): #改静态方法函数里不传入self 或 cls
print(a,b,c)
c1 = cal(10,11)
cal.cal_test(1,2,3) #>>> 1 2 3
c1.cal_test(1,2,3) #>>> 1 2 3