Python语言系列-08-面向对象3





反射

#!/usr/bin/env python3
# author: Alnk(李成果)
# 反射
# hasattr getattr setattr delattr


class Animal(object):
    gender = 'male'

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

    def foo(self):
        print('foo...')


# hasattr: 判断一个对象中有没有某种方法或属性
tom = Animal('tom', 34)
ret = hasattr(tom, 'name')  # 判断tom这个对象是否有name这个属性或者方法,等价与tom.name 如果有返回True,否则False
print(ret)   # True
ret2 = hasattr(tom, 'gender')
print(ret2)  # True
ret3 = hasattr(tom, 'run')
print(ret3)  # False
print("---------------- 1 ------------------------------")


# getattr: 获取对象中的某种属性或方法
tom = Animal('tom', 34)
ret = getattr(tom, 'name')  # 如果name存在并且为属性,则返回属性对应的值,等价于tom.name
print(ret)   # tom

ret2 = getattr(tom, 'foo')  # 如果foo存在并且为方法,则返回内存地址,等价与tom.foo
print(ret2)  # <bound method Animal.foo of <__main__.Animal object at 0x7fdd2c2f2e10>>
ret2()       # foo... tom.foo()

ret3 = getattr(tom, 'run', None)  # 如果run不存在则返回None,不设置None的话,直接报错
print(ret3)
# ret4 = getattr(tom, 'run')  # AttributeError: 'Animal' object has no attribute 'run'
print("---------------- 2 ------------------------------")


# hasattr 和 getattr 组合使用(常用的)
tom = Animal('tom', 34)
if hasattr(tom, 'name'):
    ret = getattr(tom, 'name')
    print(ret)  # tom
else:
    print('没有该方法或者属性')

if hasattr(tom, "foo"):
    f = getattr(tom, "foo")
    f()  # foo...
else:
    print("没有该方法或者属性")
print("---------------- 3 ------------------------------")


# setattr: 增加或者修改一个属性或者方法
# 增加一个属性
tom = Animal('tom', 34)
print(tom.__dict__)        # {'name': 'tom', 'age': 34}
setattr(tom, 'job', 'IT')  # 在tom的实例对象空间新增加一个属性
print(tom.__dict__)        # {'name': 'tom', 'age': 34, 'job': 'IT'}

print(getattr(tom, 'job'))  # IT
print(tom.job)              # IT
print("---------------- 4 ------------------------------")

# 修改一个属性
print(tom.age)                 # 34
setattr(tom, 'age', 1000)
print(tom.age)                 # 1000
print("---------------- 5 ------------------------------")


# 增加一个方法
def bar():
    print('bar...')


setattr(tom, 'bar', bar)
tom.bar()
print(tom.__dict__)  # {'name': 'tom', 'age': 1000, 'job': 'IT', 'bar': <function bar at 0x7fd654dc6ea0>}
print("---------------- 6 ------------------------------")


# delattr:删除一个属性
tom = Animal('tom', 34)
print(tom.__dict__)  # {'name': 'tom', 'age': 34}
delattr(tom, 'age')
print(tom.__dict__)  # {'name': 'tom'}
# 报错
# print(tom.age)  # AttributeError: 'Animal' object has no attribute 'age'
print("---------------- 7 ------------------------------")


# 基于字典的映射分发方式
# def check():
#     print('check...')
# def pay():
#     print('pay...')
# def withdraw():
#     print('withdraw...')
# data={
#     "check": check,
#     "pay": pay,
#     "withdraw": withdraw,
# }
# while 1:
#     for i in data.keys():
#         print(i)
#     action = input('>>>:')
#     data[action]()


# 反射的应用
# 基于反射的分发
class Atm(object):
    option_lis = [
        ('check', '检查'),
        ('pay', '支付'),
        ('withdraw', '提现'),
        ('login_out', '退出'),
    ]

    def check(self):
        print('check...')

    def pay(self):
        print('pay...')

    def withdraw(self):
        print('withdraw...')

    def login_out(self):
        exit()

    def view(self):
        while 1:
            for k, i in enumerate(self.option_lis, 1):
                print(k, i[1])
            action = int(input('请输入编号>>:'))
            if 0 <= (action - 1) < len(self.option_lis):
                if hasattr(self, self.option_lis[action - 1][0]):
                    getattr(self, self.option_lis[action - 1][0])()
                else:
                    print('不存在该方法或者属性')
            else:
                print('编号有误')


a = Atm()
a.view()




类的魔法方法

