人生苦短,我用Python。
在现实世界中存在各种不同形态的事物,这些事物之间存在着各种各样的联系。在程序中使用对象来映射现实中的事物,使用对象间的关系来描述事物之间的联系,这种思想就是面向对象编程(OOP,Object Oriented Programming)。
例如,分别使用面向过程和面向对象来实现五子棋:
面向对象编程有两个非常重要的概念:类和对象。
对象是面向对象编程的核心。
具有相似特征和行为的事物的集合统称为类。
对象是根据类创建的,一个类可以对应多个对象。
可以把玩具模型看作一个类,把每个玩具看作一个对象。
类是由3部分组成的:
类的名称:类名,首字母必须大写,比如Person。
类的属性:一组数据,比如姓名、性别。
类的方法:允许进行操作的方法,比如说话。
使用class关键字来声明一个类,基本格式如下:
class 类名: #类名首字母大写 类的属性 类的方法(self)
例:
class Car: price = 0 def getSpeed(self): return 100 car = Car() print(car.getSpeed())
根据类创建对象的语法格式如下:
对象名 = 类名()
要想给对象添加属性,可以通过如下方式:
对象名.新的属性名 = 值
例:
class Car: price = 0 car = Car() #实例化 print(f'car.price:{car.price}') #输出价格 car.price = 10 #修改价格 print(f'car.price:{car.price}') car.color = 'blue' #修改颜色 print(f'car.color:{car.color}') Car.price = 100 #不实例化情况下修改价格 print(f'Car.price:{Car.price}')
缺点:难以对属性进行管理,使用混乱
改进:区分类属性和实例属性,私有变量和公有变量
类属性:定义在所有函数外边,不需要实例化直接进行使用,使用方法是 类名.属性。
对象属性:定义在构造函数__init__中,只有实例化后才可使用,使用方法是 对象.属性。
私有属性和私有方法:在属性名或者函数名前加入2个下划线__。
定义成员方法(成员函数):在类内定义时必须有参数self,否则会报错。
例:
class Fruit: price=0 #定义一个类属性 def __init__(self): #构造函数 self.color="red" #实例属性,以self为前缀 zone="China" #局部变量,不以self为前缀 self.__buying_price=1 #私有属性,以self.__为前缀 if __name__=="__main__": print(Fruit.price) #使用类名调用类变量 0 apple=Fruit() #实例化apple print(apple.color) #打印apple实例的颜色 red Fruit.price += 10 #将类变量+10 print("apple price:"+str(apple.price)) #改变类属性将会影响对象属性的值,打印apple实例的price 10 banana=Fruit() #实例化banana print("banana price:"+str(banana.price)) #打印banana实例的price 10 #print(banana.zone) #无法访问局部变量 #print("apple buying price:"+str(apple.__buying_price)) #禁止访问私有变量
class Fruit: price=0 #定义一个类属性 def __init__(self): #构造函数 self.__color="red" #实例属性,以self为前缀 zone="China" #局部变量,不以self为前缀 self.__buying_price=1 #私有属性,以self.__为前缀 def setColor(self, new_color): if new_color=="white": return else: self.__color=new_color def getColor(self): return self.__color
if __name__=="__main__": apple=Fruit() #实例化apple apple.setColor("white") #修改apple实例的颜色 white print(apple.getColor()) #查看修改结果(失败) apple.setColor("yellow") #修改apple实例的颜色 yellow print(apple.getColor()) #查看修改结果(成功)
构造方法指的是__init__方法。
当创建类的实例的时候,系统会自动调用构造方法,从而实现对类进行初始化的操作。
class Car: def __init__(self, color): self.color = color print("%s的车在鸣笛..."%(self.color))
bmw = Car("雪山白")
当删除一个对象来释放类所占用资源的时候,Python解释器默认会调用另外一个方法,这个方法就是__del__( )方法。
__del__方法被称为析构方法。
class Car: def __init__(self,name): self.name = name def __del__(self): print(f'{self.name} will be deleted') car = Car("宾利") while True: pass
在方法的列表中,第1个参数永远都是self。
self的字面意思是自己,我们可以把它当做C++里面的this指针理解,表示的是对象自身。
当某个对象调用方法的时候,Python解释器会把这个对象作为第1个参数传给self,开发者只需要传递后面的参数就可以了。
class Dog: def __init__(self, color): self.color = color def printColor(self): print("颜色为:%s"%self.color)
dog1 = Dog("白色") dog1.printColor()
运算符重载是通过实现特定的方法使类的实例对象支持Python的各种内置操作 。例如:+运算符是类里提供的__add__这个函数,当调用+实现加法运算的时候,实际上是调用了__add__方法。
方法 说明 何时调用方法 __add__ 加法运算 对象加法:x+y,x+=y __sub__ 减法运算 对象减法:x-y,x-=y __mul__ 乘法运算 对象乘法:x*y,x*=y __diy__ 除法运算 对象除法:x/y,x/=y __getitem__ 索引,分片 x[i]、x[i:j]、没有__iter__的for循环等 __setitem__ 索引赋值 x[i]=值、x[i:j]=序列对象 __delitem__ 索引和分片删除 del x[i]、del x[i:j]
加法运算是通过调用__add__方法完成重载的,当两个实例对象执行加法运算时,自动调用__add__方法。
z = x+y,执行加法运算,实质是调用__add__方法
class Cat: def __init__(self,name): self.name = name def __add__(self, other): print('运算符被重载 ') print(f'{self.name} and {other.name}') cat1 = Cat('mimi') cat2 = Cat('miaomiao') print(cat1 + cat2)
跟索引相关的重载方法包括如下3个:
getitem:索引、分片;
setitem:索引赋值;
delitem:索引和分片删除。
在对实例对象执行索引、分片或者for迭代操作时,会自动调用__getitem__方法。
# 定义索引、分片运算符重载方法 def __getitem__(self, index): return self.data[index]
通过赋值语句给索引或者分片赋值时,调用__ setitem __方法实现对序列对象的修改。
def __setitem__(self, index, value): self.data[index] = value
当调用del方法时,实质上会调用__delitem__方法实现删除操作。
def __delitem__(self, index): del self.data[index]
面向对象三大特征:封装、继承、多态
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
封装的优点:
良好的封装能够减少耦合。
类内部的结构可以自由修改。
可以对成员变量进行更精确的控制。
隐藏信息,实现细节。
为了保护类里面的属性,可以采用如下方式解决:
把类中变量定义为私有变量,即在属性名的前面加上两个下划线;
由于私有变量只能被类中的成员函数修改和访问,所以需要添加用于设置和获取属性值的两个方法供外界调用。
class Person: def __init__(self, name, age): self.__name = name self.__age = age def setAge(self, new_age): if new_age<0 or new_age>160: return else: self.__age = new_age def getAge(self): return self.__age
laowang = Person('老王', 30) laowang.setAge(300) print(laowang.getAge()) laowang.setAge(80) print(laowang.getAge())
通过封装可以实现对成员变量值的控制
laowang = Person('老王', 30) laowang.__age=800 print(laowang.__age)
因为给laowang.__age赋值时,laowang.__age无法访问,所以新建了一个公开变量laowang.__age,但是这个公开变量不被getAge函数承认,这也是getAge函数存在的意义。
继承在程序中,继承描述的是事物之间的所属关系。
类的继承是指在一个现有类的基础上构建一个新的类,构建出来的新类被称作子类。
Python程序中,继承使用如下语法格式标注:
class 子类名(父类名):
假设有一个类为A,A派生出来了子类B,示例如下:
class B(A): class A(object):
默认是继承自object的
class Animal(object): def __init__(self,food): self.eatFood = food def eat(self): print('It likes eating ' + self.eatFood)
c = Cat('fish') c.eat() m = Monkey('banana') m.eat()
class Cat(Animal): pass class Monkey(Animal): pass
注意:子类不重写__init__,实例化子类时,会自动调用父类定义的__init__,但重写了__init__时,实例化子类,就不会调用父类已经定义的__init__
现实生活中,一个派生类往往会有多个基类。比如沙发床是沙发和床的功能的组合,这都是多重继承的体现。
Python支持多继承,多继承就是子类拥有多个父类,并且具有它们共同的特征,即子类继承了父类的方法和属性。
多继承可以看做是单继承的扩展,语法格式如下:
class 子类名(父类1,父类2…):
如果子类继承的多个父类间是平行的关系,子类先继承的哪个类就会调用哪个类的方法。
class Sofa(): def __init__(self): self.sit = True print("继承Sofa") class Bed(): def __init__(self): self.sleep = True print("继承Bed")
class SofaBed(Sofa,Bed): def __init__(self): Sofa.__init__(self) Bed.__init__(self) print(f'I can sit on the sofa:{self.sit}') print(f'I can sleep in the bed:{self.sleep}') s = SofaBed()
注意:如果想继承父类的对象属性,则需要在子类的构造方法中,显示地调用父类的构造方法。
在继承关系中,子类会自动拥有父类定义的方法,但是有时子类想要按照自己的方式实现方法,即对父类中继承来的方法进行重写,使得子类中的方法覆盖掉跟父类同名的方法。
需要注意的是,在子类中重写的方法要和父类被重写的方法具有相同的方法名和参数列表。
class Sofa(): def sit(self): print("可以坐着") class Bed(): def sleep(self): print("可以躺着")
class SofaBed(Sofa,Bed): ''' def sit(self): print("葛优躺") ''' s = SofaBed() s.sit() s.sleep()
多态的目的:一个接口,多种实现
class People(object): def say(self): pass
# A class American(People): def say(self): print("Hello")
# B class Chinese(People): def say(self): print("你好")
def greet(obj): obj.say()
a = American() c = Chinese() greet(a) greet(c)
类属性是类所拥有的属性,它需要在类中进行显示地定义(位于类内部,方法的外面),当使用类.属性名方式使用时它被所有类的实例对象所共有,在内存中只存在一个副本。
类属性示例代码:
class Cat(object): #类属性 num = 0
通过“实例.属性”添加属性的属性都是实例属性。
实例属性示例:
class Cat(object): num = 0 c = Cat() #实例属性 c.newNum = 1
class Cat(object): num = 0 def __init__(self): # 实例属性 self.name = 'mimi'
class AAA(): aaa = 10 # 情形1 obj1 = AAA() obj2 = AAA() print(obj1.aaa, obj2.aaa, AAA.aaa) # 情形2 obj1.aaa += 2 print(obj1.aaa, obj2.aaa, AAA.aaa) # 情形3 AAA.aaa += 3 print(obj1.aaa, obj2.aaa, AAA.aaa)
class 类名: @classmethod def 类方法名(self): 方法体
要想调用类方法,既可以通过对象名.函数名方式调用类方法,又可以通过类名.函数名调用类方法,这两种方法没有任何区别。
class Cat: name = 'mimi' @classmethod def fun(self): print(self.name)
c = Cat() #对象名.函数名 c.fun() #类名.函数名 Cat.fun()
注意:类方法只能使用类变量,因为不用实例化就可以使用。所以调用对象变量会报错。
使用修饰器@staticmethod来标识静态方法。
class 类名: @staticmethod def 静态方法名(): 方法体
静态方法是没有self参数,在静态方法中无法访问实例变量。
静态方法中不可以直接访问类属性,但是可以通过类名引用类属性。
静态方法跟定义它的类没有直接关系,只是起到了类似函数的作用。
class Cat: eyes = 2 @staticmethod def look(): #通过类名引用类属性 print(Cat.eyes)
c = Cat() c.look() #类名.函数名 Cat.look()