python之面向对象

一、名称空间

名称空间有内置名称空间,全局名称空间,局部名称空间。它是用来存放名字与值对应关系的地方。

test.py文件:

num = 10

def fn():
    print("fn run")
def func():
    a = 10
    print(locals())  # 以字典类型返回当前位置的全部局部变量{'a': 10}
func()
print(func.__dict__)  # {}

a = 10
# print(a.__dict__)  # 'int' object has no attribute '__dict__'

ls = [1, 2]
# print(ls.__dict__)  # 'list' object has no attribute '__dict__'

能产生名称空间的有:文件 | 函数 | 类

能产生名称空间的对象有__dict__这个值,通过该值可以访问到名称空间。

print(globals())  # globals()返回的是全局变量的字典

# 名称空间的使用
import test

# 两种调用方式本质是一样的
print(test.num)  # 10
print(test.__dict__['num'])  # 10

test.__dict__['fn']()  # fn run
test.fn()  # fn run


def fun1():
    pass


fun1.__dict__['index'] = 10000
print(fun1.__dict__)  # {'index': 10000}
print(fun1.__dict__['index'])  # 10000
fun1.add = lambda n1, n2: n1 + n2
print(fun1.__dict__)  # {'index': 10000, 'add': <function <lambda> at 0x000000D8066F9B70>}
print(fun1.add(10, 20))  # 30

二、面向对象

面向对象的核心体现是将数据和处理数据的程序封装到对象中。即以对象的形式组织代码,封装数据。

2.1、面向过程

1、面向过程编程
    核心是"过程"二字,过程指的是解决问题的步骤,即先干什么再干什么
    基于该思想编写程序就好比在编写一条流水线,是一种机械式的思维方式

    优点:复杂的问题流程化、进而简单化
    缺点:可扩展性差

2.2、面向对象

2、面向对象
    核心"对象"二字,对象指的是特征与技能的结合体,
    基于该思想编写程序就好比在创造一个世界,你就是这个世界的上帝,是一种
    上帝式的思维方式

    优点:可扩展性强
    缺点:编程的复杂度高于面向过程

函数与方法:都是解决问题的功能
函数:通过函数名直接调用
方法:通过附属者‘.’语法来调用

面向对象的三大特性:

封装:整合数据,数据保护
继承:子类继承父类的属性、方法
多态:继承完后对方法的重写

2.3、类和对象

类 class:对具有相同属性的对象的抽象,定义了对象的属性、方法;类其实就是一个容器(名称空间)。类体代码会在类定义阶段立即执行,并产生一个名称空间,用来将类体代码执行过程中产生的名字都丢进去。

对象 :对象是特征与技能的集合体,是类的实例化

类的声明语法:

class  类名:    # class是定义类的关键字

    pass

对象的产生:类的实例化

对象1 = 类名()
对象2 = 类名()
对象3 = 类名()

程序中类的用法:

1、当作名称空间从其内部取出名字来使用

2、调用类来产生对象

.:专门用来访问属性,本质操作的就是__dict__
OldboyStudent.school #等于经典类的操作OldboyStudent.__dict__['school']
OldboyStudent.school='Oldboy' #等于经典类的操作OldboyStudent.__dict__['school']='Oldboy'
OldboyStudent.x=1 #等于经典类的操作OldboyStudent.__dict__['x']=1
del OldboyStudent.x #等于经典类的操作OldboyStudent.__dict__.pop('x')
注意:
  1.类中可以有任意python代码,这些代码在类定义阶段便会执行
  2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看
  3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法
  4.点是访问属性的语法,类中定义的名字,都是类的属性
小结:
类拥有自己的名称空间,类的对象也拥有自己的名称空间,
所以可以通过.语法添加或使用属性和方法

定义类能不能初始设置属性与方法?
类一旦被加载(随着所属文件的加载就加载),就会进入类的内部执行类中的所有代码
python为类内置的特殊属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)


class People:
    identify = '人类'
    def sleep(self):
        print('睡觉')
    print(456)
# print(People.__dict__)
p1 = People()
p2 = People()

p1,p2虽然能访问identify,但identify属于People类,只有一份
print(p1.identify)
print(p2.identify)

p1修改的并不是People的identify,相当于给自己添加一个identify属性
p1.identify = '新人类'
print(p1.__dict__, p1.identify)
p1.__dict__.clear()
print(p1.identify)
p2自己没有,还是访问的类的
print(p2.identify)

属性的访问顺序:优先加载自身的名字,如果没有再考虑类的

面向对象小案例:

class Dog:
    def __init__(self,name):
        self.name = name
    def bulk(self):
        print("%s wang wang wang!!")

d1 = Dog('peter')  # 调用类产生一个对象d1,然后返回,对象会产生一个空的名称空间;触发类中__init__的执行,第一个参数默认传的就是d1.
d2 = Dog('Bob')
d3 = Dog('Henry')
d1.bulk()
d2.bulk()
d3.bulk()

