疯狂Python讲义学习笔记(含习题)之 类和对象

Python支持面向对象的三大特征:封装、继承和多态。

一、类和对象

可以把类当成一种自定义类型,可以使用类来定义变量,也可以使用类来创建对象。

(一)定义类

类是某一批对象的抽象,可以把类理解成某种概念;

对象是一个具体存在的实体。

语法:

class 类名:
    执行语句...
    零个到多个变量...
    零个到多个方法...

Python的类定义由类头(指class关键字和类名部分)和统一缩进的类体构成,在类体中最主要的两个成员就是类变量和方法。

类变量属于类本身,用于定义该类本身所包含的状态数据;

实例变量属于该类的对象,用于定义对象所包含的状态数据;

方法用于定义该类的对象的行为或功能实现。

Python是一门动态语言,类所包含的类变量可以动态增加或删除。实例变量同样。

在类中定义的方法默认是实例方法,定义实例方法的方法与定义函数相同,只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例)

__init__()方法被称为构造方法,用于构造该类的对象,Python通过调用构造方法返回该类的对象(无须使用new)

Python也允许为类定义说明文档,该文档同样被放在类声明之后、类体之前。

class Person:
    '这是学习Python定义的一个Person类'
    # 下面定义了一个类变量
    hair = 'black'
​
    def __init__(self, name='Charlie', age=8):
        # 下面为Person对象增加两个实例变量
        self.name = name
        self.age = age
​
    # 下面定义了一个say方法
    def say(self, content):
        print(content)

创建对象的根本途径是构造方法,Python无需使用new调用构造方法。

# 调用Person类的构造方法,返回一个Person对象
# 将该Person对象赋值给p变量
p = Person()

对象访问方法或变量的语法是:对象.变量|方法(参数)。

# 输出p的name、age实例变量
print(p.name, p.age)    # Charlie  8
# 访问p的name实例变量,直接为该实例变量赋值
p.name = '李刚'
# 调用p的say()方法,在声明say()方法时定义了两个形参
# 第一个形参(self)是自动绑定的,调用该方法只需为第二个形参指定一个值
p.say('Python语言是世界上最好的语言!')
# 再次输出p的name、age实例变量
print(p.name, p.age)   # 李刚 8

可以动态的为p对象增加实例变量,也可以动态删除实例变量:

# 为p对象增加一个skills实例变量
p.skills = ['programming', 'swinmming']
print(p.skills)
# 删除p对象的name实例变量
del p.name
# 再次访问p的name实例变量
print(p.name)    # AttributeError

为对象动态增加的方法,Python不会自动将调用者自动绑定到第一个参数(即使将第一个参数命名为self也没用)。

# 先定义一个函数
def info(self):
    print("----info函数----", self)
# 使用info对p的foo方法赋值(动态增加方法)
p.foo = info
# Python不会自动将调用者绑定到第一个参数
# 因此程序需要手动将调用者绑定到第一个参数
p.foo(p)
​
# 使用lambda表达式为p对象的bar方法赋值(动态增加方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p)

如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于types模块下的MethodType进行包装。

def intro_func(self, content):
    pring("我是一个人,信息为:%s" % content)
# 导入methodType
from types import MethodType
# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
p.intro = MethodType(intro_func, p)
# 第一个参数已经绑定,无需传入
p.intro("生活在别处")

python的类、对象有点类似于一个命名空间,因此在调用类、对象的方法时,一定要加上“类.”或“对象.”的形式。如果直接调用某个方法,属于调用函数。

当self参数作为对象的默认引用时,程序可以像访问普通变量一样来访问这个self参数,甚至可以把self参数当成实例方法的返回值。

class ReturnSelf:
    def grow(self):
        if hasattr(self, 'age'):
            self.age += 1
        else:
            self.age = 1
        # return self返回调用该方法的对象
        return self
​
​
rs = ReturnSelf()
# 可以连续调用同一个方法
rs.grow().grow().grow()
print("rs的age属性值是:", rs.age)

使用self参数作为方法的返回值可以让代码更加简洁,但可能造成实际意义的模糊。

 

二、方法

方法是类或对象的行为特征的抽象。

Python的类可以调用实例方法,但使用类调用实例方法时,Python不会自动为方法的第一个参数self绑定参数值;程序必须显式地为第一个参数self传入方法调用者。这种调用方式被称为“未绑定方法”

