17、python3 面向对象

作者: Brinnatt 分类: python 术 发布时间: 2023-03-30 10:26

17.1、语言的分类

面向机器:抽象成机器指令,机器容易理解。

  • 汇编语言

面向过程:做一件事情,排出个步骤,第一步干什么,第二步干什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理。问题规模小,可以步骤化,按部就班处理。

  • C 语言

面向对象 00P:随着计算机需要解决的问题的规模扩大,情况越来越复杂。需要很多人、很多部门协作,面向过程编程不太适合了。

  • C++、Java、Python 等

17.2、什么是面向对象

什么是面向对象呢?

  • 一种认识世界、分析世界的方法论。将万事万物抽象为类。

类 class?

  • 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。
  • 用计算机语言来描述类,就是属性和方法的集合。

对象 instance、object?

  • 对象是类的具象,是一个实体。
  • 对于我们每个人这个个体,都是抽象概念人类的不同的实体。

属性:它是对象特征的抽象,用数据结构来描述。

操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述。

每个人都有名字、身高、体重等信息,这些信息是个人的属性,但是,这些信息不能保存在人类中,因为它是抽象的概念,不能保留具体的值。

而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。

哲学:一切皆对象。对象是数据和操作的封装。对象是独立的,但是对象之间可以相互作用。目前 00P 是最接近人类认知的编程范式。

17.3、面向对象3要素

  1. 封装

    组装,将数据和操作组装到一起。

    隐藏数据,对外只暴露一些接口,通过接口访问对象。

  2. 继承

    多复用,继承来的就不用自己写了。

    多继承少修改,OCP( Open-closed Principle),使用继承来改变,来体现个性。

  3. 多态

    面向对象编程最灵活的地方,动态绑定。

人类就是封装:

  • 人类继承自动物类,孩子继承父母特征。分为单一继承、多继承;

  • 多态,继承自动物类的人类、猫类的方法 ”吃“ 不同。

17.4、Python 类

17.4.1、定义

class ClassName:
    语句块
  1. 必须使用 class 关键字。
  2. 类名必须是用大驼峰命名。
  3. 类定义完成后,就产生了一个类对象,绑定到了标识符 ClassName 上。

举例:

class MyClass:
    """An example class"""
    x = 'abc'

    def foo(self):  # 类属性foo,也是方法
        return 'My Class'

print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)

17.4.2、类对象及类属性

  • 类对象:类的定义就会生成一个类对象。

  • 类的属性:类定义中的变量和类中定义的方法都是类的属性。

  • 类变量:上例中 x 是类 MyClass 的变量。

MyClass中,x、foo 都是类的属性,__doc__ 也是类的属性。

foo 方法是类的属性,如同 是人类的方法,但是每一个具体的人才能吃东西,也就是说 是人的实例才能调用的方法。

foo 是方法对象 method,不是普通的函数对象 function 了,它一般要求至少有一个参数。第一个参数可以是self(self 只是个惯用标识符,可以换名字),这个参数位置就留给了 self。

self 指代当前实例本身

问题:上例中,类是谁?实例是谁?

17.4.3、实例化

a = MyClass() # 实例化

使用上面的语法,在类对象名称后面加上一个括号,就调用类的实例化方法,完成实例化。

实例化就真正创建一个该类的对象(实例)。例如:

tom = Person()
jerry = Person()

上面的 tom、jerry 都是 Person 类的实例,通过实例化生成了 2 个实例。

每次实例化后获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象。

Python 类实例化后,会自动调用 __init__ 方法。这个方法第一个参数必须留给 self,其它参数随意。

17.4.3.1、__init__ 方法

MyClass() 实际上调用的是 __init__(self) 方法,可以不定义,如果没有定义会在实例化后 隐式 调用。

作用:对实例进行 初始化

class MyClass:
    def __init__(self):
        print('init')

print(MyClass)  # 不会调用
print(MyClass())  # 调用__init__
a = MyClass()  # 调用__init__

