编程思想是什么,就是用代码解决现实生活中问题的思路。面试
核心点在过程二字,过程指的是解决问题的步骤,说白了就是先作什么再干什么。这种解决问题的思路就比如是工厂中的流水线。算法
运维同窗工做中接触到的shell脚本就是典型的按步骤作事。shell
优势:复杂的问题流程化,进而简单化。编程
缺点:可扩展性差,好比,一个脚本就是干一件事情的。运维
核心点是对象二字,对象指的是具备相同属性和动做的结合体叫对象。ide
面向对象编程就比如在代码中创造一个世界,创造若干对象,就像现实世界中的万物同样,每一个物体均可以有本身的属性和动做。函数
优势:可扩展性强ui
缺点:编程的复杂度高于面向过程spa
问:把大象装进冰箱,总共分几步?设计
答:三步啊,
这就是典型的面向过程解题的思惟方式。
那面向对象编程是怎么样解决这个问题呢?
首先,咱们须要有两个对象--冰箱和大象。冰箱能够开门和关门,大象能够被放进冰箱。
这就是面向对象的思惟方式。
咱们面向的再也不是事物发展的流程,而是操纵某个事物个体。
说了那么多,那究竟如何在代码中使用面向对象呢?
首先,咱们来思考这样一个问题,在现实世界中,咱们如何造一辆汽车?
咱们确定是先由设计师来设计,在图纸上勾勒出汽车应该有的东西,以及这辆汽车应该有的功能,而后再交给工厂去生产真正的汽车。
在程序中也是同样,咱们须要先设计一个图纸,图纸上描述着物体应该有的特征和功能,而后依照这个图纸建立对象。
在Python中,“画图纸”的这个过程,咱们经过定义类来实现。
类的语法很简单,像下面这样:
class 类名: pass
好比咱们建立一个车类:
class Car: pass
像这样咱们就建立了一个汽车类,图纸有了,那怎么建立一辆汽车对象呢?
根据图纸建立物体的过程,咱们称之为实例化。实例化就是由一个概念到实际物体的过程。
Python中实例化,只须要“类名()”就能够了。
c1 = Car() # 造一辆车 c2 = Car() # 再造一辆车
车如今有了,这车至少得有一些颜色、排量、品牌等信息吧。不一样的车,能够有不一样的颜色、排量、品牌等信息。
咱们能够经过下面的代码来实现:
c1.color = 'red' c1.code = 'SNIS-517' c1.power = '2.0T' c2.color = 'green' c2.code = 'SNIS-594' c2.power = '3.0T'
而后,咱们能够检查一下这两辆车的信息。
print(c1.color) print(c1.code) print(c1.power) print(c2.color) print(c2.code) print(c2.power)
咱们就这样造了两辆彻底不一样的车,可是咱们发现这两辆车是在造好以后再添加不一样的信息的。但是这些信息应该是在创造汽车的时候就设计好的,不该该后面再设计的。
好了,咱们如今的需求就是在建立对象的时候就能给对象设置一些初始的属性信息,在Python中咱们是经过在类中定义__init__()“函数”,来进行初识化操做。这个“函数”被称为构造函数(方法)。
class Car: def __init__(self, color, code, power): # self表示当前建立的这个对象,你建立的是谁self就指的谁 self.color = color self.code = code self.power = power c1 = Car('red', 'SNIS-517', '2.0T') c2 = Car('green', 'SNIS-594', '3.0T') print(c1.color) print(c1.code) print(c1.power) print(c2.color) print(c2.code) print(c2.power)
经过执行上面的代码,咱们发现咱们由一个Car类,创造了两个不一样特征(属性)的汽车对象。
咱们的这个例子尚未讲完,由于汽车不光有特征,它还应该有动做--车还会跑。并且是全部的汽车都会跑,这怎么实现呢?
咱们只须要在类中像定义函数同样定义一个方法便可。类中的方法比普通的函数多一个self参数。其实本质上就是函数,只不过这个函数被绑定给实例对象使用,不能随意经过名字调用执行它。
class Car: def __init__(self, color, code, power): # self表示当前建立的这个对象,你建立的是谁self就指的谁 self.color = color self.code = code self.power = power def run(self, speed): print('车能够跑{}迈'.format(speed)) c1 = Car('red', 'SNIS-517', '2.0T') c1.run(70) # 此时,Python会自动把c1这个对象当成run()的第一个参数传进去
构造函数中的self表示建立的当前对象。
方法中的self表示谁调用的方法,self就是谁。
类是对事物的总结,是一种抽象的概念。类用来描述对象。
对象是类实例化的结果,对象能执行哪些方法,都是由类来定义的。类中定义了什么,对象就拥有什么。
再来一个例子,帮助理解面向对象。
from math import pi class Circle: """ 定义了一个圆形类; 提供计算面积(area)和周长(perimeter)的方法 """ def __init__(self, radius): self.radius = radius # 圆的半径 def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi * self.radius circle = Circle(10) # 实例化一个圆 area1 = circle.area() # 计算圆面积 per1 = circle.perimeter() # 计算圆周长 print(area1, per1) # 打印圆面积和周长
你可能会问:面向对象和面向过程到底哪一个好?
首先这不能看作是一个谁好谁很差的问题,要具体问题具体分析。没有绝对的好和很差,这一点很是重要!
咱们借助几个例子,再来看一下面向对象和面向过程的区别:
面向过程版
# 非函数版 print('开冰箱门') print('把大象装进冰箱') print('关冰箱门') # 函数版 def open_door(): print('开冰箱门') def zhuang(): print('把大象装进冰箱') def close_door(): print('关冰箱门') open_door() zhuang() close_door()
面向对象版:
class Elephant: def open_door(self): print('开冰箱门') def zhuang(self): print('走进冰箱') def close_door(self): print('把门带上') dx = Elephant() dx.open_door() dx.zhuang() dx.close_door()
这...面向对象有点麻烦啊...别着急,继续往下看:
以一只猪,名叫佩奇,今年18岁,会使用必杀技“嘟嘟嘴”。他不光能大战奥特曼,还能大战蝙蝠侠,蜘蛛侠。
面向过程
# 面向过程 def da_out_man(name, age, jn): print('{}, 今年{}岁,正在使用{}技能对奥特曼疯狂输出...'.format(name, age, jn)) def da_bat_man(name, age, jn): print('{}, 今年{}岁,正在使用{}技能对蝙蝠侠疯狂输出...'.format(name, age, jn)) def da_spider_man(name, age, jn): print('{}, 今年{}岁,正在使用{}技能对蜘蛛侠疯狂输出...'.format(name, age, jn)) da_out_man('小猪佩奇', 18, '嘟嘟嘴') da_bat_man('小猪佩奇', 18, '嘟嘟嘴') da_spider_man('小猪佩奇', 18, '嘟嘟嘴')
面向对象:
class Pig: def __init__(self, name, age, skill): self.name = name self.age = age self.skill = skill def da_out_man(self): print('{}, 今年{}岁,正在使用{}技能对奥特曼疯狂输出...'.format(self.name, self.age, self.skill)) def da_bat_man(self): print('{}, 今年{}岁,正在使用{}技能对蝙蝠侠疯狂输出...'.format(self.name, self.age, self.skill)) def da_spider_man(self): print('{}, 今年{}岁,正在使用{}技能对蜘蛛侠疯狂输出...'.format(self.name, self.age, self.skill)) pq = Pig('小猪佩奇', 18, '嘟嘟嘴') pq.da_out_man() pq.da_bat_man() pq.da_spider_man()
看完这个例子,是否是有点眉目了。在小猪佩奇这个示例中,咱们明显可以发现使用面向对象的思想来解决问题会更清晰一些。
代码也更容易编写一些。
因此,使用哪一种编程思想来解决问题不是绝对的,咱们须要根据实际的需求来抉择。
面向对象有三大特性:封装、继承和多态。
把不少数据封装到一个对象中,把固定功能的代码封装到⼀个代码块函、数、对象, 打包成模块,这都属于封装的思想。
具体的状况具体分析,好比:你写了⼀个很牛B的函数,那这个也能够被称为封装。在⾯向对象思想中是把一些看似可有可无的内容组合到一块儿,统一进行存储和使用,这就是封装。
子类能够自动拥有父类中除了私有属性外的其余全部内容。说⽩了, ⼉⼦能够随便用爹的东西。可是朋友们, 必定要认清楚⼀个事情。必须先有爹, 后有⼉⼦。 顺序不能乱,在Python中实现继承很是简单。在声明类的时候, 在类名后⾯面添加一个小括号,把要继承的类传进去就能够完成继承关系。
那么什么状况可使用继承呢?
单纯的从代码层⾯上来看,当两个类具备相同的功能或者特征的时候,能够采用继承的形式。提取一个父类,这个父类中编写两个类中相同的部分。而后两个类分别去继承这个类就能够了。这样写的好处是咱们能够避免写不少重复的功能和代码。
若是果语境中出现了了x是一种y。这时,y就是⼀种泛化的概念,x比y更加具体。那这时x就是y的子类。举个现实中的例子:猫是⼀种动物,猫继承动物。动物能动,猫也能动。这时猫在建立的时候就有了动物的"动"这个属性。再好比⽩骨精是⼀个妖怪,妖怪天生就有⼀个比较很差的功能叫"吃人", 白骨精⼀出生就知道如何"吃人"。此时 ⽩骨精继承妖精。
话很少说,上代码:
# 妖怪类 class Yao: def eat(self): print("我是妖怪, 我天生就会吃人") # ⽩骨精继承妖怪 class BaiGuJing(Yao): pass bgj = BaiGuJing() # 我是妖怪, 我天⽣生就会吃⼈人 # 虽然白骨精类中没有编写eat,可是他爹有啊,直接拿来⽤ bgj.eat()
继承是实例对象可以访问到本身类及父类的属性和方法。
先来看几个例子:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) class B(A): def func1(self): print("B.func1", self.num) obj = B(123) obj.func1()
再来一个:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2") class B(A): def func2(self): print("B.func2") obj = B(123) obj.func1()
总结一下:
示例对象访问属性或调用方法时,永远都是先找本身的,找不到就往父类找。
最后来两个例子,巩固一下:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2", self.num) class B(A): def func2(self): print("B.func2", self.num) list1 = [A(1), A(2), B(3)] for i in list1: i.func2()
来一个更绕的:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2", self.num) class B(A): def func2(self): print("B.func2", self.num) list1 = [A(1), A(2), B(3)] for i in list1: i.func1()
在Python中,一个类能够同时继承多个父类。说白了, 如今一个⼉子可能会有多个爹了。
既然是能够有多个爹, 那总得有远有近。好比. 有一个这样的牛B的人物, 叫郭丑丑,就有不少个爹。
class QinDie: def eat(self): print("亲爹给你好吃的") def play(self): print("亲爹会陪你玩") class GanDie: def qian(self): print("干爹给钱啊") def play(self): print("干爹会陪你玩") class GuoChouChou(QinDie, GanDie): pass mm = GuoChouChou() mm.eat() mm.qian() mm.play() # 继承中,实例对象查找方法的顺序 # 亲爹没有, 找干爹 # 亲爹有了, 就不找干爹了
注意:多继承中的MRO(Method Resolution Order)算法,咱们在后面会具体分析和讲解。
同一个对象,多种形态。
这个在Python中实际上是很不容易说明白的,由于咱们一直在用,只是没有具体的说。好比咱们建立⼀个变量a = 10,咱们知道此时a是整数类型。可是咱们能够经过程序让a = "Bob", 这时a又变成了字符串类型。这是咱们都知道的。这个就是多态性。同一个变量a能够是多种形态。再好比一个打开的文件、一个列表、一个生成器均可以被for循环迭代,它们都是可迭代对象。可能这样的程序和说法你还get不到具体什么是多态。
接下来,咱们来看⼀个程序。动物园里饲养员一天的⼯做,从早上开始喂养猪, 中午喂狗。
来咱们用代码实现这样的代码:
class Animal: def eat(self): print("动物就知道吃") class Pig(Animal): def eat(self): print("猪在吃") class Dog(Animal): def eat(self): print("狗在吃") class Feeder: def feed_animal(self, ani): ani.eat() p = Pig() d = Dog() f = Feeder() f.feed_animal(p) f.feed_animal(d)
当子类和父类都存在相同的eat方法时,子类的eat方法覆盖了父类的eat方法。在代码运行的过程当中,子类对象老是会调用本身的eat方法。
此时,虽然都是Animal类的对象,都有eat方法,可是实现的功能却能够不一样。
多态的好处:程序具备超高的可扩展性。
什么是类的成员呢?很简单,你在类中定义的内容均可以称为类的成员。
到目前为止,咱们已经学过一些成员了:
class Person: # 方法 def __init__(self, name, age): # 属性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}岁!'.format(self=self))
在上面的代码中,__init__和say都是属于类的成员方法,又称为实例方法。
self.name和self.age都是实例对象的属性,这些称为成员变量或实例变量。
也就是说在类中,能够定义实例变量和实例方法,那还有些什么呢?
先说什么是实例变量,说白了,就是每一个实例都应该拥有的变量。好比,人的名字,人的年龄,每一个人的信息都属于具体的人,这些均可以称为实例变量。
class Person: # 方法 def __init__(self, name, age): # 属性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}岁!'.format(self=self)) p1 = Person('张三', 18) p2 = Person('李四', 20) print(p1.name, p1.age) # 张三 18 print(p2.name, p2.age) # 李四 20 print(id(p1.name), id(p2.name)) # 4567249208 4567250088 print(id(p1.age), id(p2.age)) # 4564114656 4564114720
那什么是类变量呢?类变量就是这一类事物统一拥有的变量,好比,在座的各位都是中国人,你们都拥有一个同一个祖国--中国。
class Person: # 类变量,全部实例对象都共享这个变量 country = '中国' # 方法 def __init__(self, name, age): # 属性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}岁!'.format(self=self)) p1 = Person('张三', 18) p2 = Person('李四', 20) print(p1.country, p2.country) # 中国 中国 print(id(p1.country), id(p2.country)) # 4567249032 4567249032
咱们能够发现,实例对象p1和p2的country属性都是类中定义的那个,公用的。
咱们尝试修改一下:
Person.country = 'China' print(p1.country, p2.country) # China China print(id(p1.country), id(p2.country)) # 4551075464 4551075464
从上面的代码中,咱们能够看到咱们修改了类变量country,各实例对象中的country属性也跟着变了。
接下来,咱们再来看一个例子。
class Person: # 类变量,全部实例对象都共享这个变量 country = '中国' # 方法 def __init__(self, name, age): # 属性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}岁!'.format(self=self)) p3 = Person('王五', 3000) p3.country = '大秦' print(p3.name) # 王五 print(p3.country) # 大秦 p4 = Person('赵六', 30) print(p4.name) # 赵六 print(p4.country) # 中国
为何王五的country是大秦,而赵六的country是中国呢?
注意,p3.country = '大秦'的时候,并无去改变类中的country,而是给本身添加了一个实例属性,这个属性只有在p3这个实例对象中才是存在的,在p4中是不存在的。
类变量,是定义在类中的变量,该类的实例对象均可以访问到该变量,可是示例对象只能查看不能修改。想要修改类变量,只能经过类名来修改。
示例变量,给实例对象用的。
类变量,实例对象共享的。
案例:经过类变量来记录建立的实例个数:
class Foo: count = 0 def __init__(self): Foo.count += 1 print(Foo.count) # 0 Foo() Foo() Foo() print(Foo.count) # 3
类中的方法分为实例方法、静态方法和类方法。
实例方法是咱们接触和使用最多的,实例对象能够直接访问的方法就叫实例方法(成员方法)。
class Computer: # 实例方法 def play(self): print('个人电脑能够玩游戏') c1 = Computer() c1.play() # 实例对象直接调用实例方法
静态方法不须要传递“self”参数,也就是说,当类中的方法不须要传入实例变量的时候,就可使用静态方法。
定义静态方法时须要咱们在该方法上添加@static,示例以下:
class Computer: # 实例方法 def play(self): print('个人电脑能够玩游戏') @staticmethod def fire(): # 方法不须要传入实例对象变量 print('个人电脑很是牛B,能够煎鸡蛋') c1 = Computer() c1.play() # 实例对象直接调用实例方法 c1.fire()
静态方法和静态变量同样,均可以使用类名直接访问。
Computer.fire()
类方法是由类调用的方法,他的第一个参数必须是类自己。类方法在被调用的时候,不须要调用者传递该参数,解释器会自动的把类当成第一个参数,类方法在定义的时候须要在类方法上添加@classmethod
class Computer: # 实例方法 def play(self): print('个人电脑能够玩游戏') @staticmethod def fire(): # 方法不须要传入实例对象变量 print('个人电脑很是牛B,能够煎鸡蛋') @classmethod def calc(cls, a, b): print(cls) return a + b c1 = Computer() c1.play() # 实例对象直接调用实例方法 c1.fire() ret = Computer.calc(10, 20) # <class '__main__.Computer'> print(ret) # 30
相关面试题
类方法、静态方法和实例方法有什么区别?
属性方法其实就是把类中的方法改形成属性的一种写法,该写法须要在方法上加上@property,例如:
from datetime import datetime from dateutil.relativedelta import relativedelta class Person: def __init__(self, name, birthday): self.name = name self.birthday = birthday @property def age(self): # 人的年龄是一个属性,可是须要由生日和当前时间计算得出 # 因此咱们把这个方法包装成属性 ret = relativedelta(datetime.now(), datetime.strptime(self.birthday, '%Y-%m-%d')) return ret.years p1 = Person('李国庆', '1990-10-01') # 像访问实例属性同样 print(p1.age)
在面向对象中,类中一般会定义属性和方法,属性用来描述实例对象的特征,方法用来描述实例对象的动做。
像年龄这种须要必定动做来得出的属性,Python中咱们会把这个方法描述成一个属性。
补充property的进阶用法:
class Goods: def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price obj = Goods() print(obj.price) # 获取商品价格 obj.price = 200 # 修改商品原价 print(obj.price) del obj.price # 删除商品原价 print(obj.price) # AttributeError: 'Goods' object has no attribute 'original_price'
在某些场景下,咱们可能会在类中存储一些只有咱们本身能访问的信息。在Python中咱们可使用两个连续的双下划线将属性或方法变为私有的。
咱们定义一个Person类,有name属性和salary属性,薪资是保密,因此在salary的前面加上两个连续的下划线,把它变成私有的。
class Person: def __init__(self, name, salary): self.name = name self.__salary = salary # 薪资是秘密,设置为私有 p1 = Person('李狗剩', 200) print(p1.name) print(p1.__salary) # AttributeError: 'Person' object has no attribute '__salary'
上面的代码报错,由于咱们没法直接访问实例对象的私有属性。
咱们能够曲线救国,经过另一个方法来获取这个属性。
class Person: def __init__(self, name, salary): self.name = name self.__salary = salary # 薪资是秘密,设置为私有 def dazuiba(self): return self.__salary p1 = Person('李狗剩', 200) print(p1.name) salary = p1.dazuiba() print(salary) # 200
私有属性不能直接访问,可是能够经过其余的实例方法来访问私有属性。这样作的好处是实现了私有属性只能查看不能修改。
不只仅实例属性能够私有,类属性也能够:
class Person: __secret = '人都是自私的' def __init__(self, name, salary): self.name = name self.__salary = salary # 薪资是秘密,设置为私有 def dazuiba(self): return self.__salary p1 = Person('李狗剩', 200) print(p1.name) salary = p1.dazuiba() print(salary) # 200 print(Person.__secret) # AttributeError: type object 'Person' has no attribute '__secret'
私有方法,顾名思义就是只能本身调用的方法,别人都不能随便调用。
class Person: def __init__(self): pass def __play(self): print('嘿嘿嘿') def work(self): print('工做使我快乐!') p1 = Person() p1.work() # 工做使我快乐! p1.__play() # AttributeError: 'Person' object has no attribute '__play'
__play是一个私有方法,只可以在类的内部调用,类的外部没法调用。
咱们能够在类的内部调用私有方法:
class Person: def __init__(self): pass def __play(self): print('嘿嘿嘿') def work(self): print('工做使我快乐!') def dazuiba(self): self.__play() p1 = Person() p1.work() p1.dazuiba() # 嘿嘿嘿
在实际的应用场景中,咱们一般会把那些不想暴露给实例对象的方法,定义为私有方法。
另外还须要注意的一点是,对于私有成员,子类是没法继承的。
class A: __secret = '密码' def __init__(self, salary): self.__salary = salary def __play(self): print('嘿嘿嘿') class B(A): def dazuiba(self): self.__play() b1 = B(200) print(b1.__salary) # 'B' object has no attribute '__salary' b1.__play() # 'B' object has no attribute '__play' b1.dazuiba() # AttributeError: 'B' object has no attribute '_B__play' print(b1.__secret) # AttributeError: 'B' object has no attribute '__secret' print(B.__secret) # AttributeError: type object 'B' has no attribute '__secret'
依赖关系
class Elephant: def __init__(self, name): self.name = name def open(self, ref): print("⼤象要开门了。默念三声!开!") # 由外界传递进来⼀个冰箱, 让冰箱开门。这时,⼤象不用背着冰箱处处跑。 # 类与类之间的关系也就不那么的紧密了,换句话说只要是有open_door()⽅法的对象均可以接收运⾏ ref.open_door() def close(self, ref): print("⼤象要关门了。默念三声!关!") ref.close_door() def enter(self): print("钻进去") class Refrigerator: def open_door(self): print("冰箱门被打开了") def close_door(self): print("冰箱门被关上了") # 造冰箱 r = Refrigerator() # 造⼤大象 el = Elephant("神奇的大象") el.open(r) # 注意:此时是把一个冰箱做为参数传递进去了。也就是说大象能够指定任何一个冰箱 el.enter() el.close(r)
关联关系
class Boy: def __init__(self, name, girl_friend=None): self.name = name self.girl_friend = girl_friend def have_a_dinner(self): if self.girl_friend: print("{}和{}一块儿去吃晚餐".format(self.name, self.girl_friend.name)) else: print("单身狗。吃什么饭!") class Girl: def __init__(self, name): self.name = name b = Boy('张大锤') b.have_a_dinner() # 忽然找到女友了 g1 = Girl("如花") b.girl_friend = g1 g2 = Girl("李小花") bb = Boy("张二锤", g2) # 娃娃亲,出⽣就有女朋友 bb.have_a_dinner() # 多么幸福的一家 # 忽然.bb失恋了。娃娃亲不跟他好了 bb.girl_friend = None bb.have_a_dinner() # 又单身了