使用@classmethod修饰的方法就是类方法;使用@staticmethod修饰的方法就是静态方法。

Python会自动绑定类方法的第一个参数(通常建议参数名为cls)也就是类本身;但对于静态方法则不会自动绑定。

使用@符号引用有的函数(比如@staticmethod、@classmethod)后,可以用于修饰其他函数。

当程序使用“@函数”(比如函数A)装饰另一个函数(比如函数B)时,实际上完成如下两步:

① 将被修饰的函数(函数B)作为参数传给@符号引用的函数(函数A)

② 将函数B替换(装饰)成第一步的返回值。

def foo(fn):
    # 定义一个嵌套函数
    def bar(*args):
        print("===1===", args)
        n = args[0]
        print("===2===", n * (n - 1))
        # 查看传给foo函数的fn函数
        print(fn.__name__)
        fn(n * (n - 1))
        print("*" * 15)
        return fn(n * (n - 1))
    return bar
​
​
'''
下面的装饰效果相当于foo(my_test)
my_test将会被替换(装饰)成该语句的返回值
由于foo()函数返回bar函数,因此funB就是bar
'''
@foo
def my_test(a):
    print("===my_test函数==", a)
​
​
# 打印my_test函数,将看到实际上是bar函数
print(my_test)    #<function foo.<locals>.bar at 0x000001F974C11378>
# 下面代码看上去是调用my_test(),其实是调用bar()函数
my_test(10)
my_test(6, 5)

输出结果:

<function foo.<locals>.bar at 0x000001F974C11378>
===1=== (10,)
===2=== 90
my_test
===my_test函数== 90
***************
===my_test函数== 90
===1=== (6, 5)
===2=== 30
my_test
===my_test函数== 30
***************
===my_test函数== 30

这种在被修饰函数之前、之后、抛出异常后增加某种处理逻辑的方式,叫做AOP(Aspect Orient Programming,面向切面编程)

Python程序默认出于全局命名空间内,类体则出于类命名空间内,Python允许在全局范围内放置可执行代码——当Python执行该程序时,这些代码就会获得执行的机会。

 

三、成员变量

在类体内定义的变量,默认属于类本身。在类命名空间内定义的变量就属于类变脸,Python可以使用类来读取、修改类变量。

class Address:
    detail = '广州'
    post_code = '510660'
​
    def info(self):
        # 尝试直接访问类变量
        # print(detail)    # 报错
        # 通过类来访问类变量
        print(Address.detail)
        print(Address.post_code)
​
​
# 通过类来访问Address类的类变量
print(Address.detail)    # 输出 广州
addr = Address()
addr.info()
# 修改Address类的类变量
Address.detail = '佛山'
Address.post_code = '460110'
addr.info()
​

程序必须使用类名来调用类变量。不管是在全局范围内还是函数内访问这些类变量,都必须使用类名进行访问。

Python完全允许使用对象来访问该对象所属类的类变量(当然还是推荐使用类访问类变量)。虽然Python允许通过对象访问类变量,但如果程序通过对象尝试对类变量赋值,其实不是对“类变量赋值”,而是定义新的实例变量。

 

如果为Python类定义了getter、setter等访问器方法,则可使用property()函数将它们定义成属性(相当于实例变量)。

property()函数的语法格式如下:

property(fget=None, fset=None, fdel=None, doc=None)

四个参数分别代表getter方法、setter方法、del方法和doc,其中doc是一个文档字符串,用于说明属性。

class Rectangle:
    # 定义构造方法
    def __init__(self, width, height):
        self.width = width
        self.height = height
​
    # 定义setsize()函数
    def setsize(self, size):
        self.width, self.height = size
​
    # 定义getsize()函数
    def getsize(self):
        return self.width, self.height
​
    # 定义delsize()函数
    def delsize(self):
        self.width, self.height = 0, 0
​
    # 使用property定义属性
    size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')
​
​
# 访问size属性的说明文档
print(Rectangle.size.__doc__)
# 通过内置的help()函数查看Rectangle.size的说明文档
help(Rectangle.size)
rect = Rectangle(4, 3)
# 访问rect的size属性
print(rect.size)    # (4, 3)
# 对rect的size属性赋值
rect.size = 9, 7
# 访问rect的width、height实例变量
print(rect.width)    # 9
print(rect.height)    # 7
# 删除rect的size属性
del rect.size
# 访问rect的width、height实例变量
print(rect.width)    # 0
print(rect.height)    # 0