初始化函数可以多个参数,请注意第一个位置必须是 self,例如 __init__(self,name,age)

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

    def show_age(self):
        print('{} is {}'.format(self.name, self.age))

tom = Person('Tom', 20)  # 实例化
jerry = Person('Je', 25)
print(tom.name, jerry.age)
jerry.age += 1
print(jerry.age)
jerry.show_age()

注意:__init__() 方法不能有返回值,也就是只能是 None。

17.4.3.2、实例对象 instance

类实例化后一定会获得一个对象,就是 实例对象

上例中的 tom、jerry 就是 Person 类的实例。

__init__ 方法的第一参数 self 就是指代某一个实例。

类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时采用 jerry.show_age() 的方式。

但是函数签名是 show_age(self),少传一个参数 self 吗?

这个 self 就是 jerry,Python 会把方法的调用者作为第一参数 self 的实参传入。

self.name 就是 jerry 对象的 name,name 是保存在了 jerry 对象上,而不是 Person 类上。所以,称为 实例变量

17.4.3.3、self

class MyClass:
    def __init__(self):
        print('self in init={}'.format(id(self)))

c = MyClass()  # 会调用 __init__
print('c = {}'.format(id(c)))

# 打印结果为
self in init=2379777013312
c = 2379777013312

上例说明,self 就是调用者,就是 c 对应的实例对象。

self 这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性。

看打印的结果,思考一下执行的顺序,为什么?

17.4.3.4、实例变量和类变量

class Person:
    age = 3

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

# tom = Person('Tom', 20)
tom = Person('Tom')
jerry = Person('Jerry')

print(tom.name, tom.age)
print(jerry.name, jerry.age)
print(Person.age)
# print(Person.name)
Person.age = 30
print(Person.age, tom.age, jerry.age)

# 运行结果
Tom 3
Jerry 3
3
30 30 30

实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法

特殊属性 含义
__name__ 对象名
__class__ 对象的类型
__dict__ 对象的属性的字典
__qualname__ 类的限定名

注意:Python 中每一种对象都拥有不同的属性。函数、类都是对象,类的实例也是对象。

举例:

class Person:
    age = 3

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

print('----class----')
print(Person.__class__)
print(sorted(Person.__dict__.items()), end='\n\n')  # 属性字典

tom = Person('Tom')
print('----instance tom----')
print(tom.__class__)
print(sorted(tom.__dict__.items()), end='\n\n')

print("----tom's class----")
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()), end='\n\n')

上例中,可以看到类属性保存在类的 __dict__ 中,实例属性保存在实例的 __dict__ 中,如果从实例访问类的属性,就需要借助 __class__ 找到所属的类。

有了上面知识,再看下面的代码。

class Person:
    age = 3
    height = 170

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

tom = Person('Tome')  # 实例化,初始化
jerry = Person('Jerry', 20)

Person.age = 30
print(1, '-->', Person.age, tom.age, jerry.age)  # 输出什么结果
print(2, '-->', Person.height, tom.height, jerry.height)

jerry.height = 175
print(3, '-->', Person.height, tom.height, jerry.height)

tom.height += 10
print(4, '-->', Person.height, tom.height, jerry.height)

Person.height += 15
print(5, '-->', Person.height, tom.height, jerry.height)

Person.weight = 70
print(6, '-->', Person.weight, tom.weight, jerry.weight)

print(1, '*-->', tom.__dict__['height'])
# print(2, '*-->', tom.__dict__['weight'])  # 可以吗
print(3, '*-->', tom.__dict__)
print(4, '*-->', Person.__dict__)

总结

是类的,也是这个类所有实例的,其实例都可以访问到;是实例的,就是这个实例自己的,通过类访问不到。

类变量是属于类的变量,这个类的所有实例可以 共享 这个变量。

实例可以动态的给自己增加一个属性。实例.__dict__[变量名]实例.变量名 都可以访问到。

实例的同名变量会 隐藏 类变量,或者说是覆盖了这个类变量。

