2、面向对象

声明

python2需要在类名后括号内写明父类,python3如果不写后面的括号则默认继承object

pass是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思

#class 类名(父类,没有就是object):
class Student(object):
    """
    类注释
    Attributes:
      attr1: 属性1
      attr2: 属性2
      attr3: 属性3
    """
    pass

实例化

class Student(object):
    pass

#实例变量名 = 类名()
stu = Student()
print(stu)

>>>>
#打印可以看到,实例属于Student
<__main__.Student object at 0x000001538F0097F0>

属性

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法(构造方法),在创建实例的时候,就把属性绑上去

__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身

class Student(object):
    #构造方法
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

stu = Student('lucy',18,'女')

print(stu.name)
print(stu.age)
print(stu.sex)

私有属性

要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过stu._Student__name来访问__name变量,但是不要这么做

class Student(object):
    #构造方法
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

stu = Student('lucy',18,'女')

方法

要定义一个方法,除了第一个参数必须是self外,其他和普通函数一样

class Student(object):
    #默认值私有属性
    __sex = '男'
    #构造方法
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def showMsg(self):
        "展示学生信息"
        print("姓名:",self.__name,"年龄:",self.__age,"性别:",self.__sex)

stu = Student('lucy',18,'女')
stu.showMsg()

私有方法

私有方法和私有属性一样,在方法名前加两个下划线__,方法就变成了私有的

私有方法同样可以通过stu._Student__sayHello()进行访问,但是不要这么做

class Student(object):
    #构造方法
    def __init__(self,name,age,sex):
        self.__name = name
        self.__age = age
        self.__sex = sex

    def showMsg(self):
        "展示学生信息"
        print("姓名:",self.__name,"年龄:",self.__age,"性别:",self.__sex)
    
    def __sayHello(self):
        "私有方法"
        print('Hello')

stu = Student('lucy',18,'女')
stu.showMsg()

类的内置方法

除了前面我们使用的__init__外,Python还提供了很多的类的内置方法,这些内置方法我们称之为:魔术方法

我们可以通过__str__方法,控制类转换为字符串的行为。

class Student:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def __str__(self):
        return f'我叫{self.name},今年{self.age}岁'
    

stu = Student("Lucy",18)
print(stu) # 我叫Lucy,今年18岁

小于符号比较方法

直接对2个对象进行比较是不可以的,但是在类中实现__lt__方法,即可同时完成:小于符号 和 大于符号 2种比较

比较大于符号的魔术方法是:__gt__,不过,实现了__lt____gt__就没必要实现了

class Student:
    def __init__(self,name,age):
        self.name = name
        self.age = age 
    def __lt__(self,other):
        return self.age < other.age
    

stu1 = Student("Lucy",18)
stu2 = Student("Tom",20)
print(stu1 < stu2) # True
        

小于等于的比较符号。同上。

>=符号实现的魔术方法是:__ge__。不过,实现了__le____ge__就没必要实现了

等于的比较符号。不实现__eq__方法,对象之间可以比较,但是是比较内存地址,也即是:不同对象==比较一定是False结果。

静态

静态属性

在类中直接定义的属性,就是静态属性,这个属性属于类,被该类所有实例共有

需要注意:

当我们根据使用实例对象修改静态属性时,该实例对象的类属性会改变,但只会作用于自身(修改的实例对象),不会影响其他实例的属性值。

当我们通过直接修改静态属性时,静态属性会发生改变,并且生效作用于其他的实例对象,其他的实例对象访问结果会变成类修改静态属性后的结果,而实例对象修改过后的静态属性却没有受到影响,它的静态属性的值是它(实例对象)修改过后的值。

class MyClass:
    class_attr = "I am a class attribute"

    def __init__(self, ins_attr):
        self.ins_attr = ins_attr


if __name__ == '__main__':
    obj1 = MyClass("I am an instance attribute of obj1")
    obj2 = MyClass("I am an instance attribute of obj2")

    print(obj1.class_attr)  # 输出 "I am a class attribute"
    print(obj2.class_attr)  # 输出 "I am a class attribute"

    print(obj1.ins_attr)  # 输出 "I am an instance attribute of obj1"
    print(obj2.ins_attr)  # 输出 "I am an instance attribute of obj2"

    obj1.class_attr = "I am a new update class attribute of obj1"
    print(obj1.class_attr)  # 输出 "I am a new update class attribute of obj1"
    print(obj2.class_attr)  # 输出 "I am a class attribute"

    MyClass.class_attr = "I am a new MyClass attribute"
    print(obj1.class_attr)  # 输出 "I am a new update class attribute of obj1"
    print(obj2.class_attr)  # 输出 "I am a new MyClass attribute"
    print(MyClass.class_attr)  # 输出 "I am a new MyClass attribute"

静态方法

python中,可以使用装饰器@classmethod@staticmethod

如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名类名.方法名

@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。

class Student:
    @staticmethod
    def good():
        print('good')

    @classmethod
    def hello(cls):
        cls.good()
        print('hello')
    
    @staticmethod
    def bye():
        Student.good()
        print('bye')

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)