还可以使用@property装饰器来修饰方法,是之成为属性。

class Cell:
    # 使用@property装饰方法,相当于为该属性设置getter方法
    @property
    def state(self):
        return self._state
​
    # 为state属性设置setter方法
    @state.setter
    def state(self, value):
        if 'alive' in value.lower():
            self._state = 'alive'
        else:
            self._state = 'dead'
​
    # 为is_dead属性设置getter方法
    # 只有getter方法的属性是只读属性
    @property
    def is_dead(self):
        return not self._state.lower() == 'alive'
​
​
c = Cell()
# 修改state属性
c.state = 'Alive'
# 访问state属性
print(c.state)
# 访问is_dead属性
print(c.is_dead)

四、隐藏和封装

封装(Encapsulation)是面向对象的三大特征之一(另外两个是继承和多态),它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

对一个类或对象实现良好的封装,可以达到以下目的:

● 隐藏类的实现细节

● 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对属性的不合理访问。

● 可以进行数据检查,从而有利于保证对象信息的完整性

● 便于修改,提高代码的可维护性

为了实现良好的封装,需要从两个方面来考虑:

● 将对象的属性和实现细节隐藏起来,不允许外部直接访问

● 把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作

※ 把该隐藏的隐藏起来,把该暴露的暴露出来

Python并没有提供类似于其他语言的private等修饰符,因此python并不能真正支持隐藏。Python玩了一个小技巧:只要将Python类的成员命名为以双下划线开头的,Python就会把他们隐藏起来

class User:
    def __hide(self):
        print('示范隐藏的hide方法')
​
    def getname(self):
        return self.__name
​
    def setname(self, name):
        if len(name) < 3 or len(name) > 8:
            raise ValueError('用户名长度必须在3~8之间')
        self.__name = name
​
    name = property(getname, setname)
​
    def setage(self, age):
        if age < 18 or age > 70:
            raise ValueError('用户年龄必须在18~70之间')
        self.__age = age
​
    def getage(self):
        return self.__age
​
    age = property(getage, setage)
​
​
# 创建User对象
u = User()
# 对name属性赋值,实际上调用setname()方法
# u.name = 'fk'    # 引发ValueError错误:用户名长度必须在3~8之间
u.name = 'fkit'
u.age = 25
print(u.name)    # fkit
print(u.age)    # 25
# u.__hide()    # AttributeError: 'User' object has not attribute '__hide'
# 调用隐藏的__hide()方法
u._User__hide()

Python会“偷偷”地改变以双下划线开头的方法名,会在这些方法名前添加单下划线和类名。

Python并没有真正的隐藏机制,所有Python类定义的所有成员默认都是公开的。

 

五、类的继承

继承是面向对象的三大特征之一,也是实现软件复用的重要手段。Python的继承是多继承机制,即一个子类可以同时又多个直接父类。

Python子类继承父类的语法是在定义子类时,将多个父类放在子类之后的圆括号里。语法格式如下:

class SubClass(SuperClass1, SuperClass2, ...):
    # 类定义部分

object类是所有类的父类,要么是其直接父类,要么是其间接父类。

实现继承的类被成为子类,被继承的类称为父类,也被成为基类、超类。

class Fruit:
    def info(self):
        print('我是一个水果!重%g克' % self.weight)
​
​
class Food:
    def taste(self):
        print('不同食物的口感不同')
​
​
# 定义Apple类,继承了Fruit类和Food类
class Apple(Fruit, Food):
    pass
​
​
# 创建Apple对象
a = Apple()
a.weight = 5.6
# 调用Apple对象的info()方法
a.info()
# 调用Apple对象的taste()方法
a.taste()

Python虽然在语法上支持多继承,但通常推荐:如果不是很有必要,则尽量不要使用多继承,而是使用单继承,这样可以保证编程思路更清晰,而且可以避免很多麻烦。

如果多个父类中包含了同名的方法,排在前面的父类中的方法会“遮蔽”排在后面的父类中的同名方法。

class Item:
    def info(self):
        print('Item中方法:', '这是一个商品')
​
​
class Product:
    def info(self):
        print('Product中的方法:', '这是一个工业产品')