实例属性的查找顺序

指的是实例使用 . 来访问属性,会先找自己的 __dict__,如果没有,然后通过属性 __class__ 找到自己的类,再去类的 __dict__ 中找。

注意,如果实例使用 __dict__[变量名] 访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的 key 查找,不是属性查找。

一般来说,类变量使用全大写来命名

17.4.4、装饰一个类

回顾,什么是高阶函数?什么是装饰器函数?

思考,如何装饰一个类?

需求,为一个类通过装饰,增加一些类属性。

# 增加类变量
def add_name(name, cls):
    cls.NAME = name  # 动态增加类属性

# 改进成装饰器
def add_name(name):
    def wrapper(cls):
        cls.NAME = name
        return cls

    return wrapper

@add_name('Tom')
class Person:
    AGE = 3

print(Person.NAME)

之所以能够装饰,本质上是为类对象动态的添加了一个属性,而 Person 这个标识符指向这个类对象。

17.4.5、类方法和静态方法

前面的例子中定义的 __init__ 等方法,这些方法本身都是类的属性,第一个参数必须是 self,而 self 必须指向一个对象,也就是类必须实例化之后,由实例来调用这个方法。

17.4.5.1、普通函数

class Person:
    def normal_method():  # 可以吗?
        print('normal')

# 如何调用
Person.normal_method()  # 可以吗
# Person().normal_method()  # 可以吗

print(Person.__dict__)

Person.normal_method()

可以,因为这个方法只是被 Person 这个名词空间管理的一个普通的方法,normal_method 只是 Person 的一个属性而已。

由于 normal_method 在定义的时候没有指定 self,所以不能完成实例对象的绑定,不能用 Person().normal_method() 调用。

注意:虽然语法是对的,但是,没有人这么用,也就是说 禁止 这么写。

17.4.5.2、类方法

class Person:
    @classmethod
    def class_method(cls):  # cls 是什么
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

Person.class_method()
print(Person.__dict__)

类方法:

  1. 在类定义中,使用 @classmethod 装饰器修饰的方法。

  2. 必须至少有一个参数,且第一个参数留给了 cls,cls 指代调用者即类对象自身。

  3. cls 这个标识符可以是任意合法名称,但是为了易读,请不要修改。

  4. 通过 cls 可以直接操作类的属性。

注意:无法通过 cls 操作类的实例。为什么?

类方法,类似于 C++、Java 中的静态方法。

17.4.5.3、静态方法

class Person:
    @classmethod
    def class_method(cls):  # cls 是什么
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HEIGHT)

Person.class_method()
Person.static_method()
print(Person.__dict__)
  1. 在类定义中,使用 @staticmethod 装饰器修饰的方法。

  2. 调用时,不会隐式的传入参数。

静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理。

17.4.5.4、方法的调用

类可以定义这么多种方法,究竟如何调用它们?

class Person:
    def normal_method():
        print('normal')

    def method(self):
        print("{}'s method".format(self))

    @classmethod
    def class_method(cls):  # cls 是什么
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

    @staticmethod
    def static_method():
        print(Person.HEIGHT)

print('-' * 10, "类访问", '-' * 10)
print(1, '-->', Person.normal_method())
# print(2, '-->', Person.method())
print(3, '-->', Person.class_method())
print(4, '-->', Person.static_method())
print(Person.__dict__)

print('-' * 10, "实例访问", '-' * 10)
tom = Person()
# print(1, '-->', tom.normal_method())
print(2, '-->', tom.method())
print(3, '-->', tom.class_method())
print(4, '-->', tom.static_method())

类几乎可以调用所有内部定义的方法,但是调用 普通的方法 时会报错,原因是第一参数必须是类的实例。

实例也几乎可以调用所有的方法,普通的函数 的调用一般不可能出现,因为不允许这么定义。

总结:

类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。

实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类。

17.4.6、访问控制

