Python面向对象:代码的魔法(上篇)

**

引言

了解完函数式编程之后,我们了解一下面向对象编程。函数式编程和面向对象编程是两种不同的编程范式,它们各自有着独特的优点和适用场景。函数式编程强调函数的纯粹性和不可变性,通过组合和转换函数来构建程序。而面向对象编程则注重对象的封装、继承和多态,通过定义类和方法来组织代码。

然而,在实际开发中,函数式编程和面向对象编程并非是完全互斥的。实际上,这两种范式可以很好地结合使用,相互补充,从而提供更灵活、可扩展的代码结构。

借助函数式编程的思想,我们可以将复杂的业务逻辑分解为一系列纯函数,实现模块化和可测试性。而面向对象编程则可以利用类的封装和继承特性,提供更强大的抽象和代码复用能力。

通过将函数式编程的思想应用于面向对象编程中,我们可以借助高阶函数、闭包等特性,使得类的方法更具表达力和灵活性。同时,将面向对象编程的技巧应用于函数式编程中,可以使函数式代码更易于理解和维护。

综上所述,函数式编程和面向对象编程可以相辅相成,互为补充。在实际项目中,我们可以根据需求的复杂程度和团队的开发习惯,灵活选择使用函数式编程或面向对象编程,或者二者的结合,以达到更好的开发效果和代码质量。

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","注册成功")   

注意:

  1. 类名称首字母大写&驼峰式命名;

  2. py3之后默认类都继承object;

  3. 在类中编写的函数称为方法;

  4. 每个方法的第一个参数是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 mroc3算法

如果类中存在继承关系,在可以通过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程序。

**

---------------------------END---------------------------

题外话

当下这个大数据时代不掌握一门编程语言怎么跟的上脚本呢?当下最火的编程语言Python前景一片光明!如果你也想跟上时代提升自己那么请看一下.

在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。


👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述
👉 CSDN大礼包:gift::[全网最全《Python学习资料》免费赠送:free:!](https://blog.csdn.net/weixin_68789096/article/details/132275547?spm=1001.2014.3001.5502) (安全链接,放心点击)

若有侵权,请联系删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值