​
​
class Mouse(Item, Product):
    pass
​
​
m = Mouse()
m.info()    # Item中方法: 这是一个商品

上面程序中Item和Product两个父类中都包含了info()方法,但Mouse子类对象调用info()方法时——子类中没有定义info()方法,因此Python会从父类中寻找info()方法,此时优先使用第一个父类Item中的info()方法。

如果子类中定义了与父类同名的方法,则子类中的方法会覆盖父类中的方法。

通过使用未绑定方法即可在子类中再次调用父类中被重写(覆盖)的方法。

class BaseClass:
    def foo(self):
        print('父类中定义的foo方法')
​
​
class SubClass(BaseClass):
    # 重写父类的foo方法
    def foo(self):
        print('子类重写父类中的foo方法')
​
    def bar(self):
        print('执行bar方法')
        # 直接执行foo方法,将会调用子类重写之后的foo方法
        self.foo()
        # 使用类名调用实例方法(未绑定方法),调用父类被重写的foo方法
        BaseClass.foo(self)
​
​
sc = SubClass()
sc.bar()
​

python要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。子类的构造方法调用父类的构造方法有两种形式:

● 使用未绑定方法

● 使用super()函数调用父类的构造方法。

super()函数的help信息如下:

class super(object)
 |  super() -> same as super(__class__, <first argument>)
 |  super(type) -> unbound super object
 |  super(type, obj) -> bound super object; requires isinstance(obj, type)
 |  super(type, type2) -> bound super object; requires issubclass(type2, type)
 |  Typical use to call a cooperative superclass method:
 |  class C(B):
 |      def meth(self, arg):
 |          super().meth(arg)
 |  This works for class methods too:
 |  class C(B):
 |      @classmethod
 |      def cmeth(cls, arg):
 |          super().cmeth(arg)
 |  
 |  Methods defined here:
 |  
 |  __get__(self, instance, owner, /)
 |      Return an attribute of instance, which is of type owner.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __self__
 |      the instance invoking super(); may be None
 |  
 |  __self_class__
 |      the type of the instance invoking super(); may be None
 |  
 |  __thisclass__
 |      the class invoking super()

六、Python的动态性

动态语言的典型特征就是:类、对象的属性、方法都可以动态增加和修改。

如果程序要限制为某个类动态添加属性和方法,可以通过__slots__属性来指定。

__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名。

__slots__属性指定的限制只对当前类的实例起作用,对该类派生出来的子类是不起作用的。

如果要限制子类的实例动态添加属性和方法,则需要在子类中也定义__slots__属性,这样,子类的实例允许动态添加属性和方法就是子类的__slots__元组加上父类__slots__元组的和。

 

程序使用class定义的所有类都是type类的实例。

使用type()定义类时可指定三个参数:

● 创建的类名

● 该类继承的父类集合。由于python支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)

● 该字典对象为该类绑定的类变量和方法。其中字典的key就是类变量或方法名,如果字典的value是普通纸,那就代表类变量,如果value是函数,则代表方法。

def fn(self):
    print('fn函数')
​
​
# 使用type()定义Dog类
Dog = type('Dog', (object, ), dict(walk=fn, age=6))
# 创建Dog对象
d = Dog()
# 分别查看d、Dog的类型
print(type(d))
print(type(Dog))
d.walk()
print(Dog.age)
​

输出结果:

<class '__main__.Dog'>
<class 'type'>
fn函数
6

如果希望创建某一批类全部具有某种特征,则可以通过metaclass来实现。使用metaclass可以在创建类时动态修改类定义。

metaclass应该继承type类,并重写__new__()方法。

# 定义ItemMetaClass,继承type
class ItemMetaClass(type):
    # cls代表被动态修改的类
    # name代表被动态修改的类名
    # bases代表诶动态修改的类的所有父类
    # attr代表被动态修改的类的所有属性、方法组成的字典
    def __new__(cls, name, bases, attrs):
        # 为该类动态加添一个cal_price方法
        attrs['cal_price'] = lambda self: self.price * self.discount
        return type.__new__(cls, name, bases, attrs)

metaclass类的__new__方法的作用是:当程序使用class定义新类时,如果指定了metaclass,那么metaclass的__new__方法就会被自动执行。

# 定义Book类
class Book(metaclass=ItemMetaClass):
    __slots__ = ('name', 'price', '_discount')
