**
引言
了解完函数式编程之后,我们了解一下面向对象编程。函数式编程和面向对象编程是两种不同的编程范式,它们各自有着独特的优点和适用场景。函数式编程强调函数的纯粹性和不可变性,通过组合和转换函数来构建程序。而面向对象编程则注重对象的封装、继承和多态,通过定义类和方法来组织代码。
然而,在实际开发中,函数式编程和面向对象编程并非是完全互斥的。实际上,这两种范式可以很好地结合使用,相互补充,从而提供更灵活、可扩展的代码结构。
借助函数式编程的思想,我们可以将复杂的业务逻辑分解为一系列纯函数,实现模块化和可测试性。而面向对象编程则可以利用类的封装和继承特性,提供更强大的抽象和代码复用能力。
通过将函数式编程的思想应用于面向对象编程中,我们可以借助高阶函数、闭包等特性,使得类的方法更具表达力和灵活性。同时,将面向对象编程的技巧应用于函数式编程中,可以使函数式代码更易于理解和维护。
综上所述,函数式编程和面向对象编程可以相辅相成,互为补充。在实际项目中,我们可以根据需求的复杂程度和团队的开发习惯,灵活选择使用函数式编程或面向对象编程,或者二者的结合,以达到更好的开发效果和代码质量。
1. 初识面向对象
想要通过面向对象去实现某个或某些功能时需要2步:
-
定义类,在类中定义方法,在方法中去实现具体的功能。
-
实例化类的个一个对象,通过对象去调用并执行方法。
class Message: def send_email(self, email, content): data = "给{}发邮件,内容是:{}".format(email,content) print(data) msg_object = Message() # 实例化一个对象 msg_object,创建了一个一块区域。 msg_object.send_email("wupeiqi@live.com","注册成功")
注意:
-
类名称首字母大写&驼峰式命名;
-
py3之后默认类都继承object;
-
在类中编写的函数称为方法;
-
每个方法的第一个参数是self;
类中可以定义多个方法,例如:
class Message: def send_email(self, email, content): data = "给{}发邮件,内容是:{}".format(email, content) print(data) def send_wechat(self, vid, content): data = "给{}发微信,内容是:{}".format(vid, content) print(data) msg_object = Message() msg_object.send_email("wupeiqi@live.com", "注册成功") msg_object.send_wechat("lisa", "注册成功")
你会发现,用面向对象编程写的类有点像归类的意思:将某些相似的函数划分到一个类中。但,这种编写方式让人感觉有些鸡肋,直接用 函数 写多好呀。对吧?别着急,记者往下看。
1.1 对象和self
在每个类中都可以定义个特殊的:__init__ 初始化方法
,在实例化类创建对象时自动执行,即:对象=类()
。
class Message: def __init__(self, content): self.data = content def send_email(self, email): data = "给{}发邮件,内容是:{}".format(email, self.data) print(data) def send_wechat(self, vid): data = "给{}发微信,内容是:{}".format(vid, self.data) print(data) # 对象 = 类名() # 自动执行类中的 __init__ 方法。 # 1. 根据类型创建一个对象,内存的一块 区域 。 # 2. 执行__init__方法,模块会将创建的那块区域的内存地址当self参数传递进去。 往区域中(data="注册成功") msg_object = Message("注册成功") msg_object.send_email("wupeiqi@live.com") # 给wupeiqi@live.com发邮件,内容是:注册成功 msg_object.send_wechat("lisa") # 给lisa发微信,内容是:注册成功
通过上述的示例,你会发现:
-
对象,让我们可以在它的内部先封装一部分数据,以后想要使用时,再去里面获取。
-
self,类中的方法需要由这个类的对象来触发并执行( 对象.方法名 ),且在执行时会自动将对象当做参数传递给self,以供方法中获取对象中已封装的值。
注意:除了self默认参数以外,方法中的参数的定义和执行与函数是相同。
当然,根据类也可以创建多个对象并执行其中的方法,例如:
class Message: def __init__(self, content): self.data = content def send_email(self, email): data = "给{}发邮件,内容是:{}".format(email, self.data) print(data) def send_wechat(self, vid): data = "给{}发微信,内容是:{}".format(vid, self.data) print(data) msg_object = Message("注册成功") msg_object.send_email("wupeiqi@live.com") # 给wupeiqi@live.com发邮件,内容是:注册成功 msg_object.send_wechat("lisa") login_object = Message("登录成功") login_object.send_email("wupeiqi@live.com") # 给wupeiqi@live.com发邮件,内容是:登录成功 login_object.send_wechat("lisa")
面向对象的思想:将一些数据封装到对象中,在执行方法时,再去对象中获取。
函数式的思想:函数内部需要的数据均通过参数的形式传递。
-
self,本质上就是一个参数。这个参数是Python内部会提供,其实本质上就是调用当前方法的那个对象。
-
对象,基于类实例化出来”一块内存“,默认里面没有数据;经过类的
__init__
方法,可以在内存中初始化一些数据。
1.2 常见成员
在编写面向对象相关代码时,最常见成员有:
-
实例变量,属于对象,只能通过对象调用。
-
绑定方法,属于类,通过对象调用 或 通过类调用。
注意:还有很多其他的成员,后续再来介绍。
class Person: def __init__(self, n1, n2): # 实例变量 self.name = n1 self.age = n2 # 绑定方法 def show(self): msg = "我叫{},今年{}岁。".format(self.name, self.age) print(msg) def all_message(self): msg = "我是{}人,我叫{},今年{}岁。".format(Person.country, self.name, self.age) print(msg) def total_message(self): msg = "我是{}人,我叫{},今年{}岁。".format(self.country, self.name, self.age) print(msg)
# 执行绑定方法 p1 = Person("lisa",20) p1.show() # 或 # p1 = Person("lisa",20) # Person.show(p1) # 初始化,实例化了Person类的对象叫p1 p1 = Person("lisa",20)
1.3 应用示例
将数据封装到一个对象,便于以后使用。
class UserInfo: def __init__(self, name, pwd,age): self.name = name self.password = pwd self.age = age def run(): user_object_list = [] # 用户注册 while True: user = input("用户名:") if user.upper() == "Q": break pwd = input("密码") # user_object对象中有:name/password user_object = UserInfo(user, pwd,19) # user_dict = {"name":user,"password":pwd} user_object_list.append(user_object) # user_object_list.append(user_dict) # 展示用户信息 for obj in user_object_list: print(obj.name, obj.password)
注意:用字典也可以实现做封装,只不过字典在操作值时还需要自己写key,面向对象只需要 .
即可获取对象中封装的数据。
将数据分装到对象中,在方法中对原始数据进行加工处理。
总结:
-
仅做数据封装。
-
封装数据 + 方法再对数据进行加工处理。
-
创建同一类的数据且同类数据可以具有相同的功能(方法)。
2. 三大特性
面向对象编程在很多语言中都存在,这种编程方式有三大特性:封装、继承、多态。
2.1 封装
属性或者方法装起来【成员修饰符】
封装主要体现在两个方面:
-
将同一类方法封装到了一个类中,例如上述示例中:匪徒的相关方法都写在Terrorist类中;警察的相关方法都写在Police类中。
-
将数据封装到了对象中,在实例化一个对象时,可以通过
__init__
初始化方法在对象中封装一些数据,便于以后使用。 -
广义 :把属性和方法装起来,外面不能直接调用了,要通过类的名字来调用
-
狭义 :把属性和方法藏起来,外面不能调用,只能在内部偷偷调用
class User: def __init__(self,name,passwd): self.usr = name self.__pwd = passwd # 私有的实例变量/私有的对象属性 alex = User('alex','sbsbsb') print(alex.__pwd) # 报错 print(alex.pwd) # 报错 # 给一个名字前面加上了双下划綫的时候,这个名字就变成了一个私有的,所有的私有的内容或者名字都不能在类的外部调用,只能在类的内部使用了
class User: __Country = 'China' # 私有的静态变量 def func(self): print(User.__Country) # 在类的内部可以调用 print(User.Country) # 报错 在类的外部不能调用 print(User.__Country)# 报错 在类的外部不能调用 User().func()
加了双下划线的名字为啥不能从类的外部调用了?
class User: __Country = 'China' # 私有的静态变量 __Role = '法师' # 私有的静态变量 def func(self): print(self.__Country) # 在类的内部使用的时候,自动的把当前这句话所在的类的名字拼在私有变量前完成变形 print(User._User__Country) print(User._User__Role) # __Country -->'_User__Country': 'China' # __Role -->'_User__Role': '法师' # User.__aaa = 'bbb' # 在类的外部根本不能定义私有的概念
# 强制访问私有成员 class Foo: def __init__(self,name): self.__x = name obj = Foo('alex') print(obj._Foo__x) # 强制访问私有实例变量
在其他语言中的数据的级别都有哪些?在python中有哪些?
-
public 公有的 类内类外都能用,父类子类都能用 python支持
-
protect 保护的 类内能用,父类子类都能用,类外不能用 python不支持
-
private 私有的 本类的类内部能用,其他地方都不能用 python支持
2.2 继承
传统的理念中有:儿子可以继承父亲的财产。
在面向对象中也有这样的理念,即:子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)。
父类 – 基类
子类 – 派生类
class Base: def func(self): print("Base.func") class Son(Base): def show(self): print("Son.show") s1 = Son() s1.show() s1.func() # 优先在自己的类中找,自己没有才去父类。 s2 = Base() s2.func()
Son.show
Base.func
Base.func
class Animal: def __init__(self,name) -> None: self.name = name def eat(self): print(f'{self.name} is eating') def drink(self): print(f'{self.name} is drinking') def sleep(self): print(f'{self.name} is sleeping') class Cat(Animal): def clibe_tree(self): print(f'{self.name} is climbing') class Dog(Animal): def house_keep(self): print(f'{self.name} house keeping') white = Cat('小白') # 先开辟空间,空间里有一个类指针-->指向Cat # 调用init,对象在自己的空间中找init没找到,到Cat类中找init也没找到, # 找父类Animal中的init white.eat() white.clibe_tree() black = Dog('小黑') black.eat() black.house_keep()
小白 is eating
小白 is climbing
小黑 is eating
小黑 house keeping
子类想要调用父类的方法的同时还想执行自己的同名方法
猫和狗在调用eat的时候既调用自己的也调用父类的,
在子类的方法中调用父类的方法 :父类名.方法名(self)
class Animal: def __init__(self, name, food) -> None: self.name = name self.food = food self.blood = 100 self.waise = 100 def eat(self): print(f'{self.name} is eating {self.food}') def drink(self): print(f'{self.name} is drinking') def sleep(self): print(f'{self.name} is sleeping') class Cat(Animal): def eat(self): self.blood += 10 Animal.eat(self) def climb_tree(self): print('%s is climbing' % self.name) self.drink() class Dog(Animal): def eat(self): self.waise += 100 Animal.eat(self) def house_keep(self): print(f'{self.name} is keeping the house') white = Cat('小白','猫粮') black = Dog('小黑','狗粮') white.eat() black.eat() print(white.__dict__) print(black.__dict__) # 小白 is eating 猫粮 # 小黑 is eating 狗粮 # {'name': '小白', 'food': '猫粮', 'blood': 110, 'waise': 100} # {'name': '小黑', 'food': '狗粮', 'blood': 100, 'waise': 200}
父类和子类方法的选择:
子类的对象,如果去调用方法,永远优先调用自己的,如果自己有 用自己的,自己没有 用父类的,
如果自己有 还想用父类的 : 直接在子类方法中调父类的方法 父类名.方法名(self)
思考一:下面代码的输出?
class Foo: def __init__(self): self.func() # 在每一个self调用func的时候,我们不看这句话是在哪里执行,只看self是谁 def func(self): print('in foo') class Son(Foo): def func(self): print('in son') Son() # in son # <__main__.Son at 0x1aa31db8400>
注意事项:
-
self 到底是谁?
-
self 是哪个类创建的,就从此类开始找,自己没有就找父类。
思考二: 如果想给狗和猫定制个性的属性
class Animal: def __init__(self, name, food): self.name = name self.food = food self.blood = 100 self.waise = 100 def eat(self): print('%s is eating %s' % (self.name, self.food)) def drink(self): print('%s is drinking' % self.name) def sleep(self): print('%s is sleeping' % self.name) class Cat(Animal): def __init__(self, name, food, eye_color): Animal.__init__(self, name, food) # 调用了父类的初始化,去完成一些通用属性的初始化 self.eye_color = eye_color # 派生属性 class Dog(Animal): def __init__(self, name, food, size): Animal.__init__(self, name, food) self.size = size # 猫 : eye_color眼睛的颜色 # 狗 : size型号 小白 = Cat('小白', '猫粮', '蓝色') print(小白.__dict__) 小黑 = Dog('小黑', '狗粮', '藏獒') print(小黑.__dict__) # {'name': '小白', 'food': '猫粮', 'blood': 100, 'waise': 100, 'eye_color': '蓝色'} # {'name': '小黑', 'food': '狗粮', 'blood': 100, 'waise': 100, 'size': '藏獒'}
单继承
class D: def func(self): print('in D') class C(D): pass class A(C): def func(self): print('in A') class B(A): pass B().func() # in A
多继承
有好几个爹
有一些语言不支持多继承 java
python语言的特点 : 可以在面向对象中支持多继承
class B: def func(self): print('in B') class A: def func(self): print('in A') class C(B,A): pass C().func() # in B
单继承
-
调子类的 : 子类自己有的时候
-
调父类的 : 子类自己没有的时候
-
调子类和父类的 :子类父类都有,在子类中调用父类的
多继承
- 一个类有多个父类,在调用父类方法的时候,按照继承顺序,先继承的就先寻找
object类 (类祖宗)
所有在python3当中的类都是继承object类的,object中有init,所有的类都默认的继承object
开辟空间 调用__init__方法
class Foo: pass class Foo(object): pass
在python3中这俩的写法是一样,因为所有的类默认都会继承object类,全部都是新式类;如果在python2中这样定义,则称其为:经典类;
class Foo: pass
如果在python2中这样定义,则称其为:新式类
class Foo(object): pass class Base(object): pass class Bar(Base): pass
总结:
-
只要继承object类就是新式类
-
不继承object类的都是经典类
经典类:在py3中不存在,在py2中不主动继承object的类;在py2中 不继承object的类都是经典类
新式类:python3 所有的类都继承object类,都是新式类;在py2中 继承object类的就是新式类了
- 在py2中
`# 经典类 class A: pass # 新式类 class B(object): pass`
- 在py3中
`# 新式类 class A: pass # 新式类 class B(object): pass`
在单继承方面(无论是新式类还是经典类都是一样的)
class A: def func(self): pass class B(A): def func(self): pass class C(B): def func(self): pass class D(C): def func(self): pass d = D()
寻找某一个方法的顺序:D->C->B->A 越往父类走,是深度
多继承
class A: def func(self): print("A") class B(A): def func(self): print("B") class C(A): def func(self): print("C") class D(B, C): def func(self): print("D") print(D.mro()) # 经典类没有mro,但新式类有 d = D() d.func() # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] # D
在走到一个点,下一个点既可以从深度走,也可以从广度走的时候,总是先走广度,再走深度,广度优先
在经典类中,都是深度优先,总是在一条路走不通之后再换一条路,走过的点不会再走了
super()
class A(object): def func(self): print('A') class B(A): def func(self): super().func() print('B') class C(A): def func(self): super().func() print('C') class D(B,C): def func(self): super().func() # 找父类总是优先找离自己最近的一个 # super(D,self).func() print('D') D().func() # D,B,C,A
super
是按照mro
顺序来寻找当前类的下一个类
在py3中不需要传参数,自动就帮我们寻找当前类的mro顺序的下一个类中的同名方法
在py2中的新式类中,需要我们主动传递参数super(子类的名字,子类的对象).函数名()
这样才能够帮我们调用到这个子类的mro顺序的下一个类中的方法
在py2的经典类中,并不支持使用super来找下一个类
在D类中找super的func,那么可以这样写 super().func()
也可以这样写 super(D,self).func()
(并且在py2的新式类中必须这样写)
在单继承的程序中,super就是找父类
class User: def __init__(self,name): self.name = name class VIPUser(User): def __init__(self,name,level,strat_date,end_date): # User.__init__(self,name) super().__init__(name) # 推荐的 # super(VIPUser,self).__init__(name) self.level = level self.strat_date = strat_date self.end_date = end_date jack = VIPUser('jack',6,'2019-01-01','2020-01-01') print(jack.__dict__)
{‘name’: ‘jack’, ‘level’: 6, ‘strat_date’: ‘2019-01-01’, ‘end_date’: ‘2020-01-01’}
2.3 继承【补充】
对于Python面向对象中的继承,我们已学过:
继承存在意义:将公共的方法提取到父类中,有利于增加代码重用性。
继承的编写方式:
# 继承 class Base(object): pass class Foo(Base): pass
# 多继承 class Base(object): pass class Bar(object): pass class Foo(Base,Bar): pass
调用类中的成员时,遵循:
-
优先在自己所在类中找,没有的话则去父类中找。
-
如果类存在多继承(多个父类),则先找左边再找右边。
上述的知识点掌握之后,其实就可以解决继承相关的大部分问题。
但如果遇到一些特殊情况(不常见),你就可能不知道怎么搞了,例如:
2.4 mro
和c3
算法
如果类中存在继承关系,在可以通过mro()
获取当前类的继承关系(找成员的顺序)。
示例1:
class C(object): pass class B(object): pass class A(B, C): pass print( A.mro() ) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>] print( A.__mro__ ) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
示例2:
class D(object): pass class C(D): pass class B(object): pass class A(B, C): pass print( A.mro() ) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]
示例3:
class D(object): pass class C(object): pass class B(D): pass class A(B, C): pass print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class 'object'>]
示例4:
class D(object): pass class C(D): pass class B(D): pass class A(B, C): pass print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]
示例5:
简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
class M: pass class N: pass class E(M): pass class G: pass class K: pass class H(K): pass class D(G, H): pass class F(M, N): pass class P: pass class C(E, F): pass class B(D, E): pass class A(B, C, P): pass print(A.mro()) # 简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
特别补充:一句话搞定继承关系
不知道你是否发现,如果用正经的C3
算法规则去分析一个类继承关系有点繁琐,尤其是遇到一个复杂的类也要分析很久。
总结:从左到右,深度优先,大小钻石,留住顶端,基于这句话可以更快的找到继承关系。
简写为:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
2.5 py2和py3区别
在python2.2之前,只支持经典类【从左到右,深度优先,大小钻石,不留顶端】
后来,Python想让类默认继承object(其他语言的面向对象基本上也都是默认都继承object),此时发现原来的经典类不能直接集成集成这个功能,有Bug。
所以,Python决定不在原来的经典类上进行修改了,而是再创建一个新式类来支持这个功能。【从左到右,深度优先,大小钻石,留住顶端。】
- 经典类,不继承object类型
class Foo: pass
- 新式类,直接或间接继承object
class Base(object): pass class Foo(Base): pass
这样,python2.2之后 就出现了经典类和新式类共存。(正式支持是2.3)
最终,python3中丢弃经典类,只保留新式类。
详细文档:https://www.python.org/download/releases/2.3/mro/
总结:Python2和Python3在关于面向对象的区别
Py2:
经典类,未继承object类型。【从左到右,深度优先,大小钻石,不留顶端】
class Foo: pass
新式类,直接获取间接继承object类型。【从左到右,深度优先,大小钻石,留住顶端 – C3算法】
class Foo(object): pass
或
class Base(object): pass class Foo(Base): pass
Py3
新式类,丢弃了经典类只保留了新式类。【从左到右,深度优先,大小钻石,留住顶端 – C3算法】
class Foo: pass class Bar(object): pass
小结:
-
执行对象.方法时,优先去当前对象所关联的类中找,没有的话才去它的父类中查找。
-
Python支持多继承:先继承左边、再继承右边的。
-
self到底是谁?去self对应的那个类中去获取成员,没有就按照继承关系向上查找 。
2.6 多态
多态,按字面翻译其实就是多种形态。
-
其他编程语言多态
-
Python中多态
其他编程语言中,是不允许这样类编写的,例如:Java
`class Cat{ public void eat() { System.out.println("吃鱼"); } } class Dog { public void eat() { System.out.println("吃骨头"); } public void work() { System.out.println("看家"); } } public class Test { public static void main(String[] args) { obj1 = Cat() obj2 = Cat() show(obj1) show(obj2) obj3 = Dog() show(obj3) } public static void show(Cat a) { a.eat() } }`
在java或其他语言中的多态是基于:接口 或 抽象类和抽象方法来实现,让数据可以以多种形态存在。
在Python中则不一样,由于Python对数据类型没有任何限制,所以他天生支持多态。
def func(arg): v1 = arg.copy() # 浅拷贝 print(v1) func("lisa") func([11,22,33,44])
class Email(object): def send(self): print("发邮件") class Message(object): def send(self): print("发短信") def func(arg): v1 = arg.send() print(v1) v1 = Email() func(v1) v2 = Message() func(v2)
在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在鸭子类型中,关注点在于对象的行为,能做什么;而不是关注对象所属的类型,例如:一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。
多态(多种形态/多种类型)鸭子模型
一个类型表现出来的多种状态
我们给出一个现实示例:
例如支付 表现出的 微信支付
和苹果支付
这两种状态;在java情况下: 一个参数必须制定类型
所以如果想让两个类型的对象都可以传,那么必须让这两个类继承自一个父类,在制定类型的时候使用父类来指定
# Python def func(arg): v = arg[-1] # arg.append(9) print(v) # java def func(str arg): v = arg[-1] print(v) class Payment:pass class WeChat(Payment): def __init__(self,name): self.name = name def pay(self,money): dic = {'username':self.name,'money':money} # 想办法调用微信支付 url连接 把dic传过去 print('%s通过微信支付%s钱成功'%(self.name,money)) class Apple(Payment): def __init__(self,name): self.name = name def pay(self,money): dic = {'name': self.name, 'number': money} # 想办法调用苹果支付 url连接 把dic传过去 print('%s通过苹果支付%s钱成功' % (self.name, money)) #JAVA def pay(Payment a, int b): a.pay(b) obj = Apple('alex') pay(obj,400) obj = WeChat('alex') pay(obj,400)
面试题:什么是鸭子模型?
对于一个函数而言,Python对于参数的类型不会限制,那么传入参数时就可以是各种类型,在函数中如果有例如:arg.send
方法,那么就是对于传入类型的一个限制(类型必须有send方法)。这就是鸭子模型,类似于上述的函数我们认为只要能呱呱叫的就是鸭子(只有有send方法,就是我们要想的类型)
3. 成员
面向对象中的所有成员如下:
变量
-
实例变量
-
类变量
方法
-
绑定方法
-
类方法
-
静态方法
属性
通过面向对象进行编程时,会遇到很多种情况,也会使用不同的成员来实现,接下来我们来逐一介绍成员特性和应用场景。
python中一切皆对象,对象的类型就是类:
所有的对象都有一个都有一个类型,class A实例化出来的对象的类型就是A类:
类型:int、float、str、list、tuple、set
、–类(内置置的数据类型,内置的类):
在类的命名空间:静态变量 绑定方法:
能定义到类中的:
-
静态变量 是个所有对象共享的变量 由对象\类调用,但是不能重新赋值
-
绑定方法 是个自带self参数的函数 由对象调用
-
类方法 是个自带cls参数的函数 由对象\类调用
-
静态方法 是个啥也不带的函数 由对象\类调用
-
property
属性 是个伪装成属性的方法 由对象调用 但是不加括号,类名调用就是一个property
对象地址(<property object at 0x0000021011049318>
)
类中的静态变量的用处
-
如果一个变量 是所有的对象共享的值,那么这个变量应该被定义成静态变量
-
所有和静态变量相关的增删改查都应该使用类名来处理
-
而不应该使用对象名直接修改静态变量
调用的习惯
-
类名.静态变量
-
对象.静态变量 (对象调用静态变量的时候,不能对变量进行赋值操作 对象.静态变量 = xxx)
3.1 变量
-
实例变量,属于对象,每个对象中各自维护自己的数据。
-
类变量,属于类,可以被所有对象共享,一般用于给对象提供公共数据(类似于全局变量)。
class Person(object): country = "中国" def __init__(self, name, age): self.name = name self.age = age def show(self): # message = "{}-{}-{}".format(Person.country, self.name, self.age) message = "{}-{}-{}".format(self.country, self.name, self.age) print(message) print(Person.country) # 中国 p1 = Person("lisa",20) print(p1.name) print(p1.age) print(p1.country) # 中国 p1.show() # 中国-lisa-20
提示:当把每个对象中都存在的相同的示例变量时,可以选择把它放在类变量中,这样就可以避免对象中维护多个相同数据。
易错点 & 面试题
第一题:注意读和写的区别。
class Person(object): country = "中国" def __init__(self, name, age): self.name = name self.age = age def show(self): message = "{}-{}-{}".format(self.country, self.name, self.age) print(message) print(Person.country) # 中国 p1 = Person("lisa",20) print(p1.name) # lisa print(p1.age) # 20 print(p1.country) # 中国 p1.show() # 中国-lisa-20 p1.name = "root" # 在对象p1中讲name重置为root p1.num = 19 # 在对象p1中新增实例变量 num=19 p1.country = "china" # 在对象p1中新增实例变量 country="china" print(p1.country) # china print(Person.country) # 中国
class Person(object): country = "中国" def __init__(self, name, age): self.name = name self.age = age def show(self): message = "{}-{}-{}".format(self.country, self.name, self.age) print(message) print(Person.country) # 中国 Person.country = "美国" p1 = Person("lisa",20) print(p1.name) # lisa print(p1.age) # 20 print(p1.country) # 美国
第二题:继承关系中的读写
class Base(object): country = "中国" class Person(Base): def __init__(self, name, age): self.name = name self.age = age def show(self): message = "{}-{}-{}".format(Person.country, self.name, self.age) # message = "{}-{}-{}".format(self.country, self.name, self.age) print(message) # 读 print(Base.country) # 中国 print(Person.country) # 中国 obj = Person("lisa",19) print(obj.country) # 中国 # 写 Base.country = "china" Person.country = "泰国" obj.country = "日本"
面试题
class Parent(object): x = 1 class Child1(Parent): pass class Child2(Parent): pass print(Parent.x, Child1.x, Child2.x) # 1 1 1 Child1.x = 2 # 赋值操作 print(Parent.x, Child1.x, Child2.x) # 1 2 1 Parent.x = 3 print(Parent.x, Child1.x, Child2.x) # 3 2 3
3.2 方法
-
绑定方法,默认有一个self参数,由对象进行调用(此时self就等于调用方法的这个对象)【对象&类均可调用】
-
类方法,默认有一个cls参数,用类或对象都可以调用(此时cls就等于调用方法的这个类)【对象&类均可调用】
-
静态方法,无默认参数,用类和对象都可以调用。【对象&类均可调用】
class Foo(object): def __init__(self, name,age): self.name = name self.age = age def f1(self): print("绑定方法", self.name) @classmethod def f2(cls): print("类方法", cls) @staticmethod def f3(): print("静态方法") # 绑定方法(对象) obj = Foo("lisa",20) obj.f1() # Foo.f1(obj) # 类方法 Foo.f2() # cls就是当前调用这个方法的类。(类) obj.f2() # cls就是当前调用这个方法的对象的类。 # 静态方法 Foo.f3() # 类执行执行方法(类) obj.f3() # 对象执行执行方法
在Python中比较灵活,方法都可以通过对象和类进行调用;而在java、c#等语言中,绑定方法只能由对象调用;类方法或静态方法只能由类调用。
import os import requests class Download(object): def __init__(self, folder_path): self.folder_path = folder_path @staticmethod def download_dou_yin(): # 下载抖音 res = requests.get('.....') with open("xxx.mp4", mode='wb') as f: f.write(res.content) def download_dou_yin_2(self): # 下载抖音 res = requests.get('.....') path = os.path.join(self.folder_path, 'xxx.mp4') with open(path, mode='wb') as f: f.write(res.content) obj = Download("video") obj.download_dou_yin()
面试题:在类中 @classmethod 和 @staticmethod 的作用?
3.3 属性
属性其实是由绑定方法 + 特殊装饰器 组合创造出来的,让我们以后在调用方法时可以不加括号,例如:
class Foo(object): def __init__(self, name): self.name = name def f1(self): return 123 @property def f2(self): return 123 obj = Foo("lisa") v1 = obj.f1() print(v1) v2 = obj.f2 print(v2)
示例:分页的功能。
class Pagination: def __init__(self, current_page, per_page_num=10): self.per_page_num = per_page_num if not current_page.isdecimal(): self.current_page = 1 return current_page = int(current_page) if current_page < 1: self.current_page = 1 return self.current_page = current_page def start(self): return (self.current_page - 1) * self.per_page_num def end(self): return self.current_page * self.per_page_num user_list = ["用户-{}".format(i) for i in range(1, 3000)] # 分页显示,每页显示10条 while True: page = input("请输入页码:") # page,当前访问的页码 # 10,每页显示10条数据 # 内部执行Pagination类的init方法。 pg_object = Pagination(page, 20) page_data_list = user_list[ pg_object.start() : pg_object.end() ] for item in page_data_list: print(item)
class Pagination: def __init__(self, current_page, per_page_num=10): self.per_page_num = per_page_num if not current_page.isdecimal(): self.current_page = 1 return current_page = int(current_page) if current_page < 1: self.current_page = 1 return self.current_page = current_page @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num user_list = ["用户-{}".format(i) for i in range(1, 3000)] # 分页显示,每页显示10条 while True: page = input("请输入页码:") pg_object = Pagination(page, 20) page_data_list = user_list[ pg_object.start : pg_object.end ] for item in page_data_list: print(item)
其实,除了咱们写的示例外,在很多模块和框架的源码中也有porperty的身影,例如:requests模块。
import requests # 内部下载视频,并将下载好的数据分装到Response对象中。 res = requests.get( url="https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg", headers={ "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS" } ) # 去对象中获取text,其实需要读取原始文本字节并转换为字符串 res.text
3.4 类属性和对象属性
class Cat: # 类属性 name = '加菲' age = 2 likes = ['鱼'] def __init__(self, name, color): # 对象属性 self.name = name self.color = color self.likes2 = ['鱼'] # 对象 cat = Cat('小白', '白色')
调用类属性:
-
类调用,直接得到类属性
-
对象调用,先得到对象属性,如果没有对象属性则获取类属性
print(Cat.age) print(cat.age) print(Cat.name) print(cat.name)
调用对象属性
print(Cat.color) *# 报错,类不可以调用对象属性* print(cat.color)
修改
Cat.name = '加菲2' cat.name = '小白2' print(cat.name) Cat.age = 3 cat.age = 3 *# 给cat对象新增一个age对象属性* print(Cat.age) print(cat.age)
类属性:是共享的
同一个类创建的不同对象共享属性
c1 = Cat('加菲', '黄色') c2 = Cat('汤姆', '蓝色') print(c1.likes) print(c2.likes) c1.likes.append('老鼠') print(c1.likes) print(c2.likes) print()
对象属性:不同对象内存是独立
print(c1.likes2) print(c2.likes2) c1.likes2.append('老鼠') print(c1.likes2) print(c2.likes2)
关于属性的编写有两种方式:
- 方式一,基于装饰器
class C(object): @property def x(self): pass @x.setter def x(self, value): pass @x.deleter def x(self): pass obj = C() obj.x obj.x = 123 del obj.x
- 方式二,基于定义变量
class C(object): def getx(self): pass def setx(self, value): pass def delx(self): pass x = property(getx, setx, delx, "I'm the 'x' property.") obj = C() obj.x obj.x = 123 del obj.x
写在最后,对属性进行一个补充:
由于属性和实例变量的调用方式相同,所以在编写时需要注意:属性名称 不要 实例变量 重名。
class Foo(object): def __init__(self, name, age): self.name = name self.age = age @property def func(self): return 123 obj = Foo("lisa", 123) print(obj.name) print(obj.func)
一旦重名,可能就会有报错。
class Foo(object): def __init__(self, name, age): self.name = name # 报错,错认为你想要调用 @name.setter 装饰的方法。 self.age = age @property def name(self): return "{}-{}".format(self.name, self.age) obj = Foo("lisa", 123)
class Foo(object): def __init__(self, name, age): self.name = name self.age = age @property def name(self): return "{}-{}".format(self.name, self.age) # 报错,循环调用自己(直到层级太深报错) @name.setter def name(self, value): print(value) obj = Foo("lisa", 123) print(obj.name)
如果真的想要在名称上创建一些关系,可以让实例变量加上一个下划线。
class Foo(object): def __init__(self, name, age): self._name = name self.age = age @property def name(self): return "{}-{}".format(self._name, self.age) obj = Foo("lisa", 123) print(obj._name) print(obj.name)
4.成员修饰符
Python中成员的修饰符就是指的是:公有、私有。
-
公有,在任何地方都可以调用这个成员。
-
私有,只有在类的内部才可以调用改成员(成员是以两个下划线开头,则表示该成员为私有)。
示例一:
class Foo(object): def __init__(self, name, age): self.__name = name # 私有,外部无法访问 self.age = age def get_data(self): return self.__name def get_age(self): return self.age obj = Foo("lisa", 123) # 公有成员 print(obj.age) v1 = self.get_age() print(v1) # 私有成员 # print(obj.__name) # 错误,由于是私有成员,只能在类中进行使用。 v2 = obj.get_data() print(v2)
示例2:
class Foo(object): def get_age(self): print("公有的get_age") def __get_data(self): print("私有的__get_data方法") def proxy(self): print("公有的proxy") self.__get_data() obj = Foo() obj.get_age() obj.proxy() # 公有的get_age # 公有的proxy # 私有的__get_data方法
示例3:
class Foo(object): @property def __name(self): print("公有的get_age") @property def proxy(self): print("公有的proxy") self.__name return 1 obj = Foo() v1 = obj.proxy print(v1) class Foo(object): @property def __name(self): print('公有的name') @property def proxy(self): print('公有的proxy') print(self.__name) return 1 obj = Foo() v1 = obj.proxy print(v1) # 公有的proxy # 公有的name # None # 1
小测试:请用Python语言实现功能:输入年月日,判断这一天是这一年的第几天?
import datetime def day_of_year(): year = int(input('请输入年份:')) month = int(input('请输入月份:')) day = int(input('请输入天:')) date1 = datetime.date(year=year, month=month, day=day) date2 = datetime.date(year=year, month=1, day=1) return (date1 - date2).days + 1 print(day_of_year()) class ShowDate: def __init__(self,year,month,day) -> None: self.year = year self.mounth = mounth self.day = day @property def showDay(self): date1 = datetime.date(year=self.year, month=self.month, day=self.day) date2 = datetime.date(year=self.year, month=1, day=1) return (date1 - date2).days + 1
特别提醒:父类中的私有成员,子类无法继承。
class Base(object): def __data(self): print("base.__data") def num(self): print("base.num") class Foo(Base): def func(self): self.num() self.__data() # # 不允许执行父类中的私有方法 obj = Foo() obj.func() # base.num # AttributeError: 'Foo' object has no attribute '_Foo__data'
写在最后,按理说私有成员是无法被外部调用,但如果用一些特殊的语法也可以(Flask源码中有这种写法,大家写代码不推荐这样写)。
class Foo(object): def __init__(self): self.__num = 123 self.age = 19 def __msg(self): print(1234) obj = Foo() print(obj.age) print(obj._Foo__num) obj._Foo__msg()
成员是否可以作为独立的功能暴露给外部,让外部调用并使用。
-
可以,公有。
-
不可以,内部其他放的一个辅助,私有。
5. 总结
面向对象编程是一种强大的编程范式,它以对象作为程序的基本单元,通过封装、继承和多态等概念来组织和管理代码。下面是对面向对象编程的总结:
封装:面向对象编程通过封装将数据和方法组合在一起,形成类的实例。封装隐藏了实现细节,使得代码模块化、易于维护,提高了代码的可读性和安全性。
继承:继承是一种重要的概念,它允许创建一个新类,它继承了现有类的属性和方法,并可以在此基础上进行扩展或修改。继承提供了代码的重用性和层次化结构,促进了代码的可扩展性。
多态:多态性允许不同的对象对相同的方法做出不同的响应。它允许我们使用通用的接口处理不同的对象,增加了代码的灵活性和可扩展性。
通过面向对象编程,我们能够更好地模拟现实世界的概念和关系,创建可重用、可维护的代码。面向对象编程提供了更结构化和模块化的开发方式,使得代码更易于理解、测试和调试。同时,它也促进了团队协作和代码复用,提高了开发效率。
总之,掌握面向对象编程是一个重要的技能,它为我们提供了创建复杂、灵活且可扩展的应用程序的能力。在实践中,合理运用面向对象编程的原则和概念,能够帮助我们构建出更高质量、可维护的Python程序。
**
题外话
当下这个大数据时代不掌握一门编程语言怎么跟的上脚本呢?当下最火的编程语言Python前景一片光明!如果你也想跟上时代提升自己那么请看一下.
感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。
👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
简历模板
👉 CSDN大礼包:gift::[全网最全《Python学习资料》免费赠送:free:!](https://blog.csdn.net/weixin_68789096/article/details/132275547?spm=1001.2014.3001.5502) (安全链接,放心点击)若有侵权,请联系删除