面向对象编程

面向对象编程(object-oriented programming)

  • 面向过程:根据业务逻辑从上到下写代码

  • 面向对象:将数据与函数绑定到一块,分类封装(即隐藏实现的内部细节),每个程序员只负责给自己的分类,这样能更快速的开发程序,减少了重复代码。继承使类得到泛化,多态实现基于对象类型的动态分类。

1.类的创建与属性访问

注意📢

  • 通常需要在单独的文件中定义一个类

  • Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName( 对象名._类名__私有属性名 ) 访问属性

单下划线、双下划线和头尾双下划线说明:

  • __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似__init__()之类的。

  • _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *

  • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

  • __private_method: 两个下划线开头,声明该方法为私有方法,不能在类的外部调用。


# 定义汽车类
class Car:
    quality = 'Good'
    __durance = 'Bad'
    # 初始化函数,对象属性有默认值:4和white;也可以通过传参的方法对对象属性进行重新赋值
    def __init__(self, wheelNum=4, color='white'):
        self.wheelNum = wheelNum
        self.color = color
    # 定义类方法
    def move(self):
        print('车在行驶')

# 创建对象五菱神车,不传参时,属性使用默认值
Wuling = Car() 
print('五菱车的颜色为:',  Wuling.color)
print('五菱车轮胎数量为:', Wuling.wheelNum)

# 创建对象自行车,传参时,新的传参值代替默认值
Bicycle = Car(2, 'black')
print('自行车的颜色为:',  Bicycle.color)
print('自行车轮胎数量为:', Bicycle.wheelNum)

print(hasattr(Wuling, 'color'))                     # 如果存在 'color' 属性返回 True。
print(getattr(Bicycle, 'color'))                    # 返回实例化对象Bycicle 'color' 属性的值
setattr(Wuling, 'color', 'Light white')             # Wuling对象添加属性 'color' 值为 'Light white'
print(getattr(Wuling, 'color'))
delattr(Wuling, 'color')                            # 删除Wuling对象的属性 'color',hasattr()方法返回 False

c = Car()
print(c.quality)                        # 实例对象可以被访问到共有属性
# print(Car.__durance)                    # 实例对象及类对象都无法访问到私有属性,会报错 AttributeError
print(c._Car__durance)                  # 该种特殊的访问方式可以从外部访问到实例对象的私有属性

c.quality = 'so-so'                     # 修改实例属性
print(c.quality)                        # 访问实例属性,实例属性会屏蔽掉同名的类属性
print(Car.quality)                      # 访问类属性,没有被改变
Car.quality = 'Bad'                     # 修改类属性
print(Car.quality)                      # 被改变


2.Python 内置类属性

  • __dict__ : 类的属性(包含一个字典,由类的数据属性组成)

  • __doc__ :类的文档字符串

  • __name__: 类名,实例化对象没有该属性,只有父类才能访问到

  • __module__: 类定义所在的模块(类的全名是 __main__.className,如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod

  • __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)


# 定义汽车类
class Car:
    '所有车的基类'
    # 初始化函数,对象属性有默认值:4和white;也可以通过传参的方法对对象属性进行重新赋值
    def __init__(self, name, wheelNum=4, color='white'):
        self.name = name
        self.wheelNum = wheelNum
        self.color = color
    # 定义类方法
    def move(self):
        print('车在行驶')


# 创建对象自行车,传参时,新的传参值代替默认值
Bicycle = Car('bicycle', 2, 'black')

print("Car.__doc__:", Car.__doc__) 
print("Car.__name__:", Car.__name__) 
print("Bicycle.__module__:", Bicycle.__module__) 
print("Car.__bases__:", Car.__bases__) 
print("Bicycle.__dict__:", Bicycle.__dict__) 


3.实例方法、类方法和静态方法

  • 实例方法

    • 最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 会自动完成绑定)。实例方法通常会用类对象直接调用
  • 类方法

    • 类对象所拥有的方法,需要用修饰器@classmethodcls来标识其为类方法

    • 最少也要包含一个参数,第一个参数必须是类对象,一般将其命名为 cls,可以通过实例对象和类对象去访问

    • 可以对类属性进行修改

  • 静态方法

    • 需要通过修饰器@staticmethod来进行修饰,主要是用来存放逻辑性的代码

    • 逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护