__new__构造方法单例模式

#!/usr/bin/env python3
# author: Alnk(李成果)

# __方法 :  私有方法
# __方法__: 魔法方法


class Animal(object):
    def __new__(cls, *args, **kwargs):  # 构造方法
        print('这是一个构造方法')
        addr = super().__new__(cls)     # 开辟一块内存空间
        print('addr', addr)
        return addr  # 一定要return,addr会传到 __init__ 方法中的self

    def __init__(self, name, age):  # 初始化方法
        print('这是一个初始化方法')
        print('self', self)  # 这个self就是 __new__ 中的addr
        self.name = name
        self.age = age


tom = Animal('tom', 18)
# 实例化的时候,输出结果如下
# 这是一个构造方法
# addr <__main__.Animal object at 0x7fb2872e7c18>
# 这是一个初始化方法
# self <__main__.Animal object at 0x7fb2872e7c18>

# tom = Animal('tom', 18)
# 1. __new__ 方法获得内存空间地址
# 2. 将空间地址作为第一个参数传给 __init__ ,完成实例空间赋值属性的过程
print("----------------- 1 ------------------------------")


# 应用场景
# 一般类
class Dog(object):
    name = "小明"


# 实例化两次,产生不同的内存地址,等于开辟了两块内存空间
t = Dog()
jerry = Dog()
print('tom', id(t))        # 140290412195008
print('jerry', id(jerry))  # 140290412194952
t.name = "小红"
print(t.name, jerry.name)  # 小红 小明
print("----------------- 2 ------------------------------")


# 单例模式: 一个类只能实例化一个对象 例如配置文件类,只需加载一次,节省资源
class Config(object):
    """配置文件类"""
    file_path = ''
    data = 'mysql'
    app = 'LOL'
    _instance = None  # 标记位

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)    # 会开辟一块内存空间
            # print("_instance", cls._instance)     # _instance <__main__.Config object at 0x7fc1762e7f98>
        return cls._instance


# 实例化两次,产生相同的内存地址,等同于只开辟了一块内存空间
cf1 = Config()
cf2 = Config()
print('cf1', id(cf1))  # 140669493346088
print('cf2', id(cf2))  # 140669493346088
cf1.app = "CF"
print(cf1.app, cf2.app)  # CF CF

# 第一次实例化时,由于此时标记位 _instance = None,所以程序会走if判断,会开辟一块内存空间并且赋值给 _instance
# 第二次以及以后的实例化时,此时的标记位 _instance 已经不为None,所以会直接返回第一次开辟的内存空间



