Python学习笔记八 面向对象高级编程(一)

参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

一、使用__slots__

正常情况下,当定义了一个类之后,我们可以给这个类的实例绑定任何属性,这就是动态语言的优势:

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

bob=Student(‘Bob‘,19)
stam=Student(‘Stam‘,20)
bob.score=98
#给示例bob增加了属性score可以访问
print(‘%s:%d years old,score is %d.‘%(bob.name,bob.age,bob.score))
#实例stam并没有score属性,报错
print(‘%s:%d years old,score is %d.‘%(stam.name,stam.age,stam.score))

同样也可以给类的实例绑定方法:

#先定义一个类的实例方法
def set_num(self,num):
    self.num=num
#导入types库
from types import MethodType
#使用绑定方法Methodtype()
bob.set_num=MethodType(set_num,bob)
bob.set_num(11)
#输出11
print(bob.num)
#报错,因为实例stam并没有绑定这个方法
stam.set_num(12)
print(stam.num)

上述代码中给实例绑定方法的方式是先创建一个类的实例方法,然后再通过MethodType()把这个方法绑定到实例对象bob上。

另外一种给实例绑定方法的方式如下:

def sumfunc(x):
    print(‘this is sumfunc function!---%d‘% x)
bob.pfunc=sumfunc
bob.pfunc(101)

而上述这种绑定方法只能针对具体的实例绑定 ,如果在一个类定义之后,发现这个类需要一种方法,也可以通过如下方式绑定:

#先定义一个Student类
#此时类中没有set_score方法
class Student(object):
    def __init__(self,name):
        self.name=name

#创建类的实例
bob=Student(‘Bob‘)

#此时发现需要给Student增加一个设置分数的方法
#先定义一个类的方法
def set_score(self,score):
    self.score=score
#再将这个方法名直接赋给类的属性,为方便起见,当然是给类新增一个和这个方法同名的类方法
#此时,Student类不仅有了这个set_score方法,也有了score这个属性
Student.set_score=set_score

bob.set_score(96)
print(bob.score)

在实际应用中,我们往往可以实现知道某个类可能需要新增的属性,而可以通过某些手段限制类定义的属性,这里就需要用到‘__slots__‘:

class Student(object):
    __slots__ = (‘name‘, ‘age‘) # 用tuple定义允许绑定的属性名称

通过给实例增加属性看一下__slots__的效果:

class Student(object):
    __slots__ = (‘name‘, ‘age‘) # 用tuple定义允许绑定的属性名称

bob=Student()
bob.name=‘Bob‘
print(bob.name)
bob.age=‘16‘
print(bob.age)
#以下代码出错,因为属性country不在__slots__范围里
bob.country=‘British‘
print(bob.country)

但__slots__仅对其所在的类的实例起作用,对这个类的所有子类实例是没有限制作用的,如果要限制子类的新增属性,需要在子类定义中的__slots__设置。而子类设置__slots__之后,其可以新增的属性不仅仅是其slots限定的范围,还可以包括父类slots所含的属性:

class Student(object):
    __slots__ = (‘name‘, ‘age‘) # 用tuple定义允许绑定的属性名称

class Wstudent1(Student):
    pass

Wangm=Wstudent1()
#子类在本身没有__slots__限制的情况下,其实例新增属性不受父类__slots__限制
Wangm.num=16
print(Wangm.num)

#因Wstudent2定义时通过__slots__限制了属性
#所以其实例新增属性受到父类和其本身的__slots__的限制
class Wstudent2(Student):
    __slots__=(‘country‘,‘score‘)

Wanglei=Wstudent2()
Wanglei.score=100
print(Wanglei.score)
Wanglei.country=‘China‘
print(Wanglei.country)
Wanglei.age=15
print(Wanglei.age)
Wanglei.name=‘WangLei‘
print(Wanglei.name)
#本身和父类的__slots__中均不含num属性,所以报错
Wanglei.num=1
print(Wanglei.num)

 二、使用@property

