Python笔记——类与对象

面向对象的程序设计具有封装、继承、多态3个基本特征,可以大大增加程序可靠性、代码可重用行和程序可维护性,从而提高程序的开发效率。

类对象与实例对象

类对象

# 类对象
class 类名:
    类体
class Person1:
    pass
# 测试代码
p1 = Person1()
print(Person1, type(Person1), id(Person1))
print(p1, type(p1), id(p1))

实例对象

# 实例对象
anObject = 类名(参数列表)
anObject.对象函数 或 anObject.对象属性

Python创建实例对象的方法无需使用关键字new,而是直接调用类对象并传递参数。因此,类对象是可调用对象

Python内置函数中,可调用内置类对象bool / int / str / list / dict / set

# 实例对象的创建实例
c1 = complex(1, 2)
c1.conjugate()
c1.real

# output
(1-2j)
1.0

属性

Python变量不需要声明,可直接使用。

实例对象属性

通过“self.变量名”定义的属性称为实例对象属性,也称为实例对象变量。类的每个实例都包含了该类的实例对象变量的一个单独副本,实例对象变量属于特定的实例。实例对象变量在类的内部通过self访问在外部通过对象实例访问

# 实例对象属性一般在__init__()方法中初始化
self.实例变量名 = 初始值
# 在其他实例函数中通过self访问
self.实例变量名 = 值
# 创建对象实例后通过对象实例访问
obj1 = 类名()
obj1.实例变量名 = 值
obj1.实例变量名
class Person2:
    def __init__(self, name, age): # 初始化__init__()方法
        self.name = name
        self.age = age
    def say_hi(self):
        print('您好,我叫', self.name)
        
# 测试代码
p1 = Person2('张三', 25)
p1.say_hi()
print(p1.age)

类对象属性

class Person3:
    count = 0
    name = "Person"

类属性如果通过obj.属性名来访问,则属于实例的实例属性。虽然类属性可以使用对象实例来访问,但容易造成困惑,访问类属性建议使用标准的访问方式"类名.变量名"

私有属性和共有属性

Python类的成员没有范文控制限制。

# 私有属性:以两下划线开头,但不以两下划线结束
class A:
    __name = 'class A'
    def get_name():
        print(A.__name)
    
A.get_name()
A.__name # 不可访问私有类属性

@property装饰器

面向对象编程的封装性原则要求不直接访问类中的数据成员。在Python中可以定义私有属性,然后定义相应的访问该私有属性的函数,并使用@property装饰器来装饰这些函数。程序可以把函数“当作”属性访问,从而提供更加友好的访问方式。

