python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python面向对象编程

Python面向对象编程(二)

作者:springsnow

本文详细讲解了Python的面向对象编程,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

Python面向对象编程(一)

Python面向对象编程(二)

Python面向对象编程(三)

一、对象的继承

Python中支持一个类同时继承多个父类

class Parent1:
    pass


class Parent2:
    pass


class Sub1(Parent1, Parent2):
    pass

使用__bases__方法可以获取对象继承的类

print(Sub1.__bases__)
# (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

在Python3中如果一个类没有继承任何类,则默认继承object类。

print(Parent1.__bases__)
#('object'>,)

1、类的构造函数继承__init__():

2、继承关系中,对象查找属性的顺序

对象自己——>对象的类——>父类——>父类。。。

class OldboyPeople:
    """由于学生和老师都是人,因此人都有姓名、年龄、性别"""
    school = 'oldboy'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class OldboyStudent(OldboyPeople):
   def choose_course(self):
        print('%s is choosing course' % self.name)


class OldboyTeacher(OldboyPeople):
    def score(self, stu_obj, num):
        print('%s is scoring' % self.name)
        stu_obj.score = num


stu1 = OldboyStudent('tank', 18, 'male')
tea1 = OldboyTeacher('nick', 18, 'male')


print(stu1.school)
# oldboy

print(tea1.school)
# oldboy

print(stu1.__dict__)
# {'name': 'tank', 'age': 18, 'gender': 'male'}

tea1.score(stu1, 99)
# nick is scoring

print(stu1.__dict__)
# {'name': 'tank', 'age': 18, 'gender': 'male', 'score': 99}

二、类的派生

子类中新定义的属性的这个过程叫做派生,子类在使用派生的属性时始终以自己的为准。

1、派生方法一(类调用)

指名道姓访问某一个类的函数:该方式与继承无关

class OldboyPeople:
    """由于学生和老师都是人,因此人都有姓名、年龄、性别"""
    school = 'oldboy'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class OldboyStudent(OldboyPeople):
    """由于学生类没有独自的__init__()方法,因此不需要声明继承父类的__init__()方法,会自动继承"""

    def choose_course(self):
        print('%s is choosing course' % self.name)


class OldboyTeacher(OldboyPeople):
    """由于老师类有独自的__init__()方法,因此需要声明继承父类的__init__()"""

    def __init__(self, name, age, gender, level):
        OldboyPeople.__init__(self, name, age, gender)
        self.level = level  # 派生

    def score(self, stu_obj, num):
        print('%s is scoring' % self.name)
        stu_obj.score = num


stu1 = OldboyStudent('tank', 18, 'male')
tea1 = OldboyTeacher('nick', 18, 'male', 10)


print(stu1.__dict__)
# {'name': 'tank', 'age': 18, 'gender': 'male'}

print(tea1.__dict__)
# {'name': 'nick', 'age': 18, 'gender': 'male', 'level': 10}

2、派生方法二(super)

class OldboyPeople:
    school = 'oldboy'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

def choose_course(self):
        print('%s is choosing course' % self.name)
class OldboyStudent(OldboyPeople):
    def __init__(self, name, age, sex, stu_id):
        # OldboyPeople.__init__(self,name,age,sex)
        # super(OldboyStudent, self).__init__(name, age, sex)
        super().__init__(name, age, sex)
        self.stu_id = stu_id

    def choose_course(self):
        print('%s is choosing course' % self.name)


stu1 = OldboyStudent('tank', 19, 'male', 1)
super(OldboyStudent,stu1).choose_course() #用子类对象调用父类已被覆盖的方法
print(stu1.__dict__)
# {'name': 'tank', 'age': 19, 'sex': 'male', 'stu_id': 1}

三、类的组合

类对象可以引用/当做参数传入/当做返回值/当做容器元素,类似于函数对象。

组合可以理解成多个人去造一个机器人,有的人造头、有的人造脚、有的人造手、有的人造躯干,大家都完工后,造躯干的人把头、脚、手拼接到自己的躯干上,因此一个机器人便造出来了