"在这一节的学习中,发现自己对于类属性中访问限制一些概念理解得还不够透彻,因此在开始@property之前,先复习巩固一下访问限制的相关内容。"

首先看一下最简单、基础的代码:

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

wanghai=Student(‘WangHai‘,99)
liyue=Student(‘LiYue‘,100)
print(wanghai.score)
print(liyue.score)    

wanghai.score=95
liyue.name=‘LiYU‘
print(wanghai.score)
print(liyue.name)  

对于上面的代码,Student类有两个属性score和name,并且在类定义结束后,使用者可以随时修改实例的属性值。

那么,针对于此,怎样才可以让使用者不能随意修改呢?我们首先想到添加类方法set方法:

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

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

    def set_score(self,score):
        self.score=score

wanghai=Student(‘WangHai‘,99)
print(wanghai.score)

#是不是真的不能修改了呢?
wanghai.score=95
wanghai.name=‘wanghang‘
print(wanghai.name)
print(wanghai.score)

可是运行结果可以发现,还是可以直接给实例的属性赋值修改。所以可见,set方法不能约束使用者直接修改属性值的行为。
那么有没有什么方法可以起到约束的作用呢。

答案是在定义类的时候在需要约束的属性名前增加两个下划线"__":

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

    def set_name(self,name):
        self.__name=name

    def set_score(self,score):
        self.score=score

wanghai=Student(‘WangHai‘,99)
#正常输出99
print(wanghai.score)
#下一句报错
print(wanghai.__name)

我们可以发现,通过对name属性名修改为‘__name‘之后,访问wanghai.__name报错,提示信息为类没有这个属性。
而访问score属性正常,因为并没有对score设置限制。

再进一步,是不是对于"__name"属性无法直接赋值修改了呢,上段代码中增加:

wanghai.__name="WH"
print(wanghai.__name)

我们却奇怪的发现,居然代码可以执行,可以修改,并且正常输出了"WH"。这到底是怎么回事呢?

是因为对于设置属性名的访问限制方法,即增加双下划线的方法后,其实在Python解释器遇见这个属性名后,会在解释器内部再次修改这个属性名为:‘_Student__name‘,而非代码中我们看到的"__name"!这也就可以解释为何上述代码可以执行了,因为上面代码实质上是给实例增加了一个__name属性,这个属性非类定义中的‘__name"属性(类定义中的该属性名实际为_Student__name)。

如果类定义中有get方法,那通过get方法我们可以更清晰的看出上述代码增加后其实并没有改变类定义中的‘__name‘属性值:

class Student(object):
    #简便起见,仅考虑一个属性
    def __init__(self,score):
        #这里的__score解释器转换为_Student__score
        self.__score=score

    def get_score(self):
        #这里的__score解释器转换为_Student__score
        return self.__score

    def set_score(self,score):
        self.__score=score

#1、常规创建实例
wanghai=Student(99)
#正常输出99
print(wanghai.get_score())
#通过set方法修改属性值
wanghai.set_score(100)
#正常输出100
print(wanghai.get_score())

#尝试直接访问属性
#因为类定义中的__score非这里的__score
#并且实例中也没有增加这个__score属性,所以报错类不存在这个__score
print(wanghai.__score)
#那么尝试直接通过_Student__name属性名直接访问
#可以正常输出,输出100
print(wanghai._Student__score)

#继续测试,给实例增加__score属性
wanghai.__score=88
#此时增加的非类定义中的__score,所以是可以添加成功,并且可以直接访问
#输出88
print(wanghai.__score)

#再看一下类定义中的__score属性值(实际为_Student__name)
#输出依然是100,没有变化
print(wanghai.get_score())
print(wanghai._Student__score)

所以综上,小结一下:
1、如果要真正限制类定义中的属性的修改和访问,需要在类定义中的属性名前面增加"__",而这个方法的实质是在Python解释器解释类定义代码过程中将其真正的属性名视为"_类名__属性名",而这一点对于使用者是不可见的,所以对于使用者无法真正修改和访问这个属性。