3.1 类方法

class People(object):
    country = 'china'

    #类方法,用classmethod来进行修饰
    @classmethod
    def getCountry(cls):
        return cls.country
    @classmethod
    def setCountry(cls,country):
        cls.country = country

p = People()
print(p.getCountry())    #可以用过实例对象引用
print(People.getCountry())    #可以通过类对象引用


p.setCountry('japan') 
print(p.getCountry())   
print(People.getCountry())


3.2 静态方法

import time

# 定义一个对时间进行操作的类,其中包含获取当前时间的操作,该方法不受实例化影响,即仅存于该类的命名空间中,便于后期维护
class TimeTest(object):
    def __init__(self, hour, minute, second):
        self.hour = hour
        self.minute = minute
        self.second = second

    @staticmethod
    def showTime():
        return time.strftime("%H:%M:%S", time.localtime())

# 使用类对象调用静态方法
print(TimeTest.showTime())              # 17:28:39
# 实例化对象
t = TimeTest(2, 10, 10)
# 使用实例对象调用静态方法 
print(t.showTime())                     # 17:28:39


4.魔法方法

魔法方法是在Python的类中被双下划线前后包围的方法,如常见的 :__init____new____del__等。这些方法在类或对象进行特定的操作时会自动被调用,我们可以使用或重写这些魔法方法,给自定义的类添加各种特殊的功能来满足自己的需求