​
    def __init__(self, name, price):
        self.name = name
        self.price = price
​
    @property
    def discount(self):
        return self._discount
​
    @discount.setter
    def discount(self, discount):
        self._discount = discount
​
​
# 定义CellPhone类
class CellPhone(metaclass=ItemMetaClass):
    __slots__ = ('price', '_discount')
​
    def __init__(self, price):
        self.price = price
​
    @property
    def discount(self):
        return self._discount
​
    @discount.setter
    def discount(self, discount):
        self._discount = discount

ItemMetaClass类的__new__方法会自动为目标类动态添加cal_price方法。

b = Book('疯狂python讲义', 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())
cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())

运行结果:

67.64
2039.1499999999999

通过使用metaclass可以动态修改城中的一批类,对它们集中进行某种修改。这个功能在开发一些基础性框架时非常有用,程序可以通过使用metaclass为某一批需要具有通用功能的类添加方法。

 

七、多态

当同一个比变量在调用同一个方法时,完全可能呈现出多种行为(具体呈现出哪种行为由该变量引用的对象来决定),这就是所谓的多态(Polymorphism)

Python提供了如下两个函数来检查类型:

● issubclass(cls, class_or_tuple):检查cls是否为后一个类或元组包含的多个类中任意类的子类。

● isinstance(obj, class_or_tuple):检查obj是否为后一个类或元组包含的多个类中任意类的对象。

Python的所有类都提供了一个__bases__属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组。

Python还为所有类都提供了一个__subclasses__()方法,通过该方法可以查看该类的所有直接子类,该方法返回该类的所有子类组成的列表。

 

八、枚举类

实例有限且固定的类,在Python中被称为枚举类

程序有两种方式来定义枚举类

● 直接使用Enum列出多个枚举值来创建枚举类。

● 通过继承Enum基类来派生枚举类

Python为枚举类提供了一个__members__属性,该属性返回一个dict字典,字典包含了该枚举的所有枚举实例。程序可通过遍历__members__属性来访问枚举的所有实例。

 

 

习题:

1. 编写一个学生类,提供name 、age 、gender 、phone 、address 、email 等属性,为学生类提供带所有成员变量的构造器,为学生类提供方法,用于描绘吃、喝、玩、睡等行为。

class Student:
    # 定义构造器,接收name、age、gender、phone、address、email参数
    def __init__(self, name, age, gender, phone, address, email):
        self.name = name
        self.age = age
        self.gender = gender
        self.phone = phone
        self.address = address
        self.email = email

    # 定义方法吃
    def eat(self):
        print('%s正在吃东西。' % self.name)

    # 定义喝方法
    def drink(self):
        print('%s正在喝东西' % self.name)

    # 定义玩方法
    def play(self):
        print('%s正在玩!' % self.name)

    # 定义睡方法
    def sleep(self):
        print('%s睡觉了!' % self.name)
​
​
if __name__ == '__main__':
    s = Student('张三', 20, '男', '13312345678', '中国·重庆市渝北区二支路街道180号', 'zhangsan@163.com')
    print('姓名:%s,年龄:%d,性别:%s,电话号码:%s,地址:%s,邮箱:%s' % (
        s.name, s.age, s.gender, s.phone, s.address, s.email
    ))
    s.eat()
    s.drink()
    s.play()
    s.sleep()

2 . 利用第1题定义的Student 类,定义一个列表保存多个Student 对象作为通讯录数据。程序可通过name 、email 、address 查询,如果找不到数据,则进行友好提示。

def find_for_name(student_lst, name):
    result = []
    for student in student_lst:
        if student.name == name:
            result.append(student)
    if result:
        print('找到姓名为%s的学生%d个' % (name, len(result)))
    else:
        print('没有找到姓名为%s的学生' % name)
​
​
def find_for_email(student_lst, email):
    result = []
    for student in student_lst:
        if student.email == email:
            result.append(student)
    if result:
        print('找到邮箱为%s的学生%d个' % (email, len(result)))
    else:
        print('没有找到邮箱为%s的学生' % email)
​
​
def find_for_address(student_lst, address):
    result = []
    for student in student_lst:
        if student.address == address:
            result.append(student)
    if result:
        print('找到邮箱为%s的学生%d个' % (address, len(result)))
    else:
        print('没有找到邮箱为%s的学生' % address)