class Person11:
    def __init__(self, name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    
# 测试代码
p = Person11('Estellasy')
print(p.name)
class Person12:
    def __init__(self, name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, value):
        self.__name = value
    @name.deleter
    def name(self):
        del self.__name
#测试代码
p = Person12('姚六')
p.name = '王依依'
print(p.name)

# property的调用格式如下
property(fget=None, fset=None, fdel=None, doc=None)
# 其中,fget为get访问器;fset为set访问器;fdel为del访问器

特殊属性

以双下划线开始和结束的属性

自定义属性

@Override

方法

对象实例方法

在一般情况下,类方法的第一个参数一般为self,这种方法称为对象实例方法。

def 方法名(self, [形参列表]):
    函数体
    
对象.方法名([实参列表])
class Person4:            #定义类Person4
    def say_hi(self, name): #定义方法say_hi
        self.name = name #把参数name赋值给self.name,即成员变量name(域)
        print('您好, 我叫', self.name)
p4 = Person4()    #创建对象实例
p4.say_hi('Alice')  #调用对象实例的方法

静态方法

与类的对象实例无关的方法,称之为静态方法。

静态方法不对特定实例进行操作,在静态方法中访问对象实例会导致错误。静态方法通过装饰器@staticmethod来定义,其声明格式如下。

@staticmetho
def 静态方法名([形参列表]):
    函数体

一般通过类名来访问,也可以通过对象实例来调用。

class TemperatureConverter:
    @staticmethod 
    def c2f(t_c): #摄氏温度到华氏温度的转换
            t_c = float(t_c)
            t_f = (t_c * 9/5) + 32
            return t_f
    @staticmethod 
    def f2c(t_f): #华氏温度到摄氏温度的转换
            t_f = float(t_f)
            t_c = (t_f - 32) * 5 /9
            return t_c
        
#测试代码
print("1. 从摄氏温度到华氏温度.")
print("2. 从华氏温度到摄氏温度.")
choice = int(input("请选择转换方向:"))
if choice == 1:
    t_c = float(input("请输入摄氏温度: "))
    t_f = TemperatureConverter.c2f(t_c)
    print("华氏温度为: {0:.2f}".format(t_f))
elif choice == 2:
    t_f = float(input("请输入华氏温度: "))
    t_c = TemperatureConverter.f2c(t_f)
    print("摄氏温度为: {0:.2f}".format(t_c))
else:
    print("无此选项,只能选择1或2!")

类方法

Python也允许声明属于类本身的方法,即类方法。类方法不对特定实例进行操作,在类方法中访问对象实例属性会导致错误。类方法通过装饰器@classmethod来定义,第一个形式参数必须为类对象本身,通常为cls。

@classmethod
def 类方法名(cls, [形参列表]):
    函数体
# 类方法一般通过类名来访问,也可以通过对象实例来调用。
类名.类方法名([实参列表])
class Foo:
    classname = "Foo"
    def __init__(delf, name):
        self.name = name
    def f1(self):
        print(self.name)
    @staticmethod
    def f2():
        print("static")
    @classmethod
    def f3():
        print(cls.classname)
        
#测试代码
f = Foo("李")
f.f1()
Foo.f2()
Foo.f3()

在Python类体中可以定义特殊的方法,例如__new__()方法和__init__()方法。
__new__()方法是一个类方法,在创建对象时调用,返回当前对象的一个实例,一般无须重载该方法。
__init__()方法即构造函数(构造方法),用于执行类的实例的初始化工作。在创建完对象后调用,初始化当前对象的实例,无返回值。

class Person5:             #定义类Person5
    def __init__(self, name): #__init__方法
        self.name = name #把参数name赋值给self.name,即成员变量name(域)
    def say_hi(self):     #定义类Person的方法say_hi
        print('您好, 我叫', self.name)
p5 = Person5('Helen')    #创建对象
p5.say_hi()            #调用对象的方法

__del__方法即析构函数(析构方法),用于实现销毁类的实例所需的操作,如释放对象占用的非托管资源(例如:打开的文件、网络连接等)
默认情况下,当对象不再被使用时,__del__方法运行,由于Python解释器实现自动垃圾回收,即无法保证这个方法究竟在什么时候运行
通过del语句,可以强制销毁一个对象实例,从而保证调用对象实例的__del__方法。

class Person3: 
    count = 0                #定义类域count,表示计数
    def __init__(self, name,age): #构造函数
        self.name = name #把参数name赋值给self.name,即成员变量name(域)
        self.age = age    #把参数age赋值给self.age,即成员变量age(域)
        Person3.count += 1   #创建一个实例时,计数加1
    def __del__(self):        #析构函数
        Person3.count -= 1   #销毁一个实例时,计数减1
    def say_hi(self):        #定义类Person3的方法say_hi()
        print('您好, 我叫', self.name)
    def get_count():        #定义类Person3的方法get_count()
        print('总计数为:', Person3.count)
print('总计数为:',Person3.count) #类名访问
p31 = Person3('张三',25)    #创建对象
p31.say_hi()        #调用对象的方法
Person3.get_count()  #通过类名访问
p32 = Person3('李四',28)   #创建对象
p32.say_hi()        #调用对象的方法
Person3.get_count()  #通过类名访问
del p31            #删除对象p31
Person3.get_count()  #通过类名访问
del p32            #删除对象p32
Person3.get_count()  #通过类名访问

两个下划线开头,但不以两个下划线结束的方法是私有的(private),其他为公共的(public).
以双下划线开始和结束的方法是Python的专有特殊方法。不能直接访问私有方法,但可以在其他方法中访问

class Book:                     #定义类Book
    def __init__(self, name, author, price):
        self.name = name  #把参数name赋值给self.name,即成员变量name(域)
        self.author = author#把参数author赋值给self.author,即成员变量author(域)
        self.price = price   #把参数price赋值给self.price,即成员变量price(域)
    def __check_name(self):       #定义私有方法,判断name是否为空
        if self.name == '' : return False
        else: return True
    def get_name(self):          #定义类Book的方法get_name
        if self.__check_name():print(self.name,self.author) #调用私有方法
        else:print('No value')
b = Book('Python程序设计教程','江红',59.0)    #创建对象
b.get_name()                    #调用对象的方法
b.__check_name()           #直接调用私有方法,非法

方法重载

可以定义多个重名的方法,只要保证方法签名是唯一的
方法签名包括三个部分:方法名、参数数量和参数类型

class Person21:
    def say_hi(self, name = None):
        self.name = name
        if name == None:
            print('您好')
        else:
            print('您好,我叫', self.name)
       
p21 = Person21()
p21.say_hi()
p21.say_hi('任思怡')
class Person22:                   #定义类Person22
    def say_hi(self, name):    #定义类方法say_hi,带两个参数
        print('您好, 我叫', self.name)
    def say_hi(self, name, age): #定义类方法say_hi,带三个参数
        print('hi, {0}, 年龄:{1}'.format(name,age))
p22 = Person22()               #创建对象
p22.say_hi('Lisa', 22)        #调用对象的方法
#p22.say_hi('Bob')        #TypeError: say_hi() missing 1 required positional argument: 'age'

Python类体中定义多个重名的方法虽然不会报错,但只有最后一个方法有效,所以建议不要定义重名的方法。

继承

派生类:python支持多重继承,即一个派生可以继承多个基类

声明派生类时,必须在其构造函数中调用基类的构造函数

class Person:                 #基类
    def __init__(self, name, age): #构造函数
        self.name = name     #姓名
        self.age = age        #年龄
    def say_hi(self):         #定义基类方法say_hi
        print('您好, 我叫{0}, {1}岁'.format(self.name,self.age))
class Student(Person):         #派生类
    def __init__(self, name, age, stu_id): #构造函数
        Person.__init__(self, name, age) #调用基类构造函数
        self.stu_id = stu_id    #学号
    def say_hi(self):          #定义派生类方法say_hi
        Person.say_hi(self)    #调用基类方法say_hi
        print('我是学生, 我的学号为:', self.stu_id)
p1 = Person('张王一', 33)            #创建对象
p1.say_hi()
s1 = Student('李姚二', 20, '2018101001') #创建对象
s1.say_hi()

查看继承的层次关系

通过类的方法mro()或类的属性__mro__可以输出其继承的层次关系

class A: pass
class B(A): pass
class C(B): pass
class D(A): pass
class E(B, D): pass
D.mro()
E.__mro__

类成员的继承和重写

通过继承,派生类继承基类中除构造方法之外的所有成员

如果在派生类中重新定义从基类继承的方法,则派生类中定义的方法覆盖从基类中继承的方法

class Dimension:  #定义类Dimensions
    def __init__(self, x, y): #构造函数
        self.x = x       #x坐标
        self.y = y       #y坐标
    def area(self):       #基类的方法area()
        pass
class Circle(Dimension):  #定义类Circle(圆)
    def __init__(self, r): #构造函数
        Dimension.__init__(self, r, 0)
    def area(self):        #覆盖基类的方法area()
        return 3.14 * self.x * self.x   #计算圆面积
class Rectangle(Dimension):  #定义类Rectangle(矩形)
    def __init__(self, w, h): #构造函数
        Dimension.__init__(self, w, h)
    def area(self):  #覆盖基类的方法area()
        return self.x * self.y  #计算矩形面积
d1 = Circle(2.0)          #创建对象:圆
d2 = Rectangle(2.0, 4.0)   #创建对象:矩形
print(d1.area(), d2.area())  #计算并打印圆和矩形面积

Python面向对象程序设计 续

对象的特殊方法

  • 包含许多以双下划线开始和结束的方法
  • 创建对象实例时,自动调用__init__方法,a__It__方法
class Person:
    def __init__(self, name, age): #特殊方法(构造函数)
        self.name = name
        self.age = age
    def __str__(self):          #特殊方法,输出成员变量
        return '{0}, {1}'.format(self.name,self.age)
#测试代码
p1 = Person('张三', 23)
print(p1)

运算符重载与对象的特殊方法

Python的运算符实际上是通过调用对象的特殊方法实现的


# 运算符重载示例
class MyList:             #定义类MyList
    def __init__(self, *args): #构造函数
        self.__mylist = []  #初始化私有属性,空列表
        for arg in args:
            self.__mylist.append(arg)
    def __add__(self, n):   #重载运算符"+",每个元素增加n
        for i in range(0, len(self.__mylist)):
            self.__mylist[i] += n 
    def __sub__(self, n):   #重载运算符"-",每个元素减少n
        for i in range(0, len(self.__mylist)):
            self.__mylist[i] -= n 
    def __mul__(self, n):   #重载运算符"*",每个元素乘以n
        for i in range(0, len(self.__mylist)):
            self.__mylist[i] *= n 
    def __truediv__(self, n): #重载运算符"/",每个元素除以n
        for i in range(0, len(self.__mylist)):
            self.__mylist[i] /= n 
    def __len__(self):     #对应于内置函数len(),返回列表长度
        return(len(self.__mylist))
    def __repr__(self):    #对应于内置函数str(),显示列表
        str1 = ''
        for i in range(0, len(self.__mylist)):
            str1 += str(self.__mylist[i]) + ' '
        return str1
#测试代码
m = MyList(1, 2, 3, 4, 5) #创建对象
m + 2; print(repr(m))   #每个元素加2
m - 1; print(repr(m))   #每个元素减1
m * 4; print(repr(m))   #每个元素乘4
m / 2; print(repr(m))   #每个元素除2
print(len(m))         #列表长度

@functools.total ordering装饰器

支持大小比较的对象需要实现特殊方法:__eq__、__lt__、__le__、__ge__、__gt__
使用functools模块的total_ordering装饰器装饰类,则只需要实现__eq__,以及__lt__、__le__、__ge__、__gt__中的任意一个
total_ordering装饰器实现其他比较运算,以简化代码量

import functools
@functools.total_ordering
class Student:
    def __init__(self, firstname, secondname):
        self.firstname = firstname
        self.secondname = secondname
    def __eq__(self, other):     #判断姓名是否一致
        return ((self.lastname.lower(), self.firstname.lower()) == (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):     #self姓名s3)

__call__方法和可调用对象(callabe)

Python类体中可以定义一个特殊的方法:__call__方法

定义了__call__方法的对象称之为可调用对象

class GDistance:
    def __init__(self, g):
        self.g = g
    def __call__(self, t):
        return (self.g*t**2)/2
    
# 测试代码
if __name__ = '__main__':
    e_gdist = GDistance(9.8)
    for t in range(11):
        print(format(e_gdist(t), "0.2f"),end=' ')

对象的引用、浅拷贝和深拷贝

  • 对象的引用:对象的赋值,id相同

  • 对象的浅拷贝:对象的赋值会引用同一个对象,即不拷贝对象

    • 切片操作

    • 对象实例化

    • copy函数

    • import copy
      acc1 = ['Charlie', ['credit', 0.0]]
      acc2 = acc1[:] # 使用切片方式拷贝对象
      acc3 = list(ac1) # 使用对象实例化方法拷贝对象
      acc4 = copy.copy(acc1) # 使用copy.copy函数拷贝对象
      acc2[0] = 'Mary'
      acc2[1][1] = -99.9
      
  • 对象的深拷贝:如果要递归复制对象中包含的子对象,可以使用copy模块的deepcopy()函数

    import copy
    acc1 = ['Charlie', ['credit', 0.0]]
    acc5 = copy.deepcopy(acc1)
    acc5[0] = 'Clinton'
    acc5[1][1] = -19.9
    

可迭代对象:迭代器和生成其

可循环迭代的对象称之为可迭代对象,迭代器和生成器函数是可迭代对象。

相对于序列,可迭代序列仅在迭代时产生数据,故可节省内存空间。

Python语言提供了若干内置可迭代对象:range\map\filter\enumerate\zip
标准库itertools模块中包含各种迭代器。

可迭代对象:实现了__iter__()的对象是可迭代对象
collections.abc模块中定义了抽象基类Iterable,使用内置的isinstance,可以判断一个对象是否为可迭代对象。

import collections.abc
isinstance((1, 2, 3), collections.abc.Iterable)
isinstance('python33',collections.abc.Iterable)   
isinstance(123,collections.abc.Iterable)               

系列对象都是可迭代对象,生成器函数和生成器表达式也是可迭代对象。

迭代器:实现了__next__()的对象是迭代器

可以使用内置函数next(), 调用迭代器的__next__方法,一次返回下一个项目值

使用迭代器可以实现对象的迭代循环,迭代器让程序更加通用、优雅、高校,更加python化。

import collections.abc
i1 = (i**2 for i in range(10))
insinstance(i1, collections.abc.Iterator)

迭代器对象必须实现两个方法:__iter__()__next()__,二者合称为迭代器协议。__iter__()用于返回对象本身,以方便for语句进行迭代,__next()__用于返回下一元素

自定义可迭代对象和迭代器

class Fib:
    def __init__(self):
        self.a, self.b = 0, 1
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        return self.a
    def __iter__(self):
        return self
# 测试代码
fibs = Fib()
for f in fibs:
    if f < 1000: print(f, end = ',')
    else: break

生成器函数

生成器函数使用yield语句返回一个值,然后保存当前函数整个执行状态,等待下一次调用。

生成器函数是一个迭代器,是可迭代对象,支持迭代

def gentripls(n):
    for i in range(n):
        yield i*3
f = gentripls(10)

反向迭代 reversed迭代器

使用内置函数reversed(),可以实现一个系列的反向系列

如果一个可迭代对象实现了__reversed__()方法,则可以使用reversed()函数获得其反向迭代对象

生成器表达式

生成器表达式的语法和列表解析基本一样,只不过生成器表达式使用()代替[]

表达式expr使用每次迭代内容iter_var,计算生成一个列表。如果指定了条件表达式cond_expr,则只有满足条件的iterable元素参与迭代

range可迭代对象

迭代时产生指定范围的数字序列,可以节省内存空间

map迭代器和itertool.starmap迭代器

map是可迭代对象,使用指定函数处理可迭代对象的每个元素(如果函数需要多个参数,则对应各可迭代对象),返回结果可迭代对象

filter迭代器和itertools.filterfalse迭代器

filter是可迭代对象,使用指定函数处理可迭代对象的每个元素,函数返回bool类型的值。若结果为True,则返回该元素。如果function为None,则返回元素为True的元素

>>> filter                                                         

>>> list(filter(lambda x: x>0, (-1, 2, -3, 0, 5)))       
[2, 5]
>>> list(filter(None, (1, 2, 3, 0, 5)))                          
[1, 2, 3, 5] 

如果需要返回结果为False的元素,则需要使用itertools.filterfalse迭代器
filterfalse根据条件函数predicate处理可迭代对象的每个元素,若结果为True,则丢弃;否则返回该元素

>>> import itertools
>>> list(itertools.filterfalse(lambda x: x%2, range(10))) 
[0, 2, 4, 6, 8]

zip迭代器和itertools.zip_longest迭代器

zip是可迭代对象,拼接多个可迭代对象iter1、iter2…的元素,返回新的可迭代对象,其元素为各系列iter1、iter2…对象元素组成的元组。如果各系列iter1、iter2…的长度不一致,则截断至最小系列长度。可以节省内存空间

>>> zip                                              #
>>> zip((1,2,3),'abc', range(3))        # 
>>> list(zip((1,2,3),'abc', range(3)))  #[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2)]
>>> list(zip('abc', range(10)))            #[('a', 0), ('b', 1), ('c', 2)]

多个可迭代对象的元素个数不一致时,如果需要取最大的长度,则需要使用itertools. zip_longest迭代器

>>> import itertools
>>> list(itertools.zip_longest('ABCD', 'xy', fillvalue='-'))
[('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]

enumerate迭代器

enumerate是可迭代对象,用于枚举可迭代对象iterable中的元素,返回元素为元组(计数, 元素)的可迭代对象

你可能感兴趣的:(Python笔记——类与对象)