【Think Python】Python笔记(十七)类和方法

(一)面向对象的特性

  • 程序包含类和方法的定义;
  • 大部分计算以对象上的操作表示;
  • 对象通常代表现实世界的物体,方法对应现实世界中物体交互的方法;

**方法:**方法是一个与特定的类相关联的函数;

  • 方法和函数语义相同,但是有两处句法的不同:
    • 方法在一个类定义内部声明,为的是显示地与类进行关联;
    • 调用方法的语句和调用函数的语法不同;

(二)打印对象

class Time:
    """Represents the time of day.
    """
    
def print_time(time):
    print('%.2d:%.2d%.2d' % (time.hour, time.minute, time.second))
  • 下面将这个print_time函数变为Time类的一个方法:
class Time:
    def print_time(time):
        print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
        
>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 0

>>> start.print_time()
09:45:00
  • 上面的例子中,start称之为主语(subject);
  • 在方法的调用中,主语被赋值为方法的第一个参数;
  • 根据方法的约定,方法的第一个参数写作self
class Time:
    def print_time(self):
        print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
  • 下面增加另外一个方法:
class Time:
    def print_time(self):
        print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
        
    def time_to_int(self):
        return self.hour * 3600 + self.minute * 60 + self.second
    
    def increment(self, seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)

这里的increment方法是一个纯函数,而不是一个修改器,它会返回一个值,但是并不会修改原来的参数对象;

>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
  • 主语实际上也是当做一个参数进行处理;

  • is_after函数重写为方法:

# inside class Time:
	def is_after(self, other):
        return self.time_to_int() > other.time_to_int()
    
>>> end.is_after(start)
True

(三)init方法

init方法是一个特殊的方法,当对一个对象初始化的时候调用,全名是__init__;【相当于 C#中的构造函数】

# inside class Time:
	def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
  • 通常情况下,__init__方法的参数和属性的名称一样;

    • 上面的参数是可选的,当不带参数调用Time,会得到默认值:
    >>> time = Time()
    >>> time.print_time()
    00:00:00
    

(四)__str__方法

__str__方法和__init__方法类似的特殊方法,返回一个对象的字符串的表现形式:

# inside class Time:
	def __str__(self):
        return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
  • 当需要打印一个对象的时候,可以使用str方法:
>>> time = Time(9, 45)
>>> print(time)
09:45:00
  • 通常,写一个新的类,从__init__开始,使得更加容易实例化;接着__str__方法,方便调试;

(五)运算符重载

通过定义一些其他的特殊方法,可以在自定义的类上指定运算符的行为;

  • 可以为Time类定义一个叫__add__的方法,这样可以在Time,对象上使用+运算符;
# inside class Time:
	def __add__(self, other):
        seconds = self.time_to_int() + other.time_to_int()
        return int_to_time(seconds)

使用方法:

>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
  • 当在Time对象上使用+运算符,python会调用__add__

改变一个运算符的行为,是其兼容自定义的类,称之为运算符重载(Operator Overloading)

其他重载

(六)类型分发(type-based dispatch)

  • 上面的是将两个Time对象相加,如果想要将一个整数与Time对象相加;
  • 下面版本的__add__将会检查other类型,并相应调用add_time或者increment
# inside class Time
	
    def __add__(self, other):
        if isinstance(other, Time):
            return self.add_time(other)
        else:
            return self.increment(other)
        
    def add_time(self, other):
        seconds = self.time_to_int() + other.time_to_int()
        return int_to_time(seconds)
    
    def increment(self, seconds):
        seconds += self.time_to_int()
        return int_to_time(seconds)
  • 内建函数isinstance接受一个值和一个类对象,如果是这个类的实例,则返回True

像上面的,根据参数的类型,将计算任务发送给不同的方法,这个操作称之为类型分发(type-based dispatch)

>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
10:07:17
  • 但是这个加法并没有交换性,可以使用一个__radd__这个特殊的方法,这个是一个右手加法,当一个Time对象出翔在+运算符的右边时候调用这个方法:
# inside class Time:
	def __radd__(self, other):
        return self.__add__(other)

使用方法:

>>> print(1337 + start)
10:07:17

(七)多态性

类型的分发在必要的时候是非常有用的,但是并不是绝对必须的,可以编写对不同参数类型都适用的函数来避免这种情况;

  • 许多为字符串编写的函数,实际上也是适用于其他序列的;如使用histogram计算单词中每个字母出现的次数:
def histogram(s):
    d = dict()
    for c in s:
        if c not in d:
            d[c] = 1
        else:
            d[c] = d[c] + 1
    return d
  • 其实这个函数同样适用于列表、元组甚至是字典,只要s的元素是可以哈希的:
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']
>>> histogram(t)
{'bacon': 1, 'egg': 1, 'spam': 4}

适用于多种类型的函数,称之为多态函数,多态函数有助于代码的复用;

  • 如,内建函数sum对一个序列的元素进行求和,只要这个序列中的元素支持加法就可以:
>>> t1 = Time(7, 43)
>>> t2 = Time(7, 41)
>>> t3 = Time(7, 37)
>>> total = sum([t1, t2, t3])
>>> print(total)
23:01:00

(八)接口和实现

  • 面向对象编程的目的之一使得软件更加容易进行维护;
  • 实现这个目标的一个原则是:接口和实现分离
  • 对于对象,意味着,一个类提供的方法不能依赖于属性的形式;
  • 当我们设计完成一个类,可能会发现有更好的实现,如果程序其他部分使用了这个类,再来改变接口需要很多时间,而且容易出错;
  • 但是,如果细心设计好接口,可以改变实现,而接口不变,这样程序的其他部分都不用改变;

(九)调试

  • 在python中,在程序执行的任何时间,为一个对象添加属性都是合法的;
  • 但是,如果相同类型的对象有不同的属性,容易出现错误;
  • 通常一个好的办法是在init方法中初始化一个对象的所有属性;
  • 当不确定一个对象是不是有某个属性,可以使用内建函数hasattr,这样可以检查,这个类中是不是包含这个属性;
  • 可以使用内建函数vars访问对象属性;这个函数接受一个对象,并返回一个将属性名称到对应值的字典:
>>> p = Point(3, 4)
>>> vars(p)
{'y':4, 'x':3}
  • 可以定义下面的代码。用于调试:
def print_attribute(obj):
    for attr in var(obj):
        print(attr, getattr(obj, attr))
  1. print_attributes遍历一个对象的字典,然后打印每个属性的名称和对应的值;
  2. 内建函数 getattr 接受一个对象和一个属性名称(字符串)作为参数,然后返回该属性的值;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值