​
​
s1 = Student(
    '张三',
    20,
    '男',
    '13312345678',
    '中国·重庆市渝北区二支路街道180号',
    'zhangsan@163.com')
s2 = Student(
    '李四',
    21,
    '男',
    '13312348970',
    '中国·重庆市渝北区二支路街道181号',
    'lisi@163.com')
s3 = Student(
    '王麻子',
    33,
    '男',
    '13312343456',
    '中国·重庆市渝北区二支路街道182号',
    'wangmazi@163.com')
student_lst = [s1, s2, s3]
find_for_name(student_lst, '张三')
find_for_name(student_lst, '王二小')
find_for_email(student_lst, 'lisi@163.com')
find_for_email(student_lst, 'xxx@xxx.com')
find_for_address(student_lst, '中国·重庆市渝北区二支路街道182号')
find_for_address(student_lst, '中国·重庆市渝北区二支路街道183号')

定义代表二维坐标系上某个点的Point 类(包括x 、y 两个属性),为该类提供一个方法用于计算两个Point 之间的距离, 再提供一个方法用于判断三个Point 组成的三角形是钝角、锐角还是直角三角形。

class Point:
    # 构造函数,接收x,y坐标作为参数
    def __init__(self, x, y):
        self.x = x
        self.y = y
​
    # 计算两个点之间的距离,接收一个Point实例对象作为参数
    def distance(self, other):
        x_distance = (self.x - other.x) ** 2
        y_distance = (self.y - other.y) ** 2
        return (x_distance + y_distance) ** 0.5
​
    # 判断三个点组成的三角形是锐角、钝角还是直角三角形
    def judge_triangle(self, other, another):
        # 计算三条边的长度
        side1 = self.distance(other)
        side2 = self.distance(another)
        side3 = other.distance(another)
        # 找出最长的边
        max_side = max(side1, side2, side3)
        if max_side == side1:
            # 先判断能否构成三角形
            if (side2 + side3) < side1:
                print('三边长度为%s,%s,%s,最长边为:%s,这不是一个三角形' % (side1, side2, side3, side1))
            else:
                # 计算side2的平方与side3的平方和,并判断其是否大于side1的平方
                if (side2 ** 2 + side3 ** 2) > (side1 ** 2):
                    print('这是一个锐角三角形')
                elif (side2 ** 2 + side3 ** 2) < (side1 ** 2):
                    print('这是一个钝角三角形')
                elif (side2 ** 2 + side3 ** 2) == (side1 ** 2):
                    print('这是一个直角三角形')
        elif max_side == side2:
            # 先判断能否构成三角形
            if (side1 + side3) < side2:
                print('三边长度为%s,%s,%s,最长边为:%s,这不是一个三角形' % (side1, side2, side3, side2))
            else:
                # 计算side2的平方与side3的平方和,并判断其是否大于side1的平方
                if (side1 ** 2 + side3 ** 2) > (side2 ** 2):
                    print('这是一个锐角三角形')
                elif (side1 ** 2 + side3 ** 2) < (side2 ** 2):
                    print('这是一个钝角三角形')
                elif (side1 ** 2 + side3 ** 2) == (side2 ** 2):
                    print('这是一个直角三角形')
        elif max_side == side3:
            # 先判断能否构成三角形
            if (side1 + side2) < side3:
                print('三边长度为%s,%s,%s,最长边为:%s,这不是一个三角形' % (side1, side2, side3, side3))
            else:
                # 计算side2的平方与side3的平方和,并判断其是否大于side1的平方
                if (side1 ** 2 + side2 ** 2) > (side3 ** 2):
                    print('这是一个锐角三角形')
                elif (side1 ** 2 + side2 ** 2) < (side3 ** 2):
                    print('这是一个钝角三角形')
                elif (side1 ** 2 + side2 ** 2) == (side3 ** 2):
                    print('这是一个直角三角形')
​
    def __repr__(self):
        return 'P(%s,%s)' % (self.x, self.y)
​
​
if __name__ == '__main__':
    p1 = Point(0, 0)
    p2 = Point(3, 3)
    p3 = Point(0, 12)
    p4 = Point(5, 5)
    print('%s到%s的距离为:%s' % (p1, p2, p1.distance(p2)))
    print('%s到%s的距离为:%s' % (p1, p3, p1.distance(p3)))
    print('%s到%s的距离为:%s' % (p2, p3, p2.distance(p3)))
    print('{},{},{},组成的三角形是:'.format(p1, p2, p3), end='')
    p1.judge_triangle(p2, p3)