2、对应于此,为规范代码,同时体现访问限制,类定义中须配套定义get和set方法。

3、其他:如要进一步限制使用者对于类属性的增加、属性值的修改这些,可以使用__slots__方法。

___________________________________________________________________________________________

接上,按照上述的一个规范的访问限制类定义的写法(按最简单的仅有一个属性的情况):

class Student(object):
    #简便起见,仅考虑一个属性
    def __init__(self,score):
        #这里的__score解释器转换为_Student__score
        self.__score=score

    def get_score(self):
        #这里的__score解释器转换为_Student__score
        return self.__score

    def set_score(self,score):
        self.__score=score

并且在使用过程中,须自觉严格使用这些set和get方法;那么一个带来的烦恼就是每次使用都需要写实例名.set_属性名()或get_属性名(),相比直接通过‘实例名.属性名‘要麻烦一些,由此,Python提供了一种便利的方法,也是本节的重点:@property。

通过@property和@属性名.setter语句的添加可以实现直接使用‘实例名.属性名‘来访问或修改属性值。

同时,需要注意的是,当通过@装饰后,原先定义的set/get方法名同时需要修改为属性名(只是set/get对应的参数不同,分别是self和self、参数。

class Student(object):
    def __init__(self,score):
        self.__score=score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self,score):
        self.__score=score

wanghai=Student(96)
print(wanghai.score)
wanghai.score=99
print(wanghai.score)

针对上例,可以扩展成一个更为健壮的代码,在setter代码段中增加判断传入参数的合理性的代码:

class Student(object):
    def __init__(self,score):
        self.__score=score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self,score):
        if not isinstance(score,int):
            raise ValueError(‘输入参数非数值!‘)
        elif score>100 or score<0:
            raise ValueError(‘输入数值参数应该在0~100范围内!‘)
        else:
            self.__score=score

wanghai=Student(96)
print(wanghai.score)
wanghai.score=‘a‘
wanghai.score=109

也可以设置类的只读属性,这种情况下,只需要对需要设为只读的属性增加@property,无需对应设置‘@属性名.setter‘。

class Student(object):
    def __init__(self,name,score):
        self.__name=name
        self.__score=score

    #__name为只读属性
    @property
    def name(self):
        return self.__name

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self,score):
        if not isinstance(score,int):
            raise ValueError(‘输入参数非数值!‘)
        elif score>100 or score<0:
            raise ValueError(‘输入数值参数应该在0~100范围内!‘)
        else:
            self.__score=score

wanghai=Student(‘WangHai‘,96)
print(wanghai.name)
print(wanghai.score)
wanghai.score=100
print(wanghai.score)
#报错,name为只读属性
#wanghai.name=‘WH0‘
#下面代码虽然执行成功,但只是给实例新增了一个‘__name‘属性
wanghai.__name=‘WH1‘
#访问类定义中的属性,即_Student__name
print(wanghai.name)
print(wanghai._Student__name)

练习:

‘‘‘
请利用@property给一个Screen对象加上width和height属性
以及一个只读属性resolution
‘‘‘
#这题有些没弄清的是属性名前面必须加下划线或者双下划线,否则就报错
#那就当一个习惯保持双下划线命名吧
class Screen(object):

    @property
    def width(self):
        return self.__width

    @property
    def height(self):
        return self.__height

    @property
    def resolution(self):
        return self.__width*self.__height

    @width.setter
    def width(self,width):
        if not isinstance(width,int):
            raise ValueError(‘参数必须是整数值!‘)
        else:
            self.__width=width

    @height.setter
    def height(self,height):
        if not isinstance(height,int):
            raise ValueError(‘参数必须是整数值!‘)
        else:
            self.__height=height

# 测试:
s = Screen()
s.width = 1024
s.height = 768
print(‘resolution =‘, s.resolution)
if s.resolution == 786432:
    print(‘测试通过!‘)
else:
    print(‘测试失败!‘)
        

 三、定制类

(一)__str__