17.4.6.1、私有属性

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.age = age

    def growup(self, i=1):
        if 0 < i < 150:  # 控制逻辑
            self.age += i

p1 = Person('tom')
p1.growup(20)  # 正常的范围
p1.age = 160  # 超过了范围,并绕过了控制逻辑
print(p1.age)

上例,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。

Python 提供了私有属性可以解决这个问题。

私有属性:使用 双下划线开头 的属性名,就是私有属性。

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def growup(self, i=1):
        if 0 < i < 150:
            self.__age += i

p1 = Person('tom')
p1.growup(20)
print(p1.__age)  # AttributeError: 'Person' object has no attribute '__age'

通过实验可以看出,外部已经访问不到 __age 了,age 根本就没有定义,更是访问不到。

那么,如何访问这个私有变量 __age 呢?

使用方法来访问:

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def growup(self, i=1):
        if 0 < i < 150:
            self.__age += i

    def getage(self):
        return self.__age

print(Person('tom').getage())

17.4.6.2、私有变量的本质

外部访问不到,能够动态增加一个 __age 呢?

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def growup(self, i=1):
        if 0 < i < 150:
            self.__age += i

    def getage(self):
        return self.__age

p1 = Person('tom')
p1.growup(20)

p1.__age = 28
print(p1.__age)
print(p1.getage())  # 为什么年龄不一样?__age没有被覆盖吗?
print(p1.__dict__)  # 秘密在这里

秘密都在 __dict__ 中,里面是 'name': 'tom', '_Person__age': 38, '__age': 28

私有变量的本质:

类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python 解释器会将其 改名,转换名称为 _类名__变量名 的名称,所以用原来的名字访问不到了。

知道了这个名字,能否直接修改呢?

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def growup(self, i=1):
        if 0 < i < 150:
            self.__age += i

    def getage(self):
        return self.__age

p1 = Person('tom')
p1.growup(20)

p1.__age = 28
print(p1.__age)
print(p1.getage())  # 为什么年龄不一样?__age没有被覆盖吗?
print(p1.__dict__)  # 秘密在这里

# 直接修改私有变量
p1._Person__age = 15
print(p1.getage())
print(p1.__dict__)

从上例可以看出,知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它。

17.4.6.3、保护变量

在变量名前使用一个下划线,称为保护变量。

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self._age = age

tom = Person('Tom')
print(tom._age)
print(tom.__dict__)

可以看出,这个 _age 属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理。

这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用。

17.4.6.4、私有方法

参照保护变量、私有变量,使用单下划线、双下划线命名方法。

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self._age = age

    def _getname(self):
        return self.name

    def __getage(self):
        return self._age

tom = Person('Tom')
print(tom._getname())  # 没改名
# print(tom.__getage())  # 无此属性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage())

17.4.6.5、私有方法的本质

单下划线的方法只是开发者之间的约定,解释器不做任何改变。

双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__方法名

方法变量都在类的 __dict__ 中可以找到。

17.4.6.6、私有成员的总结

在 Python 中使用 _ 单下划线或者 __ 双下划线来标识一个成员被保护或者被私有化隐藏起来。

但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。

Python 中没有绝对的安全的保护成员或者私有成员。

因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,否则不要使用保护成员或者私有成员,更不要修改它们。

17.4.7、补丁

可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了。

猴子补丁( Monkey Patch):在运行时,对属性、方法、函数等进行动态替换。其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。黑魔法,慎用。

# t1.py
from t2 import Person
from t3 import get_score

def monkeypatchPerson():
    Person.get_score = get_score

monkeypatchPerson()

if __name__ == '__main__':
    print(Person().get_score())

# t2.py
class Person:
    def get_score(self):
        ret = {'English': 78, 'Chinese': 86, 'History': 82}
        return ret

# t3.py
def get_score(self):
    return dict(name=self.__class__.__name__, English=88, Chinese=90, History=85)

上例中,假设 Person 类 get_score 方法是从数据库拿数据,但是测试的时候,不方便。