class Course:
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price

class OldboyPeople:
    school = 'oldboy'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


class OldboyStudent(OldboyPeople):
    def __init__(self, name, age, sex, stu_id):
        OldboyPeople.__init__(self, name, age, sex)
        self.stu_id = stu_id

    def choose_course(self):
        print('%s is choosing course' % self.name)


class OldboyTeacher(OldboyPeople):
    def __init__(self, name, age, sex, level):
        OldboyPeople.__init__(self, name, age, sex)
        self.level = level

    def score(self, stu, num):
        stu.score = num
        print('老师[%s]为学生[%s]打分[%s]' % (self.name, stu.name, num))


# 创造课程
python = Course('python全栈开发', '5mons', 3000)
linux = Course('linux运维', '5mons', 800)


# 创造学生与老师
stu1 = OldboyStudent('tank', 19, 'male', 1)
tea1 = OldboyTeacher('nick', 18, 'male', 10)

# 组合
# 将学生、老师与课程对象关联/组合
stu1.course = python
tea1.course = linux

四、多父类继承问题

Python同样有限的支持多继承形式。多继承的类定义形如下例:

class DerivedClassName(Base1, Base2, Base3):
    
    .
    .
    .

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

1、新式类(MRO)列表

class G(object):
    # def test(self):
    #     print('from G')
    pass


class E(G):
    # def test(self):
    #     print('from E')
    pass


class B(E):
    # def test(self):
    #     print('from B')
    pass


class F(G):
    # def test(self):
    #     print('from F')
    pass


class C(F):
    # def test(self):
    #     print('from C')
    pass


class D(G):
    # def test(self):
    #     print('from D')
    pass


class A(B, C, D):
    def test(self):
        print('from A')


obj = A()
obj.test()  # A->B->E-C-F-D->G-object
# from A

python计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,来实现继承的。

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。

print(A.mro())  # A.__mro__
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]

for i in A.mro():
    print(i)

# <class '__main__.A'>
# <class '__main__.B'>
# <class '__main__.E'>
# <class '__main__.C'>
# <class '__main__.F'>
# <class '__main__.D'>
# <class '__main__.G'>
# <class 'object'>

2、super()方法详解

super() 函数是用于调用父类(超类)的一个方法。

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

下面的例子可以看到:

# 胖子老板的父类
class FatFather(object):
    def __init__(self, name, *args, **kwargs):
        print()
        print("=============== 开始调用 FatFather  ========================")
        print('FatFather的init开始被调用')
        self.name = name
        print('调用FatFather类的name是%s' % self.name)
        print('FatFather的init调用结束')
        print()
        print("=============== 结束调用 FatFather  ========================")


# 胖子老板类 继承 FatFather 类
class FatBoss(FatFather):
    def __init__(self, name, hobby, *args, **kwargs):
        print()
        print("=============== 开始调用 FatBoss  ========================")
        print('胖子老板的类被调用啦!')
        # super().__init__(name)
        # 因为多继承传递的参数不一致,所以使用不定参数
        super().__init__(name, *args, **kwargs)
        print("%s 的爱好是 %s" % (name, hobby))
        print()
        print("=============== 结束调用 FatBoss  ========================")


# 胖子老板的老婆类 继承 FatFather类
class FatBossWife(FatFather):
    def __init__(self, name, housework, *args, **kwargs):
        print()
        print("=============== 开始调用 FatBossWife  ========================")
        print('胖子老板的老婆类被调用啦!要学会干家务')
        # super().__init__(name)
        # 因为多继承传递的参数不一致,所以使用不定参数
        super().__init__(name, *args, **kwargs)
        print("%s 需要干的家务是 %s" % (name, housework))
        print()
        print("=============== 结束调用 FatBossWife  ========================")


# 胖子老板的女儿类 继承 FatBoss FatBossWife类
class FatBossGril(FatBoss, FatBossWife):
    def __init__(self, name, a, b):