4. 定义代表三维笛卡尔坐标系上某个点的Point 类(包括x 、y 、z 三个属性),为该类定义一个方法,可接收b 、c 、d 三个参数,用于计算当前点、b 、c 组成的面与b 、c 、d 组成的面之间的夹角。提示: cos (夹角) = (X·Y)/|X||Y|,其中X=AB×BC,Y=BC×CD,X·Y代表X与Y的点积,AB×BC代表AB与BC的叉乘。

import math
​
​
class Point:
    # 定义构造函数
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
​
    # 重写方法__sub__为-操作符提供支持
    def __sub__(self, other):
        return Point((self.x - other.x),
                     (self.y - other.y),
                     (self.z - other.z))
​
    # 定义方法dot,计算点积
    def dot(self, other):
        return Point((self.x * other.x),
                     (self.y * other.y),
                     (self.z * other.z))
​
    # 定义方法cross,计算叉积
    def cross(self, other):
        return Point((self.y * other.z - self.z * other.y),
        (self.z * other.x - self.x * other.z),
        (self.x * other.y - self.y * other.x))
​
    def absolute(self):
        return pow((self.x ** 2 + self.y ** 2 + self.z ** 2), 0.5)
​
    # 定义方法接收b,c,d三个参数,计算当前点、b 、c 组成的面与b 、c 、d 组成的面之间的夹角
    def angle(self, b, c, d):
        X = (b - self).cross(c - b)
        Y = (c - b).cross(d - c)
        return math.acos(X.dot(Y) / (X.absolute() * Y.absolute()))
​
​
if __name__ == '__main__':
    points = list()
    print('请依次输入4个点的x y z(中间以空格隔开)')
    for i in range(4):
        a = list(map(float, input().split()))
        points.append(a)
​
    a, b, c, d = Point(*points[0]), Point(*points[1]), Point(*points[2]), Point(*points[3])
    angle = a.angle(b, c, d)
    print("%.2f" % math.degrees(angle))

5. 定义交通工具、汽车、火车、飞机这些类,注意它们的继承关系,为这些类提供构造器。

class Vehicle:
    # 定义构造方法
    def __init__(self, name, speed):
        self.name = name
        self.speed = speed
​
​
class Car(Vehicle):
    # 定义构造方法
    def __init__(self, name, speed):
        super().__init__(name, speed)
​
​
class Train(Vehicle):
    # 定义构造方法
    def __init__(self, name, speed):
        super().__init__(name, speed)
​
​
class Airplane(Vehicle):
    # 定义构造方法
    def __init__(self, name, speed):
        super().__init__(name, speed)

 

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Python类和对象的练习题主要是通过编写方法来实现一些具体的功能。引用中给出了三个例子,分别是定义Point并生成对象,实现两数之和的查找,以及实现一个函数来返回一个列表中最小的k个数。这些练习题可以帮助Python初学者更好地理解类和对象的概念,并提高编程能力。 1. 定义Point并生成对象:这个练习题主要是让学生理解如何定义类和对象,并且掌握的属性和方法的使用。在这个例子中,我们定义一个Point,它有两个属性x和y,以及一个方法saveInfo(),用于保存坐标信息。然后我们生成三个Point对象,并将它们放入一个列表中,最后打印出每个对象的属性值。 2. 实现两数之和的查找:这个练习题主要是让学生掌握字典的使用,以及如何在一个数组中查找两个数的和等于目标值。在这个例子中,我们定义一个Solution,它有一个方法twoSum(),用于查找两个数的和等于目标值。我们首先将数组中的数字作为key,下标作为value存到一个字典中,然后遍历数组,查找另外一个数字是否在字典中,如果在就可以直接返回value了。 3. 实现一个函数来返回一个列表中最小的k个数:这个练习题主要是让学生掌握列表的使用,以及如何实现一个函数来返回一个列表中最小的k个数。在这个例子中,我们定义一个Solution,它有一个方法mostCompetitive(),用于返回一个列表中最小的k个数。我们首先遍历列表,如果当前数字比下一个数字大,就将当前数字从列表中删除,然后继续遍历。最后返回列表中前k个数字即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值