在一个类的实例通过print()方法打印出来时总是类似于"<__main__.Student object at 0x109afb190>"的型式,不仅复杂,也不明确看出实例的具体信息。

那么在类的定义中,可以增加__str__方法,返回这个类的实例信息:

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

    def __str__(self):
        return ‘Student object (name:%s).‘% self.name

#输出:Student object (name:Bob).
print(Student(‘Bob‘))

需要注意的是,上述__str__能替代的情况是Student(‘参数‘)这种情况,如果对于"变量名=Student(‘参数‘)"时,在命令行格式下,直接敲‘变量名‘还是返回的是原始信息,这是因为这种情况下,使用的是__repr__()而非__str__()。

如果要设置__repr__,最简单的方法如下:

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

    def __str__(self):
        return ‘Student object (name:%s).‘% self.name

    __repr__=__str__

(二)__iter__

如果一个类想通过for/in循环,类似于list或tuple,就必须实现一个__iter__()方法,这个方法返回一个迭代对象,然后Python的for循环就可以不断调用这个迭代对象的__next__()方法获取循环下一个值,直到遇到StopIteration错误。

class Fib(object):
    def __init__(self):
        self.a,self.b=0,1

    def __iter__(self):
        return self

    def __next__(self):
        self.a,self.b=self.b,self.a+self.b
        if self.a<100:
            return self.a
        else:
            raise StopIteration()

for i in Fib():
    print(i)
    

(三)__getitem__

上面的Fib()类虽然可以通过for/in循环,但依然不能当成list使用,比如不能使用列表的下标访问元素。如果要实现这个功能,需要给这个类添加__getitem__方法。

class Fib(object):
    def __getitem__(self,n):
        a,b=1,1
        for x in range(n):
            a,b=b,a+b
        return a
f=Fib()
print(f[0])
print(f[3])
print(f[13])
print(f[33])

#可以看出__getitem__()方法定义后自动也给类添加了for循环功能
for i in Fib():
    if i<1000:
        print(i)
    else:
        break

(四)__getattr__

正常情况下如果调用一个不存在的类的属性或方法时就会报错。

如果要处理这个报错,那就需要写一个__getattr__方法,当调用不存在的属性时,Python解释器就会尝试去调用这个方法。

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

    def __getattr__(self,attr):
        #可以根据情况设定有可能被使用者误访问的属性
        if attr==‘score‘:
            return 99
        #对于其他情况,报错
        else:
            raise AttributeError(‘\‘Student\‘ object has no attribute \‘%s\‘‘ % attr)

s=Student(‘Bob‘)
#正常访问,输出Bob
print(s._name)
#通过getattr方法返回,输出99
print(s.score)
#通过getattr方法,抛出错误提示
print(s.country)

(五)__call__
通常在调用类的实例的方法的时候,需要以“实例名.方法名()”的形式,那么能不能直接通过“实例名()”直接调用呢?在类定义中只需要加一个__call__()方法,就可以实现:

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

    def __call__(self):
        print(‘instance\‘s name is %s‘ % self._name) 

    def sp(self):
        print(‘Student\‘s another method for printing 12345!‘)

Student(‘Bob‘)()
Student(‘Stam‘)()
Student(‘Steve‘).sp()

通过代码我们可以发现,当定义了__call__()方法后,实例名()对应的就是call方法,而对于其他类中定义的方法还需要原始的写法才可以使用。

在实例直接调用后,这里实例对象就类似于函数,而实质上这两者也没有本质的区别。对于带有__call__()定义的类的实例,它就是一个Callable对象(可调用对象),同样函数也是一个Callable对象。

class Student0(object):
    def __init__(self,name):
        self._name=name
    def __call__(self):
        print(‘instance\‘s name is %s‘ % self._name) 

class Student1(object):
    def __init__(self,name):
        self._name=name

s0=Student0(‘Bob‘)
s1=Student1(‘Bob‘)
#s0是Callable对象,输出True
print(callable(s0))
#s1不是Callable对象,输出False
print(callable(s1))
#max是函数,输出True
print(callable(max))
#输出False
print(callable(1))