使用猴子补丁,替换了 get_score 方法,返回模拟的数据。

17.4.8、属性装饰器

一般好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用 getter 读取属性和 setter 方法设置属性。

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def age(self):
        return self.__age

    def set_age(self, age):
        self.__age = age

tom = Person('Tom')
print(tom.age())
tom.set_age(20)
print(tom.age())

通过 age 和 set_age 方法操作属性。

有没有简单的方式呢?

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        self.__age = age

    @age.deleter
    def age(self):
        # del self.__age
        print('del')

tom = Person('Tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age

特别注意:使用 property 装饰器的时候这三个方法同名。

property 装饰器

  • 后面跟的函数名就是以后的属性名。它就是 getter。这个必须有,有了它至少是只读属性。

setter装饰器

  • 与属性名同名,且接收 2 个参数,第一个是 self,第二个是将要赋值的值。有了它,属性可写。

deleter装饰器

  • 可以控制是否删除属性。很少用。

property 装饰器必须在前,setter、 deleter 装饰器在后。

property 装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果。

其它的写法:

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def getage(self):
        return self.__age

    def setage(self, age):
        self.__age = age

    def delage(self):
        # del self.__age
        print('del')

    age = property(getage, setage, delage, 'age property')

tom = Person('Tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age

还可以如下:

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    age = property(lambda self: self.__age)

tom = Person('Tom')
print(tom.age)

17.4.9、对象的销毁

类中可以定义 __del__ 方法,称为析构函数(方法)。

作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。

注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。

使用 del 语句删除实例,引用计数减 1。当用计数为 0 时,会自动调用 __del__ 方法。

由于 Python 实现了垃圾回收机制,不能确定对象何时执行垃圾回收。

import time

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

    def __del__(self):
        print('delete {}'.format(self.name))

def test():
    tom = Person('tom')
    tom.__del__()
    tom.__del__()
    tom.__del__()
    tom.__del__()
    print('=========start=========')
    tom2 = tom
    tom3 = tom2
    print(1, 'del')
    del tom
    time.sleep(3)

    print(2, 'del')
    del tom2
    time.sleep(3)
    print('~~~~~~~~~~~~~')

    del tom3  # 注释一下看看效果
    time.sleep(3)
    print('=========end=========')

test()

对象销毁后,垃圾回收机制才会真正清理对象,还会在清理之前自动调用 __del__ 方法,除非你明确知道自己的目的,否则建议不要手动调用这个方法。

17.4.10、方法重载

在其他面向对象的高级语言中,都有重载的概念。

所谓重载,就是同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载。

Python 没有重载!Python 不需要重载!

Python 中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。

一个函数的定义可以实现很多种不同形式实参的调用。所以 Python 不需要方法的重载。或者说 Python 本身就实现了其它语言的重载。

17.4.11、封装

面向对象的三要素之一,封装 Encapsulation。

封装:将数据和操作组织到类中,即属性和方法。将数据隐藏起来,给使用者提供操作( 方法)。使用者通过操作就可以获取或者修改数据。

getter 和 setter:通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或私有成员。

17.5、练习

17.5.1、随机整数生成类

可以指定一批生成的个数,可以指定数值的范围。常规实现如下:

import random

# 普通类实现
class RandomGen:
    def __init__(self, start=1, stop=100, count=10):
        self.start = start
        self.stop = stop
        self.count = count

    def generate(self):
        return [random.randint(self.start, self.stop) for _ in range(self.count)]

# 作为工具类来实现,提供类方法
class RandomGen1:
    @classmethod
    def generate(cls, start=1, stop=100, count=10):
        return [random.randint(start, stop) for _ in range(count)]

使用生成器实现,如下:

import random

class RandomGenerator:
    def __init__(self, start=1, stop=100, patch=10):
        self.start = start
        self.stop = stop
        self.patch = patch
        self._gen = self._generate()

    def _generate(self):
        while True:
            yield random.randint(self.start, self.stop)

    def generate(self, count=0):
        if count <= 0:
            return [next(self._gen) for _ in range(self.patch)]
        else:
            return [next(self._gen) for _ in range(count)]

a = RandomGenerator()
print(a.generate())
print(a.generate(5))

生成器换一种写法:

import random

class RandomGenerator:
    def __init__(self, start=1, stop=100, patch=10):
        self.start = start
        self.stop = stop
        self.patch = patch
        self._gen = self._generate()

    def _generate(self):
        while True:
            yield [random.randint(self.start, self.stop) for _ in range(self.patch)]

    def generate(self, count=0):
        if count > 0:
            self.patch = count
        return next(self._gen)

a = RandomGenerator()
print(a.generate())
print(a.generate(5))
# 使用property
import random

class RandomGenerator:
    def __init__(self, start=1, stop=100, patch=10):
        self.start = start
        self.stop = stop
        self._patch = patch
        self._gen = self._generate()

    def _generate(self):
        while True:
            yield [random.randint(self.start, self.stop) for _ in range(self.patch)]

    def generate(self):
        return next(self._gen)

    @property
    def patch(self):
        return self._patch

    @patch.setter
    def patch(self, value):
        self._patch = value

a = RandomGenerator()
print(a.generate())
a.patch = 5
print(a.generate())

17.5.2、打印坐标

使用上题中的类,随机生成20个数字,两两配对形成二维坐标系的坐标,把这些坐标组织起来,并打印输出。

import random

class RandomGenerator:
    def __init__(self, start=1, stop=100, patch=10):
        self.start = start
        self.stop = stop
        self._patch = patch
        self._gen = self._generate()

    def _generate(self):
        while True:
            yield [random.randint(self.start, self.stop) for _ in range(self.patch)]

    def generate(self):
        return next(self._gen)

    @property
    def patch(self):
        return self._patch

    @patch.setter
    def patch(self, value):
        self._patch = value

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

R1 = RandomGenerator()
R1.patch = 7
R2 = RandomGenerator()
R2.patch = 7
points = [Point(x, y) for x, y in zip(R1.generate(), R2.generate())]

for p in points:
    print('{}:{}'.format(p.x, p.y))

17.5.3、车辆信息

记录车的品牌 mark、颜色 color、价格 price、 速度 speed 等特征,并实现增加车辆信息、显示全部车辆信息的功能。

class Car:
    def __init__(self, mark, speed, color, price):
        self.mark = mark
        self.speed = speed
        self.color = color
        self.price = price

class CarInfo:
    def __init__(self):
        self.info = []

    def addcar(self, car: Car):
        self.info.append(car)

    carinfo = property(lambda self: self.info)

ci = CarInfo()
car = Car('audi', 400, 'red', 100)
ci.addcar(car)

print(ci.carinfo)

17.5.4、模拟购物车购物

思路:

购物车购物,分解得到两个对象 购物车物品,一个操作 购买

购买不是购物车的行为,其实是人的行为,但是对于购物车来说就是 增加add

商品有很多种类,商品的属性多种多样,怎么解决?

购物车可以加入很多不同的商品,如何实现?

class Color:
    RED = 0
    BLUE = 1
    GREEN = 2
    GOLDEN = 3
    BLACK = 4
    OTHER = 1000

class Item:
    def __init__(self, **kwargs):
        self.__spec = kwargs

    def __repr__(self):
        return str(sorted(self.__spec.items()))

class Cart:
    def __init__(self):
        self.items = []

    def additem(self, item: Item):
        self.items.append(item)

    allitems = property(lambda self: self.items)

mycart = Cart()

myphone = Item(mark='Huawei', corlor=Color.GOLDEN, memory='4g')
mycart.additem(myphone)

mycar = Item(mark='Red Flag', color=Color.BLACK, year=2020)
mycart.additem(mycar)

print(mycart.allitems)

注意:以上代码只是一个非常简单的实现,生产环境实现购物车的增删改查,要考虑很多。

标签云