print('胖子老板的女儿类被调用啦!要学会干家务,还要会帮胖子老板斗地主')
        super().__init__(name, a, b)


def main():
    print("打印FatBossGril类的MRO")
    print(FatBossGril.__mro__)
    # (<class '__main__.FatBossGril'>, <class '__main__.FatBoss'>, <class '__main__.FatBossWife'>, <class '__main__.FatFather'>, <class 'object'>)
    print("=========== 下面按照 MRO 顺序执行super方法 =============")
    gril = FatBossGril("胖子老板", "打斗地主", "拖地")


if __name__ == "__main__":
    main()

# =========== 下面按照 MRO 顺序执行super方法 =============
# 胖子老板的女儿类被调用啦!要学会干家务,还要会帮胖子老板斗地主
# 
# =============== 开始调用 FatBoss  ========================
# 胖子老板的类被调用啦!
#
# =============== 开始调用 FatBossWife  ========================
# 胖子老板的老婆类被调用啦!要学会干家务
#
# =============== 开始调用 FatFather  ========================
# FatFather的init开始被调用
# 调用FatFather类的name是胖子老板
# FatFather的init调用结束
#
# =============== 结束调用 FatFather  ========================
# 胖子老板 需要干的家务是 拖地
#
# =============== 结束调用 FatBossWife  ========================
# 胖子老板 的爱好是 打斗地主
#
# =============== 结束调用 FatBoss  ========================

五、抽象类

多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

import abc


class Animal(metaclass=abc.ABCMeta):  # 同一类事物:动物
    @abc.abstractmethod  # 上述代码子类是约定俗称的实现这个方法,加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法
    def talk(self):
        raise AttributeError('子类必须实现这个方法')


class People(Animal):  # 动物的形态之一:人
    def talk(self):
        print('say hello')


class Dog(Animal):  # 动物的形态之二:狗
    def talk(self):
        print('say wangwang')


class Pig(Animal):  # 动物的形态之三:猪
    def talk(self):
        print('say aoao')


peo2 = People()
pig2 = Pig()
d2 = Dog()

peo2.talk()
pig2.talk()
d2.talk()

# say hello
# say aoao
# say wangwang

六、类的封装

类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

1、私有属性:双下划线的方式__x

在python中用双下划线的方式__x实现隐藏属性(设置成私有的),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。

class Site:
    def __init__(self, name, url):
        self.name = name       # public
        self.__url = url   # private
 
    def who(self):
        print('name  : ', self.name)
        print('url : ', self.__url)
 
    def __foo(self):          # 私有方法
        print('这是私有方法')
 
    def foo(self):            # 公共方法
        print('这是公共方法')
        self.__foo()
 
x = Site('菜鸟教程', 'www.runoob.com')
x.who()        # 正常输出
x.foo()        # 正常输出
x.__foo()      # 报错

2、外部使用变形访问:_类名__x

类中所有双下划线开头的名称如__x都会自动变形成: _类名__x的形式:

这种自动变形的特点:

这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。

class A:
    __N = 0  # 把类的数据属性设置成私有的如__N,会变形为_A__N

    def __init__(self):
        self.__X = 10  # 变形为self._A__X

    def __foo(self):  # 变形为_A__foo
        print('from A')

    def bar(self):
        self.__foo()  # 只有在类内部才可以通过__foo的形式访问到.


# 对象测试
a = A()
print(a._A__N)  # 0
print(a._A__X)  # 10


# 类测试
print(A._A__N)  # 0
print(A._A__X)  # 对象私有的属性# type object 'A' has no attribute '_A__X'

注意:变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形。

a = A()
print(a.__dict__)  # {'_A__X': 10}

a.__Y = 1
print(a.__dict__)  # {'_A__X': 10, '__Y': 1}

3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

# 正常情况
class A:
    def fa(self):
        print('from A')

    def test(self):
        self.fa()


class B(A):
    def fa(self):
        print('from B')


b = B()
b.test()  # from B