四、使用枚举类

枚举类型可以看做是一系列常量的集合,在Python中可以通过字典、类去实现,在通过字典或者常规的类定义的话,无法避免对其的修改,因此在Python3.4后的版本提供了enum库,可以定义枚举类型的类(枚举类)。

from enum import Enum

#注意在定义枚举类的时候基类参数需要设置为Enum
#星期的枚举类共有7个成员
#每个成员都有名字和值
#定义成员的时候,成员名字不能相同
#成员的值可以相同
#但相同的值的成员,后面的成员名被视为第一个对应该值的成员的别名
class Weekday(Enum):
    Monday=1
    Mon=1   #Mon被视为Monday的成员
    Tuesday=2
    Wednsday=3
    Thursday=4
    Friday=5
    Saturday=6
    Sunday=7

#打印成员
print(Weekday.Mon)
print(Weekday.Sunday)

#遍历成员
for x in Weekday:
    #获取成员名
    print(x.name)
    #获取成员值
    print(x.value)
    #获取成员信息
    print(x)

而如果对于具有同一值的多个成员名的情况,需要全部遍历出来,则需要用到__members__属性:

‘‘‘
输出:
(‘Monday‘, <Weekday.Monday: 1>)
(‘Mon‘, <Weekday.Monday: 1>)
(‘Tuesday‘, <Weekday.Tuesday: 2>)
(‘Wednsday‘, <Weekday.Wednsday: 3>)
(‘Thursday‘, <Weekday.Thursday: 4>)
(‘Friday‘, <Weekday.Friday: 5>)
(‘Saturday‘, <Weekday.Saturday: 6>)
(‘Sunday‘, <Weekday.Sunday: 7>)
‘‘‘
for x in Weekday.__members__.items():
    print(x)

枚举类也可以限制为不可以设定同一值的多个成员名,则需要import进unique模块,并且在枚举类定义前加上装饰器@unique

from enum import Enum,unique

@unique
class Weekday(Enum):
    Monday=1
    #Mon=1,这句代码就不能设置了,会报错
    Tuesday=2
    Wednsday=3
    Thursday=4
    Friday=5
    Saturday=6
    Sunday=7

在for循环遍历中通过替代变量可以直接获取各个成员,如果知道某个成员的名字或者值,那么也可以获取该成员。

from enum import Enum

class Weekday(Enum):
    Monday=1
    Mon=1
    Tuesday=2
    Wednsday=3
    Thursday=4
    Friday=5
    Saturday=6
    Sunday=7
#通过成员名获取成员
print(Weekday[‘Tuesday‘])
print(Weekday[‘Monday‘])
print(Weekday[‘Mon‘])  #这里也可以看出Mon是Monday的别名
#通过成员值获取成员
print(Weekday(6))

练习:

‘‘‘
把Student的gender属性改造为枚举类型,可以避免使用字符串
‘‘‘
from enum import Enum,unique

@unique
class Gender(Enum):
    Male=0
    Femal=1

class Student(object):
    def __init__(self,name,gender):
        self._name=name
        self._gender=gender

# 测试:
bart = Student(‘Bart‘, Gender.Male)
if bart._gender == Gender.Male:
    print(‘测试通过!‘)
else:
    print(‘测试失败!‘)
        

原文地址:https://www.cnblogs.com/tsembrace/p/8645985.html

时间: 2024-10-06 04:04:32

Python学习笔记八 面向对象高级编程(一)的相关文章

Python学习笔记八 面向对象高级编程(二)元类

参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000 在廖老师的学习网站里"使用元类"这部分还是把我给看晕了...网上搜到一篇感觉讲的相对易懂一些,贴出链接:两句话掌握 Python 最难知识点--元类--以此文作为这篇笔记的源本. "道生一,一生二,二生三,三生万物" 1.在Python世界中,"type"即为道

python学习笔记(七) - 面向对象高级编程