__init__初始化方法

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
# @Author: Alnk(李成果)
# @Email: 1029612787@qq.com
# @Date: 2021/4/22 3:04 下午
"""

# __init__ 初始化方法


class Animal(object):

    def __init__(self):
        """初始化方法"""
        print("我在类的实例化的时候自动执行")
        print("创建数据库连接...")
        print("程序开始启动...")


a = Animal()
# 打印结果
# 我在类的实例化的时候自动执行
# 创建数据库连接...
# 程序开始启动...



__str__格式化打印类的实例对象

#!/usr/bin/env python3
# author: Alnk(李成果)
# 打印类的任何 实例 对象,都返回 __str__ 的返回值
# 注意: 返回值必须是字符串


class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        """打印类的任何实例对象,都返回 __str__ 的返回值"""
        # return self.name  # 返回值必须是字符串
        return "姓名:%s,年龄:%s" % (self.name, str(self.age))


t = Animal('tom', 18)
j = Animal('jerry', 28)
print(t)       # 姓名:tom,年龄:18
print(j)       # 姓名:jerry,年龄:28
print(t.name)  # tom

# 当注释掉自定义的 __str__ 方法以后,会有如下返回
# print(t)  # <__main__.Animal object at 0x7f96fe9c5278>
# print(j)  # <__main__.Animal object at 0x7f96fe9c5358>



__call__可调用对象

#!/usr/bin/env python3
# author: Alnk(李成果)

# __call__
# 只要可以调用的类,内部一定有 __call__ 方法,如果没有则不能调用

# Python中万物都可称为对象,但对象与对象之间也有差别
# 对象有 可被调用对象 和 不可被调用对象 之分
# Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象
# 当一个对象为可被调用对象时,callable(object)返回为True,否则为False:
# 实例是不可以被调用的,但是通过 __call__ 方法可以让实例可以调用


class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def foo(self):
        print('foo...')


# callable: 判断一个对象是否可以调用
tom = Animal("tom", 12)
print(callable(tom.foo))       # True  tom.foo()     可以调用
print(callable(Animal.foo))    # True  Animal.foo()  可以调用
print(callable(tom))           # False tom()         不能调用
# tom()  # 报错


# 在上面的类中增加 __call__ 方法
class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def foo(self):
        print('foo...')

    def __call__(self, *args, **kwargs):
        """增加__call__方法,保证了类的实例可以被调用"""
        print('i am call...')


t = Animal("tom", 18)
print(callable(t))  # True 可以调用了
t()  # i am call...



__del__析构方法程序首尾操作

#!/usr/bin/env python3
# author:Alnk(李成果)

# 析构方法:__del__
# 在程序结束,或者类在内存中被删除的时候,就会执行 __del__ 方法
# python 垃圾回收机制:任何对象在执行过程中失去了指引,则被回收

import time


class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def foo(self):
        print('foo...')

    def __del__(self):
        print('我是析构方法...')
        print("断开与数据库连接...")
        print("保存文件...")


# 会等待5s,程序结束,然后执行 __del__ 析构方法
# tom = Animal('tom', 34)
# time.sleep(5)
"""
# 输出结果
# 我是析构方法...
# 断开与数据库连接...
# 保存文件...
"""


# 会先执行 __del__ 析构方法,然后等待5s 程序结束
tom = Animal('tom', 34)
del tom
time.sleep(5)


# 应用场景
# 例如:关闭数据库连接,关闭文件连接等



__getitem__ __setitem__ __delitem__

#!/usr/bin/env python3
# author: Alnk(李成果)


class Animal(object):
    gender = 'male'

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

    def __getitem__(self, item):
        print('getitem被调用')
        print('大量的业务逻辑代码...')
        # 下面第二个return的写法更好,避免程序报错
        # return self.__dict__[item]      # 如果找不到item变量,程序报错
        return getattr(self, item, None)  # 如果找不到item变量,则返回None

    def __setitem__(self, key, value):
        print('__setitem__ 被调用')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('__delitem__被调用')
        del self.__dict__[key]


# __getitem__(self, item) 返回键对应的值
# 调用一个属性前,加自己的业务逻辑等
# 调用属性 必须要使用 tom["name"] 这种方法调用才会触发 __getitem__ 方法
tom = Animal('tom', 18)
print(tom['name'])
print("------------- 1 --------------------")
# 输出结果如下
# getitem被调用
# 大量的业务逻辑代码...
# tom

print(tom.name)  # 没有触发 __getitem__
print("------------- 2 --------------------")

print(tom['age'])  # tom.age
print("------------- 3 --------------------")

print(tom['gender'])  # tom.gender
print("------------- 4 --------------------")

print(tom['job'])  # tom.job
print("------------- 5 --------------------")


# __setitem__(self, key, value): 设置给定键的值
jerry = Animal('jerry', 19)
jerry['job'] = 'IT'      # __setitem__ 被调用
jerry['gender'] = 'F'   # __setitem__ 被调用
print("------------- 6 --------------------")

print(jerry['job'])
print(jerry.__dict__)    # {'name': 'jerry', 'age': 19, 'job': 'IT', 'gender': 'F'}
print("------------- 7 --------------------")


# __delitem__:删除给定键对应的元素
tom = Animal('tom', 19)
print(tom.__dict__)  # {'name': 'tom', 'age': 19}

del tom['name']  # __delitem__被调用

print(tom['name'])
# getitem被调用
# 大量的业务逻辑代码...
# None

print(tom.__dict__)  # {'age': 34}



__getattr__ __setattr__

#!/usr/bin/env python3
# author: Alnk(李成果)


# getattr
class Animal(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __getattr__(self, item):
        print('getattr', item)
        return '对象没有%s属性' % item


# __getattr__
# 如果属性查找在实例以及对应的类中(通过__dict__)失败
# 那么会调用到类的__getattr__函数
# 如果没有定义这个函数,那么抛出 AttributeError 异常
# 由此可见 __getattr__ 是作用于属性查找的最后一步,兜底
# 当使用点号获取实例属性时,如果属性不存在就自动调用__getattr__方法
tom = Animal('tom', 55)
print(tom.age)  # 55

# 实例中没有gender属性,所以调用__getattr__方法
print(tom.gender)
print("------------ 1 ------------------------")
# 输出结果
# getattr gender
# 对象没有gender属性


# setattr
class Animal(object):
    def __init__(self, name, age):
        self.name = name           # object类一定会有__setattr__方法
        self.age = age             # 注意: 初始化的时候会调用 __setattr__(self, key, value) 进行赋值

    def __setattr__(self, key, value):
        # 打印日志
        print("空间地址%s为%s属性赋值%s" % (self, key, value))
        super().__setattr__(key, value)  # 继承父类的__setattr__ 方法


# __setattr__
# 当类实例化时,会调用 __init__ 方法,__init__ 方法设置属性的时候会调用 __setattr__
tom = Animal('tom', 55)
# 输出结果
# 空间地址<__main__.Animal object at 0x7f9e08af2cc0>为name属性赋值tom
# 空间地址<__main__.Animal object at 0x7f9e08af2cc0>为age属性赋值55

tom.name = 'lisa'  # 空间地址<__main__.Animal object at 0x7f9a362e7c88>为name属性赋值lisa
print(tom.name)    # lisa


# tom = Animal('tom', 55)
# 类实例化的过程:
# 1. __new__: 开辟内存空间,传递空间地址给 __init__
# 2.__init__: 接收 __new__ 传递过来的空间地址,并且进行赋值操作
# 3.__setattr__: 接收 __init__ 的赋值,在内存中进行赋值



__eq__

#!/usr/bin/env python3
# author:Alnk(李成果)


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

    def __eq__(self, other):
        if self.name == other.name and self.age == other.age:
            return True


tom = Student('tom', 34)
jerry = Student('tom', 34)

# __eq__ 方法注释就为False,否则为True
print(tom == jerry)



__len__

#!/usr/bin/env python3
# author:Alnk(李成果)


class Data(object):

    def __len__(self):
        return 345  # 只能返回数字


d = Data()
print(len(d))         # 345
print(d.__len__())    # 345



__dict__

#!/usr/bin/env python3
# author:Alnk(李成果)


class Dog(object):
    x = 100

    @classmethod
    def speak(cls):
        print("来 请开始你的表演")

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

    def talk(self):
        print('%s is wang wang wang ' % self.name)


# 通过类调用,以字典形式打印类中的所有方法和属性,不包括实例属性
print(Dog.__dict__)
"""
{
    'x': 100, 
    'speak': <classmethod object at 0x7f8ed2aba278>, 
    '__init__': <function Dog.__init__ at 0x7f8ed2ae19d8>, 
    'talk': <function Dog.talk at 0x7f8ed2ae18c8>,
}
"""

# 只打印实例属性,不包括类属性
d = Dog('tom')
print(d.__dict__)  # {'name': 'tom'}




练习题1

#!/usr/bin/env python3
# author:Alnk(李成果)
# 1、new方法和init方法执行的执行顺序
# 先执行 __new__ 方法,后执行 __init__ 方法


# 2、call方法在什么时候被调用
#


# 3、请用写一个类,用反射为这个类添加一个静态属性
class A(object):
    pass


def foo():
    print("in foo...")


setattr(A, "foo", foo)
A.foo()  # in foo...
print("-------------- 1 ----------------------------")


# 4、请用反射为上题的类的对象添加一个属性name,值为你的名字
setattr(A, "name", "Alnk")
print(A.name)  # Alnk
print("-------------- 2 ----------------------------")


# 5、请使用new方法实现一个单例模式
class Config(object):
    """配置文件类"""
    user = "root"
    pwd = "root123"
    db_name = "mysql"
    ip = "192.168.1.2"
    port = 3306
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance


cf1 = Config()
print(id(cf1))  # 140441860078896
cf2 = Config()
print(id(cf2))  # 140441860078896
cf1.user = "admin"
print(cf1.user, cf2.user)  # admin admin
print("-------------- 3 ----------------------------")


# 6、校验两个文件的一致性
import hashlib


def get_md5(path: str):
    """
    获取文件的md5
    @param path: 文件路径
    @return: 文件md5串
    """
    md5 = hashlib.md5()

    with open(path) as f:
        for i in f.readline():
            print("+++", i)
            md5.update(i.encode("utf-8"))
    return md5.hexdigest()


a = get_md5("a.txt")
b = get_md5("b.txt")
print(a == b)
print("-------------- 4 ----------------------------")


# 7、加密的密文登陆
import getpass


def login():
    user_name = input("用户名>>>:")
    user_pwd = getpass.getpass("密码>>>:")  # 在终端好使,在pycharm不可以

    if user_name == "alnk" and user_pwd == "123":
        print("登录成功")
    else:
        print("登录失败")


# login()
print("-------------- 5 ----------------------------")


# 8、完成一个既可以向文件输出又可以向屏幕输出的日志设置
import logging.config


# 定义三种日志输出格式 开始
# 标准版
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'  # 其中name为getlogger指定的名字

# 定义日志输出格式 结束
logfile_name = r'staff.log'  # log文件名

# log配置字典
LOGGING_DIC = {
    'version': 1,  # 版本
    'disable_existing_loggers': False,  # 可否重复使用之前的logger对象
    'formatters': {
        'standard': {
            'format': standard_format
        },
    },
    'filters': {},
    'handlers': {
        # 打印到终端的日志
        'stream': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'standard'
        },
        # 打印到文件的日志,收集info及以上的日志  文件句柄
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',   # 标准
            'filename': logfile_name,  # 日志文件
            'maxBytes': 30000,         # 日志大小 30000 bit
            'backupCount': 5,          # 轮转文件数
            'encoding': 'utf-8',       # 日志文件的编码,再也不用担心中文log乱码了
        },
    },
    'loggers': {
        # logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['stream', 'file'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG',                # 总级别
            'propagate': True,               # 向上(更高level的logger)传递
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)    # 生成一个log实例
    logger.info('It works!')                # 记录该文件的运行状态


load_my_logging_cfg()




练习2-ATM项目

README.txt

作者: Alnk(李成果)
版本:v1.0

程序介绍
    功能全部用python知识完成,用到了 os\sys\json\模块导入\面向对象编程 等知识
    用户角色:
        实现ATM常用功能,如查看账户信息、提现、还款、转账、支付等
    管理员角色:
        添加账户、调整用户额度、冻结/激活账户、查看用户信息等功能


程序启动方式
    直接运行 atm/bin/start.py 文件


登录账号密码
    管理员账号
        账号:admin
        密码:123
    普通用户
        账号:123456
        密码:123


程序结构
├── atm
    ├─bin                   # 执行目录
    │  │  start.py          # 执行文件
    |  |  __init__.py       # 包文件
    │
    ├─conf                  # 配置文件目录
    │  │  settings.py       # 配置文件
    |  |  __init__.py       # 包文件
    │
    ├─core                  # 核心代码目录
    │  │  admin.py          # 管理员模块
    │  │  basic.py          # 基础模块
    │  │  decorator.py      # 装饰器模块,外部调用付款接口时会用到
    │  │  log.py            # 日志模块
    │  │  login.py          # 登录认证木块
    │  │  main.py           # 功能分发模块
    │  │  user.py           # 普通用户模块
    │  │  payment.py        # 第三方调用支付模块
    |  |  __init__.py       # 包文件
    │
    ├─db                    # 数据目录
    │  └─user_info          # 用户登录信息目录
    │          123456.json  # 普通文件,可以用管理员账号创建
    │          admin.json   # 管理员初始化文件,记录账号密码权限等信息
    |          __init__.py  # 包文件
    │
    ├─docs                  # 文档目录
    │      README.txt       # 程序说明文件
    │      需求.txt          # 需求
    │
    ├─log                   # 日志记录目录
    │      123456.log       # 普通用户日志
    │      admin.log        # 管理员日志文件


db/user_info/admin.json 文件示例说明
    user_dict = {
        'name': name,  # 账号
        'pwd': pwd,  # 密码
        'role': role,  # 角色 管理员:admin, 用户:user
        'total_quota': total_quota,  # 总额度
        'available_quota': total_quota,  # 可用额度
        'state': 1,  # 状态 1:可用,0:冻结
    }


start.py

#!/usr/bin/env python3
# author: Alnk(李成果)
import os
import sys


# 添加项目目录到python环境
base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # 项目目录
# print(base_path)
sys.path.insert(0, base_path)
# print(sys.path)


from atm.core.main import view


# 程序入口,就是这么简单
if __name__ == '__main__':
    view()


settings.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import os

# 程序目录
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# 用户数据库目录与文件
# 数据库目录
db_path = os.path.join(base_path, 'db')
# 用户信息目录
user_info_path = os.path.join(db_path, 'user_info')


# 日志目录
log_path = os.path.join(base_path, 'log')
# admin管理员日志文件
admin_log_path = os.path.join(log_path, 'admin.log')


# 提现手续费
service_charge = 0.05


admin.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import os
from atm.core.basic import Basic
from atm.conf import settings
from atm.core.log import get_logger


class Admin(Basic):
    """管理类"""

    option_lis = [
        ('添加账户', 'create_user'),
        ('调整用户额度', 'modify_quota'),
        ('冻结/激活账户', 'frozen_user'),
        ('查看用户信息', 'check_user_info'),
        ('退出', 'login_out'),
    ]

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

    def create_user(self):
        """
        创建用户
        :return:
        """
        name = input('\n输入卡号>>>:')
        pwd = input('输入密码>>>:')
        total_quota = 15000  # 额度
        role = 'user'  # 角色
        user_dict = {
            'name': name,                    # 账号
            'pwd': pwd,                      # 密码
            'role': role,                    # 角色 管理员:admin, 用户:user
            'total_quota': total_quota,      # 总额度
            'available_quota': total_quota,  # 可用额度
            'state': 1,                      # 状态 1:可用,0:冻结
        }

        file_path = os.path.join(settings.user_info_path, '%s.json' % name)  # 用户文件路径
        self.write_dict(user_dict, file_path)
        log_msg = '用户[%s]创建成功!' % name
        get_logger(settings.admin_log_path, log_msg)

    def modify_quota(self):
        """
        调整用户额度
        :return:
        """
        name = input('调整的卡号>>>')
        quota = int(input('调整的额度>>>'))
        file_path = os.path.join(settings.user_info_path, '%s.json' % name)  # 用户文件路径
        user_dict = self.read_dict(file_path)
        user_dict['total_quota'] = user_dict['total_quota'] + quota  # 调整总额度
        user_dict['available_quota'] = user_dict['available_quota'] + quota  # 调整实际额度
        self.write_dict(user_dict, file_path)
        log_msg = '账户[%s]额度调整成功,调整后的额度为[%s]' % (name, user_dict['total_quota'])
        get_logger(settings.admin_log_path, log_msg)

    def frozen_user(self):
        """
        冻结/解冻 账户
        :return: Ture
        """
        name = input('需要冻结/激活的卡号>>>')
        state = int(input('1:激活/0:冻结 >>>'))
        file_path = os.path.join(settings.user_info_path, '%s.json' % name)  # 文件路径
        user_dict = self.read_dict(file_path)
        user_dict['state'] = state
        self.write_dict(user_dict, file_path)
        print('账号[%s]被[%s]' % (name, '激活' if user_dict['state'] == 1 else '冻结'))
        log_msg = '账号[%s]被[%s]' % (name, '激活' if user_dict['state'] == 1 else '冻结')
        get_logger(settings.admin_log_path, log_msg)  # 日志

    def check_user_info(self):
        name = input('需要查看的账号>>>')
        super().check_user_info(name)

    def run(self):
        while 1:
            print('\n--- 欢迎管理员[%s] ---' % self.name)
            for num, value in enumerate(self.option_lis, 1):
                print(num, value[0])
            action = int(input('\n输入编号>>>'))
            if hasattr(self, self.option_lis[action - 1][1]):
                getattr(self, self.option_lis[action - 1][1])()
            else:
                print('\n编号有误,请重新输入\n')


basic.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import json
import os
from atm.conf import settings


class Basic:
    """基础类"""

    def write_dict(self, user_dict, file_name):
        """
        写入数据
        :param user_dict: 用户信息字典
        :param file_name: 文件名称
        :return: True
        """
        with open(file_name, encoding='utf-8', mode='w') as f:
            json.dump(user_dict, f)
            f.flush()
        return True

    def read_dict(self, file_name):
        """
        读取数据
        :param file_name: 文件名称
        :return: user_dict
        """
        with open(file_name, encoding='utf-8', mode='r') as f:
            user_dict = json.load(f)
        return user_dict

    def check_user_info(self, name):  # 查看用户详细信息
        file_path = os.path.join(settings.user_info_path, '%s.json' % name)  # 文件路径
        user_dict = self.read_dict(file_path)
        msg = '''\n------ 账号[%s]的详细信息 ------
        账号: %s
        总额度: %s
        可用额度: %s
        状态: %s\n\n
        ''' % (user_dict['name'], user_dict['name'],
               user_dict['total_quota'],
               user_dict['available_quota'],
               '激活' if user_dict['state'] == 1 else '冻结',
               )
        print(msg)

    def login_out(self):
        exit('退出')


decorator.py

#!/usr/bin/env python3
# author:Alnk(李成果)
from atm.core.login import Login


def outer(name, pwd):  # 装饰器
    def wraper(func):
        def inner(*args, **kwargs):
            login = Login()
            ret = login.login(name, pwd)
            if ret:
                ret2 = func(*args, **kwargs)
                return ret2

        return inner

    return wraper


log.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import logging
from logging import DEBUG


def get_logger(log_file, log_msg):
    # 1.创建logger函数对象
    logger = logging.getLogger()  # 这个参数是用户名字
    # 2.创建流对象:文件流fh,屏幕流ch
    fh = logging.FileHandler(log_file)  # 写入日志文件
    # 3.创建日志格式:这里可以创建多个日志格式
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # 4.流对象添加格式对象
    fh.setFormatter(formatter)  # 文件流添加日志格式
    # 5.logger对象添加流对象
    logger.addHandler(fh)
    # 6.设置日志打印级别
    logger.setLevel(DEBUG)  # 全局设置,同时设置文件流和屏幕流
    #
    logger.info(log_msg)  # 打印INFO日志
    # 在记录日志之后移除句柄,不然会重复打印日志
    logger.removeHandler(fh)


login.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import os
from atm.conf.settings import user_info_path, log_path
from atm.core.basic import Basic
from atm.core.log import get_logger


class Login(Basic):
    """
    认证类
    """

    def login(self, name=None, pwd=None):
        if not name and not pwd:
            name = input('卡号>>>:')
            pwd = input('密码>>>:')

        file_path = os.path.join(user_info_path, '%s.json' % name)
        if os.path.isfile(file_path):
            user_dict = self.read_dict(file_path)
        else:
            print('账号或者密码错误!')
            return False

        if name == user_dict['name'] and pwd == user_dict['pwd'] and user_dict['state'] == 1:
            user_log_path = os.path.join(log_path, '%s.log' % name)
            log_msg = '卡号[%s]登录成功!' % name
            get_logger(user_log_path, log_msg)  # 写入日志
            print('登录成功!\n')
            return user_dict
        else:
            print('登录失败,联系管理员')
            return False


main.py

#!/usr/bin/env python3
# author: Alnk(李成果)
from atm.core.admin import Admin
from atm.core.login import Login
from atm.core.user import User


def view():
    """
    视图函数
    """
    login = Login()
    ret = login.login()
    if ret:
        if ret['role'] == 'admin':
            a = Admin(ret['name'])
            a.run()
        elif ret['role'] == 'user':
            u = User(ret['name'])
            u.run()
        else:
            print("无效用户")
    else:
        print("登录失败,请重新登录")


payment.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import os
from atm.conf import settings
from atm.core.decorator import outer
from atm.core.basic import Basic
from atm.core.log import get_logger
from atm.core.login import Login


class PayMent(Login):  # 支付功能

    def payment(self, cash):
        ret = self.login()  # 获取登录返回值
        if ret == False:
            return 0  # 账号或密码错误
        else:
            self.name = ret['name']
        file_path = os.path.join(settings.user_info_path, '%s.json' % self.name)  # 用户文件路径
        user_dict = self.read_dict(file_path)  # 获取用户详细信息
        if cash <= user_dict['available_quota']:
            user_dict['available_quota'] -= cash
            self.write_dict(user_dict, file_path)  # 写入文件
            log_file = os.path.join(settings.log_path, '%s.log' % self.name)
            log_msg = '通过第三方支付[%s]元' % cash
            get_logger(log_file, log_msg)
            return 2  # 支付成功
        else:
            return 1  # 额度不够


user.py

#!/usr/bin/env python3
# author:Alnk(李成果)
import os
from atm.core.basic import Basic
from atm.conf import settings
from atm.core.decorator import outer
from atm.core.log import get_logger


class User(Basic):  # 用户类
    option_lis = [('查看账户信息', 'check_user_info'),
                  ('取现', 'with_draw'),
                  ('还款', 'pay_back'),
                  ('转账', 'transfer'),
                  ('退出', 'login_out')]

    def __init__(self, name):
        self.name = name
        self.file_path = os.path.join(settings.user_info_path, '%s.json' % self.name)  # 初始化用户信息文件路径
        self.user_dict = self.read_dict(self.file_path)  # 获取用户详细信息
        self.log_file = os.path.join(settings.log_path, '%s.log' % self.name)  # 日志文件路径

    def check_user_info(self):  # 查看账户信息
        super().check_user_info(self.name)

    def with_draw(self):  # 提现
        print('你可提现额度为[%s]' % (float(self.user_dict['available_quota']) * (1 - settings.service_charge)))  # 要手续费
        quota = float(input('输入你想提现的金额 手续费为5%>>>'))
        if quota <= float(self.user_dict['available_quota']) * (1 - settings.service_charge):  # 提现不能超过可用额度
            self.user_dict['available_quota'] -= quota * (1 + settings.service_charge)  # 可用额度减去提现额度和手续费
            self.write_dict(self.user_dict, self.file_path)
            print('提现成功\n')
            log_msg = '用户[%s]提现[%s]元 手续费[%s]' % (self.name, quota, quota * settings.service_charge)
            get_logger(self.log_file, log_msg)  # 写入到日志
        else:
            print('请输入合理的提现金额')

    def pay_back(self):  # 还款
        pay_back_quota = self.user_dict['total_quota'] - self.user_dict['available_quota']  # 还款额度
        print('你需要还款的总额为[%s]' % pay_back_quota)
        if pay_back_quota == 0:
            print('不需要还款\n')
            return False
        input_quota = float(input('输入还款金额>>>'))
        if 0 < input_quota <= pay_back_quota:
            self.user_dict['available_quota'] += input_quota
            self.write_dict(self.user_dict, self.file_path)
            print('还款成功,可用额度为[%s]\n' % self.user_dict['available_quota'])
            log_msg = '用户[%s]还款[%s]元' % (self.name, input_quota)
            get_logger(self.log_file, log_msg)

    def transfer(self):  # 转账
        friend_card = input('输入对方卡号>>>')
        money = float(input('转账金额>>>'))
        if not os.path.isfile(os.path.join(settings.user_info_path, '%s.json' % friend_card)):
            print('账号[%s]不存在\n' % friend_card)
            return False
        if self.user_dict['available_quota'] >= money:
            self.user_dict['available_quota'] -= money  # 转账,自己账号减少额度
            self.write_dict(self.user_dict, self.file_path)  # 写入自己的文件
            file_path = os.path.join(settings.user_info_path, '%s.json' % friend_card)  # 对方文件路径
            friend_dict = self.read_dict(file_path)  # 对方相信信息
            friend_dict['available_quota'] += money  # 对方账号增加额度
            self.write_dict(friend_dict, file_path)  # 对方详细信息写入文件
            print('转账成功')
            log_msg = '用户[%s]向账号[%s]转账[%s]元' % (self.name, friend_card, money)
            get_logger(self.log_file, log_msg)
        else:
            print('可用余额不足\n')

    def run(self):
        while 1:
            print('\n--- 欢迎用户[%s] ---' % self.name)
            for num, value in enumerate(self.option_lis, 1):
                print(num, value[0])
            action = int(input('\n输入编号>>>'))
            if hasattr(self, self.option_lis[action - 1][1]):
                getattr(self, self.option_lis[action - 1][1])()
            else:
                print('\n编号有误,请重新输入\n')


123456.json

{"name": "123456", "pwd": "123", "role": "user", "total_quota": 18000, "available_quota": 806.0, "state": 1}


admin.json

{"name": "admin", "pwd": "123", "role": "admin", "total_quota": 15000, "available_quota": 15000, "state": 1}


需求.txt

需求
1. 用户额度 15000或自定义
2. 实现购物商城,买东西加入 购物车,调用信用卡功能进行结账
3. 可以提现,手续费5%
4. 支持多账户登录
5. 支持账户间转账(用户A转账给用户B,A账户减钱、B账户加钱)
6. 记录每月日常消费流水
7. 提供还款功能
8. ATM记录操作日志(使用logging模块记录日志)
9. 提供管理功能,包括添加账户、用户额度,冻结账户等
10. 用户认证用装饰器


编码规范需求
1. 代码规范遵守pep8 (https://python.org/dev/peps/pep-0008/)
2. 函数有相应的注释
3. 程序有文档说明文件(README.md参考:https://github.com/csrftoken/vueDrfDemo)
4. 程序的说明文档必须包含的内容:程序的实现的功能、程序的启动方式、登录用户信息、程序的运行效果
5. 程序设计的流程图:


123456.log

2021-04-23 14:14:39,348 - INFO - 卡号[123456]登录成功!
2021-04-23 14:22:43,287 - INFO - 卡号[123456]登录成功!
2021-04-23 14:51:57,139 - INFO - 卡号[123456]登录成功!


admin.log

2021-04-23 14:26:57,269 - INFO - 卡号[admin]登录成功!


posted @ 2021-04-23 15:53  李成果  阅读(24)  评论(0编辑  收藏  举报