# 把fa定义成私有的,即__fa
class A:
    def __fa(self):  # 在定义时就变形为_A__fa
        print('from A')

    def test(self):
        self.__fa()  # 只会与自己所在的类为准,即调用_A__fa


class B(A):
    def __fa(self): #子类无法覆盖
        print('from B')


b = B()
b.test()  # from A

python模块也遵循这种约定,如果模块中的变量名_amodule以单下划线开头,那么from module import *时不能被导入该变量,但是你from module import_amodule依然是可以导入该变量的。

如果遇到下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部也是可以用的。严格控制属性的访问权限,只能借助内置方法如__getattr__。

七、类的属性(property)

1、装饰器方式 (推荐使用)

property装饰器用于将被装饰的方法伪装成一个数据属性,在使用时可以不用加括号而直接使用。

property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。

分页的功能包括:

class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val


# ############### 调用 ###############
p = Pager(2)
print(p.start)  # 就是起始值,即:m
# 10

print(p.end)  # 就是结束值,即:n
# 20

2、经典类和新式类的属性方式:

class Goods(object):
    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):
        print('del')
        del self.original_price


obj = Goods()
print(obj.price)  # 获取商品价格
# 80.0

obj.price = 200  # 修改商品原价
print(obj.price)
# 160.0

del obj.price  # 删除商品原价
# del

3、类属性方式

注意:当使用类属性的方式创建property属性时,经典类和新式类无区别。

property方法中有个四个参数

class Goods(object):
    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    def get_price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    PRICE = property(get_price, set_price, del_price, '价格属性描述...')


obj = Goods()
print(obj.PRICE)  # 获取商品价格
# 80.0

obj.PRICE = 200  # 修改商品原价
print(obj.PRICE)
# 160.0

del obj.PRICE  # 删除商品原价

3、实例

实现一个属性的设置和读取方法,可做边界判定

class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")


a = Money()
a.money = 100
print(a.money)# 100

八、实例方法及非绑定实例方法

1、实例方法

在类中没有被任何装饰器修饰的方法就是绑定到对象的实例方法,这类方法专门为对象定制。

class Person:
    country = "China"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def speak(self):
        print(self.name + ', ' + str(self.age))


p = Person('Kitty', 18)
print(p.__dict__)
# {'name': 'Kitty', 'age': 18}

print(Person.__dict__['speak'])
#

speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

绑定到对象的方法:

p = Person('Kitty', 18)
p.speak()  # 通过对象调用
# Kitty, 18

Person.speak(p)  # 通过类调用
# Kitty, 18

2、类方法(@classmethod )

类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。

class Operate_database():
    host = '192.168.0.5'
    port = '3306'
    user = 'abc'
    password = '123456'

    @classmethod
    def connect(cls):  # 约定俗成第一个参数名为cls,也可以定义为其他参数名
        print(cls)
        print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)


Operate_database.connect()
# <class '__main__.Operate_database'>
# 192.168.0.5:3306 abc/123456


Operate_database().connect()  # 输出结果一致
# <class '__main__.Operate_database'>
# 192.168.0.5:3306 abc/123456

3、静态方法(@staticmethod )

在类内部使用 @staticmethod 修饰的方法即为非绑定的静态方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

import hashlib


class Operate_database():
    def __init__(self, host, port, user, password):
        self.host = host
        self.port = port
        self.user = user
        self.password = password

    @staticmethod
    def get_passwrod(salt, password):
        m = hashlib.md5(salt.encode('utf-8'))  # 加盐处理
        m.update(password.encode('utf-8'))
        return m.hexdigest()


hash_password = Operate_database.get_passwrod('lala', '123456')  # 通过类来调用
print(hash_password)
# f7a1cc409ed6f51058c2b4a94a7e1956

p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
hash_password = p.get_passwrod(p.user, p.password)  # 也可以通过对象调用
print(hash_password)
# 0659c7992e268962384eb17fafe88364

九、类的专有方法:

运算符重载

对类的专有方法进行重载实例如下:

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)

到此这篇关于Python面向对象编程的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文