2.4、对象特有的名称空间

class Student:
    identify = '学生'
# Student.identify = '新学生'  # 类的属性只有自己才能更改

stu1 = Student()   # 实例化
print(stu1.identify)  # 学生,这里自己没有,就访问类里的

stu1.identify = '学生1'  # 可以给自己添加属性,不用类的
print(stu1.identify)  # 学生1
print(stu1.__dict__)  # 相当于给自己的名称空间添加了属性,{'identify': '学生1'}
# 类实例化后,对象的其他属性可按照如下方式添加
stu1.name = 'Bob'
stu1.sex = 'male'

stu2 = Student()
stu2.identify = '学生2'
stu2.name = 'Tom'
stu2.sex = 'female'
print(stu2.__dict__)  # {'identify': '学生2', 'name': 'Tom', 'sex': 'female'}

这样添加属性显得比较冗余,而且有很多属性,因此官方为我们提供了一个初始化的方法__init__

__init__方法会在实例化对象时被调用
1、会为实例化的对象形成空的名称空间
2、就是一个方法,可以被传参,在 类名(实参) 这种方式下调用并传参 __init__(self, 形参)
3、第一个self就是要产生的当前对象
重点:在方法内部,形参拿到了实参值,利用 self.属性名=形参,对对象的名称空间添加属性
class Student:
    # def __init__(self,name,sex):
    #     print('2>>>',self)
    #     self.name = name
    #     self.sex = sex

    def fn():
        print("fn run !")
    def set_stu(stu,name,sex):
        stu.name = name
        stu.sex = sex
# print(Student.__dict__)  # 存了类里函数的名字与地址
# Student.fn()  # 虽然没有self,但是通过类可以直接调用
stu1 = Student()
print(stu1.__dict__)  # {},set_stu并不存在stu1的名称空间,由类来调用
Student.set_stu(stu1, 'Bob', 'male')
print(stu1.__dict__)  # {'name': 'Bob', 'sex': 'male'}
print(stu1.name)
print(stu1.sex)
stu2 = Student()
Student.set_stu(stu2, 'Tom', 'female')
print(stu2.name, stu2.sex)

# 如果将产生空名称空间与对名称空间赋值操作整合
class Student:
    def __init__(self,name,sex):
        print('2>>>',self)  # 2>>> <__main__.Student object at 0x000000FA78971748>
        self.name = name
        self.sex = sex

    def fn():
        print("fn run !")
    def set_stu(stu,name,sex):
        stu.name = name
        stu.sex = sex

stu3 = Student('Bob','male')
print('1>>',stu3)  # 1>> <__main__.Student object at 0x000000FA78971748>
print(stu3.name)
print(stu3.sex)
stu3.name = 'bob'
print(stu3.name)
stu4 = Student('Tom', 'female')
print('4>>',stu4)  # __init__中的self就是实例化的对象,内存地址一致
print(stu4.name)
print(stu4.sex)

2.5、类中方法的第一个默认参数

实例化与传值

class Student:
    def __init__(self, name):
        self.name = name

    def study(self):
        print(self.name + ' study')

stu = Student('Bob')
stu.study()
stu1 = Student('Tom')
stu1.study()

对象调用类的方法:

class Student:
    def test(self):
        print(self)
        pass
stu = Student()
# 二级优化
stu.test()  # <__main__.Student object at 0x00000008CDE88898>
# 一级优化
Student.test(stu)  # <__main__.Student object at 0x00000008CDE88898>
# 实现原理
Student.__dict__['test'](stu)  # <__main__.Student object at 0x00000008CDE88898>

类中定义的变量是类的数据属性,类可以用,对象也可以用,大家都指向同一个内存地址,类变量值一旦改变,所有对象中的也都改变。

类中定义的函数是类的函数属性,类可以用,但类来调用就是一个普通的函数,且是绑定给对象用的。

重点:方法的第一个参数一定是调用该方法的对象

2.6、类方法

重点:

1.类的属性如何定义,类的方法如何定义
2.对象的属性如何定义,对象的方法如何定义
3.什么时候定义类方法与对象方法
4.封装的语法与原理
5.封装的目的
6.对象属性的封装对外提供操作接口的手段
class Tool:
    # 类自己的方法
    def add(cls, n1, n2):
        return n1+n2
    # a如果被外界对象tool调用,那么内部调用b,b其实也是被外界对象tool调用的
    def a(self):
        self.b()
    def b(self):
        pass

res = Tool.add(Tool,10,20)  # 类调用,外界传入两个参数,内部接收到两个
print(res)

