09-类与对象
面向对象编程的三大特性,分别是:封装、继承、多态。
1. 类的调用
将一些具有相同或者类似性的方法封装到一个类中,这样要是想要使用这个类中的某种功能时,首先要先创建一个类的实例化对象,其次使用这个实例化对象来调用类中的方法。
class LaoGou:
def __init__(self,name,age,gender): # __init__()方法被成为构造方法。在这里将参数打包,在调用时只需传一次参数即可。
self.name = name
self.age = age
self.gender = gender
def kc(self):
data = "%s,性别%s,今年%s岁。" % (self.name,self.gender,self.age)
print(data)
def db(self):
data = "%s,性别%s,今年%s岁。" % (self.name,self.gender,self.age)
print(data)
def bj(self):
data = "%s,性别%s,今年%s岁。" % (self.name,self.gender,self.age)
print(data)
obj = LaoGou("Tom","20","男")
obj.kc()
obj.db()
obj.bj()
2. 封装
封装,指将某一个抽象概念可能存在的行为 或者是放功能,进行一定程度上的封装,将他们封装到一个类中,方便在之后进行调用。
在进行封装的时候,如果类中的各个方法存在相同的参数,就可以将这些相同的参数拿出来,进行一次打包,写进__init__()
方法中,这样在进行调用方法并传参时,只需要将参数传递给类的实例化对象即可。这样可以省却很多传参的时间。
同样的,如果存在需要进行重复多次的操作,也可以单独拿出来,写进__init__()
方法中,这样在创建实例化对象的时候,首先会先去执行__init__()
方法,这样也就在创建实例化对象的同时,将这些操作完成,会节省大量的代码运行的时间。
也方便在程序执行完之后整体结束这些重复的程序。
class FileHandle:
def __init__(self,file_path):
self.file_path = file_path
self.f = open(self.file_path,mode="r",encoding="utf-8") # 每一次对文件进行读取时,都需要打开文件,将打开文件操作写进__init__构造方法中,使每一次创建一个实例对象时首先去调用它,这样节省运行的时间。
def read(self):
content = self.f.read()
print(content)
obj1 = FileHandle("log")
obj1.read()
obj1.f.close()
在同一个类中的方法可以互相的进行调用:
class UserInfo:
def __init__(self):
self.name = None
self.password = None
def user_info(self):
print("当前用户:%s,用户密码:%s" % (self.name,self.password))
def account(self):
print("用户%s的账单是:" %self.name)
def shopping(self):
print("用户%s购买了..." % self.name)
def login(self):
username = input("姓名:")
password = input("密码:")
if username == "alex":
self.name = username
self.password = password
while 1 :
print("""
1.查看当前用户信息
2.查看用户账单
3.购买商品
""")
num = int(input("选择操作:"))
if num == 1:
self.user_info()
if num == 2:
self.account()
if num == 3:
self.shopping()
else:
print("输入错误!!!")
else:
print("登陆失败")
3. 类中的成员
类中的成员,大致分为三类:变量,属性,方法
3.1 变量
变量又分为实例变量(字段)和类变量(静态变量)。
3.1.1 实例变量
实例变量一般写在类中的__init__()
方法中,通过self
赋值给创建的类对象中。实例变量可以通过创建的实例对象直接访问,当然只能自己访问自己的实例变量。
实例对象可以对实例变量进行修改操作:当你在创建一个类的实例化对象时,对实例对象同时传参,之后使用实例对象访问了实例变量,并且对齐进行修改操作,那么实例变量已经发生变化。
class Foo:
def __init__(self,name):
# 实例变量
self.name = name
def func(self):
pass
obj = Foo("Tom")
obj.name # Tom
class Foo:
def __init__(self,name):
# 实例变量
self.name = name
def func(self):
pass
obj = Foo("Tom")
obj.name = "Jack"
print(obj.name) # Jack
实例属性的特点:
- 实例属性只有通过实例对象才能够访问,不能通过类名直接访问。
- 实例属性在每次创建实例对象的时候,会在类的内部自动调用。
- 在类中的任意方法中,都可以调用实例属性,操作实例属性。
- 可以使用实例对象在外部访问实例属性,也可以修改实例属性。
3.1.2 类变量
类变量是直接定义在类内部,不在任何的实例方法中的属性。
类变量可以直接通过类来直接访问,而不用通过实例对象。通过类来访问类变量时,可以对其进行修改的操作,通过实例对象不能修改类变量值。
建议:直接使用类来访问类变量。
class Foo:
# 类变量
country = "中国"
def __init__(self,name):
# 实例变量
self.name = name
def func(self):
pass
obj = Foo("Tom")
obj1 = Foo("Jerry")
print(obj.country) # 中国
print(obj1.country) # 中国
Foo.country = "美国"
print(obj.country) # 美国
子类可以访问父类中的属性。
class Base:
country = "中国"
class Foo(Base):
def __init__(self,name):
self.name = name
def func(self):
print(self.country)
print(Foo.country)
obj = Foo("Tom")
obj.func()
3.1.3 私有变量
实例变量和类变量又分别分成私有变量和公有变量。
公有变量就是我们在类中定义,在外部能够访问的变量。而私有变量是在内部定义,只能用于内部访问,无法从外部访问的变量。
私有变量定义时需要在变量前加上两个下划线_
。私有变量在外部不能被访问,也不能被子类访问。
# 实例变量私有化
class Foo:
country = "中国"
def __init__(self,name):
self.__name = name
def func(self):
print(self.__name)
obj = Foo("Tom")
prnt(obj.__name) # 这时会报错
# 类变量私有化
class Foo:
__country = "中国"
def __init__(self,name):
self.__name = name
def func(self):
print(self.__name)
print(Foo.__country)
obj = Foo("Tom")
obj.__country # 报错,外部无法访问
obj.func() # Tom,中国 可以通过内部访问私有变量
父类中的私有类变量,子类是不能够进行访问的。
class Base:
__secert = "受贿"
class Foo(Base):
def func(self):
print(self.__secert)
print(Foo.__secert)
obj = Foo()
obj.func() # AttributeError: 'Foo' object has no attribute '_Foo__secert'
但是如果父类内部存在一个方法去访问了这个私有类变量,那么子类去调用这个方法也是可以访问到父类中的私有类变量。
class Base:
__secert = "受贿"
def f1(self):
print(self.__secert)
class Foo(Base):
def func(self):
print(self.__secert)
print(Foo.__secert)
obj = Foo()
obj.f1() # 受贿
3.2 方法
在类中编写的方法,分为多种:实例方法、静态方法、类方法。
3.2.1 实例方法
实例方法就是需要调用封装在对象中的值的方法。
一般的,编写面向对象时,大部分使用的都是实例方法。
直接定义在类中的方法,就是实例方法。
class Foo:
def __init__(self,name):
self.name = name
# 实例方法
def func(self):
print(self.name)
3.2.2 静态方法
静态方法与实例方法相反,它不需要调用封装在对象中的值。
class Foo:
def __init__(self,name):
self.name = name
# 静态方法
@staticmethod
def display(a1,a2):
return a1 + a2
ret = Foo.display(1,2)
print(ret)
静态方法的编写:
- 需要固定的在静态方法上方添加:
@staticmethod
。 - 方法中的默认参数
self
不需要添加,但是可以添加其余的参数。 - 推荐使用类名来直接调用静态方法,当然也可以使用对象来调用。
什么时候使用静态方法:
当方法中不需要调用封装在对象中的值的时候,使用静态方法。
class Foo:
def __init__(self,name):
self.name = name
# 实例方法
def func(self):
print(self.name)
# 静态方法
@staticmethod
def display(a1,a2):
return a1 + a2
obj = Foo("Tom")
obj.func()
obj.display(1,2)
ret = Foo.display(1,2)
print(ret)
3.2.3 类方法
类方法是当你需要用到类本身的时候,才会定义一个类方法。
类方法的编写:
- 需要固定的在方法上方,添加:
@classmethod
。这个装饰器是在类被加载的时候执行。 - 方法中有一个固定的参数:
cls
,类方法同样也可以传入另外的参数。 - 推荐使用类名来调用类方法。
注意:类方法只能访问类变量,不能访问实例变量。
class Foo:
def __init__(self,name):
self.name = name
# 实例方法
def func(self):
print(self.name)
# 类方法
@classmethod
def func1(cls,a1,a2):
print(cls,a1,a2)
Foo.func1(1,2) # <class '__main__.Foo'> 1 2
obj = Foo("Tom")
obj.func1(1,2) # <class '__main__.Foo'> 1 2
3.2.4 私有方法
私有方法和私有变量一样,只能在内部进行调用,外部是无法调用到的。
子类也不能访问父类中的私有方法,但是可以通过访问父类中的某一个需要访问私有方法的方法去访问父类中的私有方法。
私有方法的编写,是以双下划线开头,只要是函数名前有双下滑线__
的,就是类中的私有方法。
# 私有静态方法
class Foo(object):
def __init__(self):
pass
@staticmethod
def __display(args):
print("私有静态方法,",args)
def get_method(self):
Foo.__display(123)
@staticmethod
def getMethod():
Foo.__display(123)
obj = Foo()
obj.get_method()
Foo.getMethod()
# 私有类方法
class Foo(object):
def __init__(self):
pass
@classmethod
def __func(cls,name):
print(cls,name)
def get_func(self):
Foo.__func("Tom")
obj = Foo()
obj.get_func() # <class '__main__.Foo'> Tom
4. 继承
当一个类想要使用另一个类中的方法,或者是属性的时候,这个时候就需要使用继承。
继承,就是指的一个子类,在继承父类的时候,能够使用父类中的方法和属性。 当然,父类中的私有属性和私有方法是不能被子类访问使用的。
继承的作用,就在于能够最大程度的提高代码的复用性,提高代码的质量。
class SuperBase:
def f3(self):
print("F3")
class Base(SuperBase): # 父类
def f2(self):
print("F2")
class Foo(Base): # 子类
def f1(self):
print("F1")
obj = Foo()
obj.f1()
obj.f2()
obj.f3()
4.1 多继承
多继承指一个子类继承多个父类中的方法。
但是继承多个父类时,首先去离子类最近的那个父类中去找方法,之后再去其他的父类中找方法。
class SuperBase:
def f3(self):
print("F3")
class Base: # 父类
def f2(self):
print("F2")
class Foo(Base,SuperBase): # 子类
def f1(self):
print("F1")
obj = Foo()
obj.f1()
obj.f2()
obj.f3()
多继承中 还有一个复杂的菱形继承,这其中牵扯到一个在继承多个 父类,且每个父类又都有继承父类时,函数方法的调用顺序问题,这里面有一个C3算法的问题。
4.2 重写父类方法
当一个子类需要在子类中定义一个属于子类自己的方法,但是这个方法在父类中已经存在,于是在子类又不想使用父类中的方法,可以选择去重写覆盖父类中的方法。
只需要在子类中定义一个与父类中相关方法名字一样的函数,之后写上自己的业务逻辑,就可以在实例化子类时,调用子类自己的方法,而不用去调用父类的方法。
class Base:
def eat(seflf):
print("父类在吃饭...")
class Son(Base):
def eat(self):
print("子类在吃饭,没有使用父类的方法...")
obj = Son()
obj.eat() # 父类与子类拥有同一个实例方法,而在实例化子类时,调用的是子类自己的方法,不是父类的。
4.3 super
函数
如果子类想要使用父类的方法,但是又觉得父类中的方法功能太少,此时可以使用super
函数,来在使用父类方法的基础上,对父类方法进行一次功能上的扩展。
class Base(object):
def eat(self):
print("父类在吃饭...")
class Son(Base):
def eat(self):
super(Son, self).eat()
print("子类也在吃饭...")
# 这样,在调用子类的eat方法时,不仅会保留原来的父类的eat方法的功能,还有属于子类自己的eat功能逻辑。
son = Son()
son.eat()
如果是想要扩展父类中的__init__
方法,那么在使用super
函数的时候,需要将父类中的__init__
方法中的参数同样传递过来,如果想要在父类的基础上,再新增别的实例属性,只需要在子类的__init__
方法中添加上新的属性即可。
class Base(object):
def __init__(self, name):
self.name = name
def eat(self):
print("父类在吃饭...")
class Son(Base):
# 不仅继承了父类上的name实例属性,还在父类的继承上,拥有了额外的属于子类自己的age实例是属性。
def __init__(self, name, age):
super(Son, self).__init__(name)
self.age = age
def eat(self):
super(Son, self).eat()
print("子类也在吃饭...")
son = Son("Tom", 18)
son.eat()
5. @property
属性和@setter
5.1 @property
属性
这个装饰器可以将 类中的方法,编程一个属性,属性可以使用实例化对象直接访问。
属性就是在类对象中无需传递参数,且有返回值得方法,就可以改写成属性。
这里说的属性是指将类中的方法改变成属性。
编写属性:
- 在方法上方固定的添加:
@property
; - 此时,方法参数只有一个
self
,且不能传递其他的多余参数。 - 在调用时,不需要在方法名后面加括号。使用对象来进行调用。
class Foo:
def __init__(self):
pass
@property
def start(self):
return 1
@property
def end(self):
return 0
obj= Foo()
print(obj.start) # 1
属性也是存在私有属性和共有属性的。
私有属性在外部不能被访问。只能允许内部访问。
可以给类设置属性。
5.2 @setter
这个装饰器可以给@property
属性进行赋值。
class Person(object):
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
p1 = Person()
p1.name = "Tom"
print(p1.name) # Tom
但是需要注意的是,修饰的属性前需要加一个下划线_
,否则会触发无限循环。
还有一点需要注意:@setter
必须用在@property
后面,并且@setter
所修饰的函数名,必须与@property
所修饰的函数一样。