一. 为类动态添加属性和方法: 1. 动态给一个实例添加属性和方法: 给一个实例绑定的方法,对另一个实例是不起作用的. class Student(object): pass s = Student() s.name = 'Michael' # 动态给实例绑定一个属性 print s.name def set_age(self, age): # 定义一个函数作为实例方法 self.age = age from types import MethodType s.set_age = MethodT

Python学习笔记捌——面向对象高级编程

__slots__特殊变量的使用: 由于Python是动态语言,允许先编写类,然后在创建实例的时候添加属性或者方法:而__slots__特殊变量就是,限制往类里添加属性的: 在创建类的时候,使用__slots__ =('name','age'),就是在创建实例时候,只允许添加绑定name和age两个属性:注意!__slots__只对当前类有效,不会作用于子类: @property装饰器:为了实现数据的封装,不把属性暴露在外面,所以如果想访问实例内部属性的话,就需要使用get和set方法,但是这样

【Python学习之七】面向对象高级编程——__slots__的使用

1.Python中的属性和方法的绑定 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法. (1)首先,定义一个class:  class Student(object):  pass (2)然后,给实例绑定属性: >>> s = Student() >>> s.name = 'Michael' # 动态给实例绑定一个属性 >>> print(s.name) Michael (3)或者,给实例绑定一

python学习笔记12-python面向对象

python学习笔记12-python面向对象 python一切皆对象 一.基本概念 1.面向对象和面向过程 面向对象编程:C++,Java,Python 面向过程编程:函数式编程,C程序等 2.类和对象 类:是对事物的抽象,比如:人类,球类 对象:是类的一个实例,比如:足球,篮球,对象就是对类的实例化 属性:五官,眼,鼻子,理解为一个变量,静态属性 方法:对人来说,吃穿住行,理解为一个函数,动态方法 实例说明:球类可以对球的特征和行为进行抽象,然后可以实例化一个真实的球实体出来 3.为什么要使

python学习笔记八——正则表达式

1.元字符 []-常用来指定一个字符集:[abc];[a-z] -元字符在字符集中不起作用:[akm$] -补集匹配不在区间范围内的字符:[^5] ^-匹配行首 $-匹配行尾 \-后可加不同字符以表示不同意义,也可用于取消所有元字符 \d 匹配任何十进制数,相当于[0-9] \D 匹配任何非数字字符,相当于[^0-9] \s 匹配任何空白字符,相当于[\t\n\r\f\v] \S 匹配任何非空白字符 \w 匹配任何字母数字字符 \W 匹配任何非字母数字字符 *-匹配前一个字符零次或多次 +-至少

Python学习笔记__9章 IO编程

# 这是学习廖雪峰老师python教程的学习笔记 1.概览 IO在计算机中指Input/Output,也就是输入和输出. 由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,所以涉及到数据交换的地方,通常是磁盘.网络等,这些地方就需要IO接口. 数据从内存往外发是Output 数据从外往内存发是Iutput 2.同步IO和异步IO 同步IO:发起IO请求后,等到IO的返回结果,在接着往下执行 异步IO:发起IO请求后,可以去做其他事.IO结果返回后,会通知调用者. 注:本章所讲

python学习笔记之面向对象编程特性(二)

面向对象程序设计中的术语对象(Object)基本上可以看做数据(特性)以及由一系列可以存取.操作这些数据的方法所组成的集合.传统意义上的"程序=数据结构+算法"被封装"掩盖"并简化为"程序=对象+消息".对象是类的实例,类的抽象则需要经过封装.封装可以让调用者不用关心对象是如何构建的而直接进行使用. 首先说明一下python编程规范: #!/usr/bin/env python #coding=utf-8 #编程规范,示例如下:   class 

python 学习笔记7 面向对象编程

一.概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强..." 二.创建类和对象 面向对象编程是一种编程方式,此编程方式的落地需要使用 "类" 和 "对象" 来实现,所以,面向对象编程其实就是对 "类" 和 "对象" 的使用. 类就是一个模板,模板里可以包含多个函数,函数里实现一些功能 对象