# 问题:类的名字,对象都可以使用,但是出现了类与对象使用时,参数个数不一致
tool = Tool()
print(tool.add(10,20)) # 对象调用,外界传入两个参数,内部接收到三个,第一个是对象本身
class Toool:
    # 类方法:可以被类与对象调用的方法,第一个参数一定是类
    # 类方法不建议拿对象来调用
    @classmethod
    def add(cls,n1,n2):
        print(id(cls))
        cls.test()
        return n1+n2
    @classmethod
    def test(cls):
        pass
# 类调用
print(Toool.add(10,20))

# 对象调用
toool = Toool()
print(toool.add(100,200))
print(id(Toool),id(toool))

# 对象调用所属类的类方法,默认第一个参数传入的是 对象.__class__  就是所属类
print(toool.__class__)  # <class '__main__.Toool'>

2.7、属性与方法的总结

class OldBoy:
    # 类的属性
    name = '老男孩'
    # 对象的属性
    def __init__(self, name):
        self.name = name
    # 属于类的方法
    # 需求:获取公司的名字
    @classmethod
    def get_company_name(cls):
        return cls.name
    # 属于对象的方法
    # 需求:获取地点的名字
    def get_place_name(self):
        return self.name

# 先创建地点
G1 = OldBoy('上海')
G2 = OldBoy('北京')

# 类方法的使用
# 建议使用类调用
print(OldBoy.get_company_name())

# 类方法拿对象调用并没有多大的意义,不建议拿对象调用
print(G1.get_place_name())
print(G2.get_place_name())

# 对象方法的使用
# 类调用对象方法,必须把要操作的对象手动传入,不建议使用
print(OldBoy.get_place_name(G1))
print(OldBoy.get_place_name(G2))

# 对象调用对象方法,默认将自身传入,建议使用
print(G1.get_company_name())
print(G2.get_company_name())

 2.8、封装

将类中的一些功能与属性,进行隐藏,不让外界直接访问(但可以间接访问)

优点:外界不能直接访问,让内部的属性与方法具有安全保障

以下代码中,类的所有属性与方法都能被外界访问,不安全
class A:
    x = 20
    y = 10
    def f1(self):
        print("f1 run")
    def f2(self):
        print("f2 run")
    # 找到保险箱
    def get_box(self):
        print("找到保险箱")
        self.get_money()
    # 保险箱取钱操作
    def get_money(self):
        print('输入密码,取出100w零花钱')

a = A()
print(a.x)
print(a.y)
a.f1()
a.f2()
a.get_box()
# a.get_money()

 以下代码是封装后的:

class A:
    # __开头的属性,在外界不能通过 cord|__cord 直接访问:对外隐藏了
    __cord = '01010101'
    # __开头的方法,在外界不能通过 get_money|__get_money 直接访问:对外隐藏了
    @classmethod
    def __get_money(cls):
        print('输入密码,取出100w零花钱')

    @classmethod
    def test(cls, flag):
        print('test方法被外界调用')
        # 在调用test与访问具体数据与功能间添加安全处理的操作
        if flag == '自家人':
            print(cls.__cord)
            cls.__get_money()

# print(A.__cord)  # type object 'A' has no attribute '__cord'
# A.__get_money()  # type object 'A' has no attribute '__get_money'
A.test('自家人')

封装的原理:把用__开头的名字更名为  (_类名__变量名 ) ,所以直接用 (变量名|__变量名) 就访问不到。

如果想访问,可以通过如下访问,但是违背了我们封装的初衷

print(A._A__cord)  # 01010101
A._A__get_money()  # 输入密码,取出100w零花钱

2.9、对象的属性方法封装与接口提供

对象的属性与方法封装机制与类的属性与方法封装原理一样

class AAA:
    def __init__(self, money):
        self.__money = money
        self.__id = 1000

    @property  # 让外界访问时像访问普通字段一样
    def id(self):
        return self.__id
    @id.setter
    def id(self, id):
        self.__id = id
    # 对象的属性封装
    # 1、对象的属性值一般都来自外界,外界是有权再次访问的
    # 2、封装的目的不是让外界无法访问,而是不让其直接访问,可以在完成安全处理后再访问
    # 3.如何做到外界还是通过变量名来对属性进行取值赋值,但是是走的方法间接拿到的值
    #       -- __money被封装,外界还是可以通过 对象.money 取值赋值

    # 取值
    @property  # 在外界可以 对象.money 进行取值
    def money(self):
        print('走方法拿到的money')
        return self.__money

    # 赋值
    @money.setter  # 在外界可以 对象.money = 新值 进行赋值
    def money(self,money):
        self.__money = money

    # 删除
    # @money.deleter
    # def money(self):
    #     del self.__money

    def get_money(self, flag):
        if flag == '自家人':
            return self.__money
        return 0

    def set_money(self, money):
        self.__money += money

    # 对象的方法封装一般的实现需求都是,这些方法只在内部使用
    def __test(self):
        print('test run')