可以使用isinstance(obj,class)来查看对象obj是否属于类class

class Animal(object):
	pass
#狗、猫类继承于动物类
class Dog(Animal):
    pass
class Cat(Animal):
    pass
#实例化
d = Dog()
c = Cat()

print(isinstance(d,Animal))
print(isinstance(c,Animal))

属性的继承

子类会继承父类中已经声明的属性,并且可以使用默认值

class Animal(object):
    name = '动物'

#狗类继承于动物类
class Dog(Animal):
    pass

#实例化
d = Dog()

#调用父类声明的属性
print(d.name)

方法的继承

子类会继承父类的所有方法,包括构造方法__init__()

class Animal(object):
    def __init__(self,name):
        self.__name = name
    def run(self):
        "动物都有的行为"
        print('%s在跑' % self.__name)

#狗、猫类继承于动物类
class Dog(Animal):
    pass
class Cat(Animal):
    pass
#实例化,继承父类的构造方法
d = Dog("狗")
c = Cat("猫")
#继承父类的方法
d.run()
c.run()

方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法

class Animal(object):
    def run(self):
        print("动物在跑")

class Dog(Animal):
    #重写父类方法run()
    def run(self):
        print("狗子在跑")

dog = Dog()
dog.run()

super()函数

super() 函数是用于调用父类(超类)的一个方法,也就是使用子类对象,调用父类已被重写的方法

super() 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

MRO就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。

class Animal(object):
    def run(self):
        print("动物在跑")

class Dog(Animal):
    # 重写父类方法run()
    def run(self):
        # 调用父类方法run()
        super().run()
        print("狗子在跑")

d = Dog()
d.run()
# 动物在跑
# 狗子在跑

# 调用实例的父类方法
super(Dog,d).run() # 动物在跑

多继承

python支持多继承,即可以继承于多个父类

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索即方法在子类中未找到时,从左到右查找父类中是否包含方法

class Animal(object):
    def run(self):
        "动物都有的行为"
        print('动物可以跑')

class Pet(object):
    def play(self):
        "宠物都有的行为"
        print('宠物可以和主人玩')

class Cat(Animal,Pet):
    pass

cat = Cat()

cat.play()
cat.run()

多态

python不支持多态也用不到多态,多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚鸭子类型(Duck Typing)

鸭子类型:是一种动态类型的风格。一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

也就是说,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误

class Duck(object):
    def go(self):
        print('鸭子走')
    def run(self):
        print('鸭子跑')
class Dog(object):
    def go(self):
        print('狗走')
    def run(self):
        print('狗跑')
#python的多态,不在于是哪个类,而是只要该对象有这两个方法就可以
def handler(duck):
    duck.go()
    duck.run()

duck = Duck()
dog = Dog()

handler(duck)
handler(dog)

获取对象信息

type()可以获取对象的类型,返回一个Class类型的变量,可以使用==判断两个对象是否同一类型

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

print(type(person)) # <class '__main__.Person'>

isinstance(obj,class)来查看对象obj是否属于类class

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

print(isinstance(person,Person)) # Ture

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

print(dir(person))

'''
['_Person__age', '_Person__name', '__class__', '__delattr__',
 '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
  '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
   '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__',
    '__new__', '__reduce__', '__reduce_ex__', '__repr__',
     '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
      '__weakref__', 'showMsg']
'''

仅仅把属性和方法列出来是不够的,配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态,类似java的反射机制

获取对象某个属性的值

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)
#获取person对象_Person__name属性的值
print(getattr(person,'_Person__name'))
#获取person对象_Person__name属性的值,如果不存在,返回404
print(getattr(person,'_Person__name',404))

设置对象某个属性的值

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

#设置person对象,_Person__name属性值为tom
setattr(person,'_Person__name','tom')

person.showMsg()

判断对象是否含有某个属性

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

#判断对象person中是否含有属性_Person__name
print(hasattr(person,'_Person__name'))

此外,这三个函数也可以作用于对象的方法

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def showMsg(self):
        print('姓名',self.__name,'年龄',self.__age)

person = Person('lucy',18)

#判断对象person中是否含有方法showMsg
print(hasattr(person,'showMsg'))

#获取对象person的方法showMsg
show = getattr(person,'showMsg')

#调用方法
show() # 姓名 lucy 年龄 18

类型注解

Python在3.5版本的时候引入了类型注解,以方便静态类型检查工具,IDE等第三方工具。

类型注解:在代码中涉及数据交互的地方,提供数据类型的注解(显式的说明)。

支持:

# 变量的类型注解
a: int = 10
b: float = 2.5
c: str = 'hello'
d: bool = True
e: dict[str,int] = {'a':1,'b':2}
f: list[int] = [1,2,3,4]
g: set[int] = {1,2,3,4,5}
h: tuple[int] = (1,2,3,4,5)

# 函数的类型注解
def test(a: int,b: str) -> str:
    pass

Union类型

使用Union[类型, ......, 类型]可以定义联合类型注解,可以理解为,里面的类型都是可以的

from typing import Union

a: list[Union[int,str]] = [1,2,'3','4']