4.1 __new__

  • __new__(cls, […]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。它的第一个参数是这个类,其他的参数是用来直接传递给__init__ 方法。

  • __new__ 决定是否要使用该 __init__ 方法,因为__new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果__new__ 没有返回实例对象,则__init__ 不会被调用。

  • 当我们需要继承内置类时,例如,想要继承 int、str、tuple,就无法使用__init__ 来初始化了,只能通过__new__ 来初始化数据。

class Person(object):

   def __new__(cls, *args, **kwargs):
       print("__new__()方法被调用了")
       print('这个是*agrs', *args)
       print('这个是kwagrs', **kwargs)
       
       # cls表示这个类,剩余所有的参数传给__init__()方法,
       # 若不返回,则__init__()不会被调用
       return object.__new__(cls)

   def __init__(self, name, age):
       print("__init__()方法被调用了")
       self.name = name
       self.age = age
       print(self.name, self.age)

p = Person("张三", 20)

# Output:
# __new__()方法被调用了
# 这个是*agrs 张三 20
# 这个是kwagrs
# __init__()方法被调用了
# 张三 20


4.2 Python对象销毁(垃圾回收) -> __del__

  • Python 使用了引用计数这一简单技术来跟踪和回收垃圾

    • 在 Python 内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量,称为一个引用计数器。
    • 当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。
  • 垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。

    • 循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。
  • Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充,垃圾收集器也会留心被分配的总量很大(即未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。

实例

  • 析构函数 del ,__del__在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行:
class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   # 析构器,当一个实例被销毁时自动调用的方法。
   def __del__(self):
      class_name = self.__class__.__name__
      print(class_name, "销毁") 
 
pt1 = Point()                                   # 创建对象,<pt1> 
pt2 = pt1                                       # 增加引用,<pt1> 的计数
pt3 = pt1                                       # 增加引用,<pt1> 的计数
print(id(pt1), id(pt2), id(pt3))                # 打印对象的id
del pt1                                         # 减少引用
del pt2
del pt3

4.3 初始化实例对象 -> __init__

  • __init__(self)中,默认有1个参数名字为self,如果在创建对象时传递了2个实参,那么除了self作为第一个形参外还需要2个形参,例如 init(self, x, y)

  • __init__(self)中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递进去。

class Washer:
   def __int__(self):
       pass

haier = Washer() 
print(haier)

# output
# <__main__.Washer object at 0x00000266AFF21550>


4.4 表示类魔法方法

  • 用来描述类或对象的信息

  • __repr__(object):将对象转化为供解释器读取的形式,返回一个对象的 string 格式。

  • __str__(object)的目标是可读性,返回一个对象的 string 格式。

  • 当两者同时定义在一个类内时,只返回__str__方法的返回值。 一般的,也可以只定义__str__方法而不定义__repr__方法。

实例

class Washer:
   def __int__(self, name, cost):
       pass
   def __repr__(self) -> str:
        return 'This is __repr__'

haier = Washer() 
print(haier)

# output
# This is __repr__
class Washer:
   def __int__(self):
       pass

   def __repr__(self):
       return '我是__repr__()魔法方法!'

   def __str__(self):
       """
       类的说明或对象状态的说明
       :return:
       """
       return '我是__str__魔法方法!'

haier = Washer()
# 不定义str方法,直接打印,结果是对象的内存地址,定义了str方法,显示的就是str方法返回的内容
print(haier)  

# output
# 我是__str__魔法方法


4.5 访问类魔法方法

  • __setattr__:定义当一个属性被设置时的行为

  • __getattr__:定义当用户试图获取一个不存在的属性时的行为

  • __delattr__:删除某个属性时调用

  • __getattribute__:访问任意属性或方法时调用

class Person(object):

    def __setattr__(self, key, value):
        """属性赋值"""
        if key not in ('name', 'age'):
            return
        if key == 'age' and value < 0:
            raise ValueError()
        super(Person, self).__setattr__(key, value)

    def __getattr__(self, key):
        """访问某个不存在的属性"""
        return 'unknown'

    def __delattr__(self, key):
        """删除某个属性"""
        if key == 'name':
            raise AttributeError()
        super().__delattr__(key)

    def __getattribute__(self, key):
        """所有属性/方法调用都经过这里"""
        if key == 'money':
            return 100
        elif key == 'hello':
            return self.say
        return super().__getattribute__(key)

p1 = Person()
p1.name = '张三'                    # 调用__setattr__
p1.age = 20                        # 调用__setattr__
print(p1.name, p1.age)             # 张三 20

setattr(p1, 'name', '李四')	         # 调用__setattr__
setattr(p1, 'age', 30)              # 调用__setattr__
print(p1.name, p1.age)              # 李四 30

print(p1.sex)                       # 调用__getattr__

# 上面只要是访问属性的地方,都会调用__getattribute__方法


4.6 比较操作类魔法方法

  • __eq__() 可以判断两个对象是否相等。

  • __ne__() 判断两个对象是否不相等,这个和__eq__()方法基本一样,只不过这个是反面。

  • __lt__() 比较对象的大小的, __lt__为小于。

  • __gt__() 比较对象的大小的,__gt__为大于。

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __eq__(self, other):
        return self.uid == other.uid
    
    def __ne__(self, other):
        """对象 != 判断"""
        return self.uid != other.uid

    def __lt__(self, other):
        """对象 < 判断 根据self.uid"""
        return self.uid < other

    def __gt__(self, other):
        """对象 > 判断 根据self.uid"""
        return self.uid > other
    
p1 = Person(1)
p2 = Person(1)
p3 = Person(2)
 
print(p1 == p2) # True
print(p2 == p3) # False

 
print(p1 != p2) # False
print(p2 != p3) # True

 
print(p1 < p2) # False
print(p2 < p3) # True

print(p1 > p2) # False
print(p2 > p3) # False


4.7 容器类魔法方法

  • Python 中常见的容器类型有以下四种,它们都是 「可迭代」

    • 字典
    • 元组
    • 列表
    • 字符串
  • 容器类的魔法方法包括:

    • __setitem__(self, key, value):定义设置容器中指定元素的操作 ,相当于 self[key] = value;

    • __getitem__(self, key): 定义获取容器中指定元素的操作 ,相当于 self[key];

    • __delitem__(self, key):定义删除容器中指定元素的操作 ,相当于 del self[key];

    • __len__(self):定义当被 len() 调用时的操作(返回容器中元素的个数);

    • __iter__(self):定义迭代容器中的元素的操作;返回值有两种情况:

      • 返回 iter(obj):代表使用 obj 对象的迭代协议,一般 obj 是内置的容器对象;

      • 返回 self:代表迭代的逻辑由本类来实现,此时需要重写 next 方法,实现自定义的迭代逻辑

    • __contains__(self, item):定义当使用成员测试运算符(in 或 not in)时的操作;

    • __reversed__(self):定义当被 reversed() 调用时的操作。

class MyList(object):
    """自己实现一个list"""

    def __init__(self, values=None):
        # 初始化自定义list
        self.values = values or []
        self._index = 0

    def __setitem__(self, idx, value):
        # 添加元素
        self.values[idx] = value

    def __getitem__(self, idx):
        # 获取元素
        return self.values[idx]

    def __delitem__(self, idx):
        # 删除元素
        del self.values[idx]

    def __len__(self):
        # 自定义list的元素个数
        return len(self.values)

    def __iter__(self):
        # 可迭代
        return self

    def __next__(self):
        # 迭代的具体细节
        # 如果__iter__返回self 则必须实现此方法
        if self._index >= len(self.values):
            raise StopIteration()
        value = self.values[self._index]
        self._index += 1
        return value

    def __contains__(self, idx):
        # 元素是否在自定义list中
        return idx in self.values

    def __reversed__(self):
        # 反转
        return list(reversed(self.values))

# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])

print(my_list[0])	     # __getitem__
my_list[1] = 20		     # __setitem__

print(1 in my_list)	     # __contains__
print(len(my_list))     # __len__

print([i for i in my_list])  # __iter__
del my_list[0]	             # __del__

reversed_list = reversed(my_list) # __reversed__
print([i for i in reversed_list])  # __iter__


# output
# 1
# True
# 5
# [1, 20, 3, 4, 5]
# [5, 4, 3, 20]


4.8 可调用对象魔法方法

  • 在Python中,方法也是一种高等的对象。这意味着他们也可以像其他对象一样被传递到方法中,这是一个非常惊人的特性。 Python中有一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这个魔法方法就是__call__(self, [args…])

  • 这个魔法方法通常会用在类实现一个装饰器、元类等场景中

class Circle(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, x, y):
        self.x = x
        self.y = y

a = Circle(10, 20)	    # __init__
print(a.x, a.y)	        # 10 20

a(100, 200)	            # 此时a这个对象可以当做一个方法来执行,这是__call__魔法方法的功劳
print(a.x, a.y)	        # 100 200

5.继承


5.1 实例

  • 使用issubclass()或者isinstance()方法来检测

    • issubclass(sub,sup) -> 布尔函数判断一个类是另一个类的子类或者子孙类

    • isinstance(obj, Class) -> 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。

# 定义父类
class Parent:        
   parentAttr = 100
   def __init__(self):
      print("调用父类构造函数") 
 
   def parentMethod(self):
      print('调用父类方法') 
 
   def setAttr(self, attr):
      Parent.parentAttr = attr
 
   def getAttr(self):
      print("父类属性 :", Parent.parentAttr) 
 

# 定义子类 
class Child(Parent): 
   def __init__(self):
      print("调用子类构造方法") 
 
   def childMethod(self):
      print('调用子类方法') 


# 定义孙类,继承自 Child,Parent
class Grandson(Child, Parent):
    pass
 

c = Child()          # 实例化子类
c.childMethod()      # 调用子类的方法
c.parentMethod()     # 调用父类方法
c.setAttr(200)       # 再次调用父类的方法 - 设置属性值
c.getAttr()          # 再次调用父类的方法 - 获取属性值

g = Grandson()       # 可调用 Child 和 Parent 类中的所有方法
print(issubclass(Grandson, Child))      # 传入的参数必须是类,而不是实例对象
print(isinstance(g, Grandson))          # 判断该实例对象是否是父类或继承于该父类的所有子类中的实例对象

5.2 继承形式

  • 显示继承,不重写父类中构造的方法

  • 重写继承,只显示子类中重写后覆盖父类的同名方法的返回结果

  • super(子类,self).__init__(参数1,参数2,....)父类名称.__init__(self,参数1,参数2,...)

    • 既保留子类中的重写父类的同名方法后的返回结果,又继承了父类中该方法的返回结果
# 子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('Bob')
    print ( son.getName() )


""" 输出结果:
name: Bob
Son Bob """
# 如果重写了__init__ 时,实例化子类,就不会调用父类已经定义的 __init__
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        print ( "hi" )
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('Bob')
    print ( son.getName() )


""" 输出结果
hi
Son Bob """
# 如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name))
    def getName(self):
        return 'Father ' + self.name
 
class Son(Father):
    def __init__(self, name):
        super(Son, self).__init__(name) 
        # Father.__init__(self, Son)              # 作用同 super(),此外还输出:name: <class '__main__.Son'>       
        print ("hi")
        self.name =  name
    def getName(self):
        return 'Son '+self.name
 
if __name__=='__main__':
    son=Son('Bob')
    print ( son.getName() )

基础重载方法:

序号方法,描述 & 调用
1__init__ ( self [,args...] ),构造函数,简单的调用方法: obj = className(args)
2__del__( self ),析构方法, 删除一个对象,简单的调用方法 : del obj
3__repr__( self ),转化为供解释器读取的形式,简单的调用方法 : repr(obj)
4__str__( self ),用于将值转化为适于人阅读的形式,简单的调用方法 : str(obj)
5__cmp__ ( self, x ),对象比较,简单的调用方法 : cmp(obj, x)

运算符重载:在类中定义并实现一个与运算符对应的处理方法,这样当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理。

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print(v1 + v2) 


""" 输出结果: Vector (7, 8) """

6.多态

  • 多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象。本质在于不同的类中定义有相同的方法名, 这样我们就可以不考虑类而统一用一种方式去使用对象。但其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象, 这正是python崇尚的“鸭子类型”

    • 比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度
  • 多态性的好处在于增强了程序的灵活性和可扩展性

  • 可以通过关键字@abc.abstractmethod在父类引入抽象类的概念来硬性限制子类必须有某些方法名。

class Animal:           #同一类事物:动物
    def talk(self):
        pass
class Cat(Animal):      #动物的形态之一:猫
    def talk(self):
        print('喵喵喵')
class Dog(Animal):      #动物的形态之二:狗
    def talk(self):
        print('汪汪汪')
class Pig(Animal):      #动物的形态之三:猪
    def talk(self):
        print('哼哼哼')


#实例化得到三个对象
cat=Cat()
dog=Dog()
pig=Pig()

# 同类事物下的共同动态属性,定义统一接口
def Talk(animal):
    animal.talk()

Talk(cat)
# 喵喵喵
Talk(dog)
# 汪汪汪
Talk(pig)
# 哼哼哼

import abc

# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod             # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self):                 # 抽象方法中无需实现具体的功能
        pass

class Cat(Animal):                  # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):                 # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
                                    # TypeError: Can't instantiate abstract class Cat with abstract methods talk
        pass

cat=Cat()


Duck typing:“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。

  • 在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。 鸭子类型的好处就在于能够避免一些类的重写与继承,无需大量复制相同的代码。

  • 鸭子类型使用的前提是需要良好的文档支持,不然会让代码变得很混乱

"""
定义是的类型和运行时的类型不一样,就是多态的体现
Python崇尚鸭子类型
"""
class Cat(object):
    def say(self):
        print("I am Cat")


class Dog(object):
    def say(self):
        print("I am Dog")


class Duck(object):
    def say(self):
        print("I am Duck")


# 以上定义了三个类
animal_list = [Cat, Dog, Duck]    # 这里将三个封装好的类分别作为animal_list的三个元素

for animal in animal_list:    # animal_list是一个列表,是可迭代的对象
    animal().say()            # animal()是实例化对象的过程,然后分别调用 Cat, Dog, Duck的say方法


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Larissa857

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值