a = AAA(88888)
print(a.get_money('自家人'))  # 88888
a.set_money(1000000)
print(a.get_money('自家人'))  # 1088888

# get_money和set_money可以用装饰器定义为同一个方法
print(a.money)  # 1088888
a.money = 999999
print(a.money)  # 999999

2.10、通过一个简单的植物大战僵尸的小游戏来消化一下

# -*- coding: utf-8 -*-
# @Author  : Sunhaojie
# @Time    : 2019/4/18 18:45
'''
用面向对象实现 植物大战僵尸游戏

1.定义一个僵尸Zombie类,该类可以实例化出多种僵尸对象,僵尸对象产生默认都有 名字name、血量HP、防具armor
	-- 名字:普通僵尸 | 路障僵尸 | 铁桶僵尸
	-- 血量:默认就是100,不需要外界提供
	-- 防具:不需要外界提供,从名字中分析确定,防具的值是一个列表,从名字分析得到
			-- ['无', 0] | ['路障', 5] | ['铁桶', 15]  => [防具名, 防具的防御值]
	-- 通过@property的getter、setter方式,对外提供防具的两个访问接口armor_name与armor_count
			-- armor_name可以取值、赋值、删除值:通过一个
				-- eg: 普通僵尸对象.armor_name = '铁桶',不仅改变了防具名
				--     普通僵尸对象的名字name也会变成 铁桶僵尸
			-- armor_count只可以取值

2.定义一个角色User类,该类有名字name属性、以及打僵尸的beat方法
	-- 名字:随意自定义
	-- beat:该方法需要传入一个僵尸对象
			-- 在方法内部可以实现:某某用户攻击了某某个僵尸,僵尸损失多少血,还剩多少血
			-- 每一次攻击,都固定扣除25滴血,但是不同的僵尸会被防具相应抵消掉一定的伤害值
			-- 循环攻击僵尸,3s攻击一次,僵尸被击杀后,打印 某某用户击杀了某某个僵尸 并结束方法

3.定义一个Game类,该类有一个name属性,属性值为 "植物大战僵尸" ,该类中有一个start方法,通过Game.start()来启动游戏
	-- 游戏一开始先显示游戏的名字 植物大战僵尸游戏
	-- 会随机产生三种僵尸,总共产生三个作为要被击杀的对象
	-- 生成一个有角色名的角色,依次去击杀随机产生的每一只僵尸
		-- 开始击杀第一只僵尸 => 某某用户攻击了某某个僵尸,僵尸损失多少血,还剩多少血 => 某某用户击杀了某某个僵尸 => 第一只僵尸已被击杀完毕 => 开始击杀第二只僵尸 ... => 第三只僵尸已被击杀完毕
'''

import random
import time


class Zombie:
    '''
    名字name、血量HP、防具armor
    '''
    # 匹配的字典
    dic = {'普通僵尸': ['无', 0], '路障僵尸': ['路障', 5], '铁桶僵尸': ['铁桶', 15]}

    def __init__(self, name, HP=100):
        # 传入的僵尸类型
        self.name = name
        # 血量,初始100
        self.HP = HP
    #查僵尸的防具
    @property
    def armor_name(self):
        return self.dic[self.name][0]
    # 设置僵尸类型
    @armor_name.setter
    def armor_name(self, name1):
        if name1 == '无':
            self.name = '普通僵尸'
        else:
            self.name = name1 + '僵尸'
    # 删除僵尸
    @armor_name.deleter
    def armor_name(self):
        del self.name

    # 取护具的防护值
    @property
    def armor_count(self):
        return self.dic[self.name][1]


class User:
    def __init__(self, name):
        self.name = name

    # 打僵尸
    def beat(self, z):
        while True:
            # 每次减25点血
            z.HP -= 25
            # 把防具的防护值加回去
            z.HP += z.armor_count
            if z.HP <= 0:
                print('用户[%s]击杀了[%s]' % (self.name, z.name))
                break
            print("用户[%s]攻击了[%s],僵尸损失[%s]血量,还剩[%s]血量" % (self.name, z.name, 25 - z.armor_count, z.HP))
            time.sleep(3)


class Game:
    name = "植物大战僵尸"

    @classmethod
    def start(self):
        print(Game.name + '游戏')
        zombies = []
        for i in range(3):
            arm = random.choice(['普通僵尸', '路障僵尸', '铁桶僵尸'])
            zombies.append(arm)
        u = User('||<<豌豆射手>>||')
        count = 1
        for n in zombies:
            print("开始击杀第%s只僵尸" % count)
            # 实例化僵尸类
            z = Zombie(n)
            # 调用击杀的方法
            u.beat(z)
            print("第[%s]只僵尸已被击杀完毕" % count)
            count += 1


Game.start()

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值