python学习笔记-面向对象高级编程-20200324

面向对象高级编程

使用__slots__

定义了一个class并创建了一个class的实例后,可以给该实例绑定任何属性和方法。如:

# 定义class
class Student(object):
    pass
#       创建实例   
s=Student()    
#      给实例绑定属性
s.name = 'a'
#      给实例绑定方法
def set_age(self,age):
    self.age=age
from types import MethodType
s.set_age=MethodType(set_age,s)
s.set_age(25)
>>>s.age--->5
  • 给一个实例绑定的方法,对另一个实例不起作用
  • 为了给所有实例都绑定方法,可以给 class绑定方法:
def set_score(self,score):
    self.score = score
Student.set_score=set_score
# using
s.set_score(100)
>>>s.score--->100
  • 通常上面的set_score方法可以直接定义在class中,但动态绑定允许在程序运行的过程中动态给class加上功能,这在静态语言中很难实现

使用__slots__

  • 限制实例的属性,如只允许对Student实例添加name和age属性,此时在定义class时,定义一个特殊的__slots__变量,来限制该class实例能添加的属性

a. 可以用tuple定义允许绑定的属性名称

class Student(object):
    __slots__ = ('name','age')    

b. slot__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义__slots,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

使用@property

绑定属性时,限制其范围,如在Student中的score属性设置范围可以通过set_score()方法设置成绩,再通过一个get_score()来获取成绩:

class Student(object):

    def get_score(self):
        return self._score
    
    def set_score(self,value):
            if not isinstance(value,int):
                raise ValueError('score must be an integer1')
            if value < 0 or value >100:
                raise ValueError('score must between 0~100!')
            self._score=value
  • 既可以检查函数,又可以给函数动态加上功能—使用python内置的@property装饰器将一个方法变成属性调用,
    对实例属性操作时候,该属性是通过getter和setter方法来实现的:
class Student(object):

    @property               # 加上这个可以把一个getter方法变属性
    def score(self):
        return self._sore
    
    @score.setter           # @property本身又创建了零一装饰器@score.setter
    def score(self,value):
        if not isinstance(value,int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0~100!')
        self._score = value

定义只读属性:只定义getter方法,不定义setter方法就是一个只读属性

class Student(object):

    @property
    def birth(self):
        return self._birth
    
    @birth.setter               ## birth时可读可写属性
    def birth(self,value):
        self._birth = value
    @property
    def age(self):              ## age就是一个只读属性,因为age可以根据birth和当前时间计算出来
        return 2015 - self._birth

练习

请利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution:

class Screen(object):

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

    @width.setter
    def width(self,value):
        self._width=value

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

    @height.setter

    def height(self,value):
        self._height=value

    @property
    def  resolution(self):
        return self._width*self._height

多重继承

继承是面向对象编程的一个重要方式,通过继承,子类就可以扩展父类的功能

通过多重继承,一个子类就可以同时获得多个父类的所有功能,如:


class Animal(object):
    pass

# 大类:class Mammal(Animal):
    pass

class Bird(Animal):
    pass

# 各种动物:class Dog(Mammal):
    pass

class Bat(Mammal):
    pass

class Parrot(Bird):
    pass

class Ostrich(Bird):
    pass
## 给动物加上Runnable类和Flyable
class Runnable(object):
    def run(self):
        print('Running...')
class Flyable(object):
    def fly(self):
        print('Flying...')

对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:

class Dog(Mammal,Runnable)
    pass

对于需要Flyable功能的动物,就多继承一个Flyable,例如Bat:

class Bat(Mamal,Flyable):
    pass

Mixln

增加额外的功能,可以通过多重继承实现,如class Dog(Mammal,Runnable),让Dog除了继承Mammal还要继承Runnable,这种设计叫做Mixln。
Mixln的目的就是给一个类增加多个功能
如:

## 哺乳动物,飞行,肉食
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
  • python自带的很多库也是用了Mixln
  • 设计时使用Mixln,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需子类

小结

  • 由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单* 一继承的语言(如Java)不能使用MixIn的设计。

定制类

str

  • 作用:打印实例返回一个好看的字符串
class Student(object):
    def __init__(self,name):
        self.name=name
    def __str__(self):
        return 'Student object (name: %s)'        %self.name
    __repr__ = __str__
  • __repr__()是程序开发者看到的字符串,是为调试服务的, __str__()返回用户看到的字符串,所以可以加一句__repr__ = __str__使得print打印出来的实例 与 定义实例后直接敲变量后打印出来的实例一样

iter

__iter__()方法可以使得一个类被用于for...in循环,该方法返回一个迭代对象,费波纳基数列:

class Fib(object):
    def __init__(self):
        self.a,self.b = 0,1       # 初始化两个计数器a,b
        
    def __iter__(self):
        return self         # 实例本身就是迭代对象,故返回自己
        
    def __next__(self):
        self.a,self.b=self.b,self.a+self.b
        if self.a > 100000:
            raise StopIteration()
        return self.a
        
 ## 把Fib实例作用于for循环
 for n in Fib():
    print(n)
  • 该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
  • 注意:Fib实例虽然能作用域for循环,看起来和list有点像,但是它不能提取其中元素,如Fib()[5]就不可以

getitem

  • 将类被用于for循环像list和tuple那样,并且还可以提取元素可以使用__getitem__方法:
class Fib(object):
    def __getitem__(self,n):
        a,b = 1,1
        for x in range(n)
            a,b = b , a+b
        return a
 
# using
f=Fib()
>>>f[0]--->1
>>>f[1]--->1
>>>f[2]--->2
...
  • getitem()传入的参数可能是一个int也可能是一个切片对象slice,所以使用切片法提取元素前要做判断:
class Fib(object):
    def __getitem__(self,n):
        if isinstance(n,int):    # n是索引
            a,b = 1,1
            for x in range(n):
                a,b = b,a+b
            return a
        if isinstance(n,slice):      # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                strat = 0
            a,b = 1,1
            L=[]
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a,b = b,a+b
            return L
## using slice
f = Fib()
>>>f[0:5]-----[1,1,2,3,5]
  • 此Fib的切片没有对step参数做处理,所以不可f[:10:2],也没有对负数做处理
  • 若把对象堪称dict,getitem()的参数也可能是一个可以做key 的object,如str。
  • setitem()方法,把对象视作list或dict来对集合赋值
  • delitem()方法,用于删除某个元素

getattr

  • getattr()方法可以动态返回一个属性:
class Student(object):

    def __init__(self):
        self.name = 'a'
    
    def __getattr__(self,attr):
        if attr=='score':
            return 99
            
# using
s=Student()
s.score--->99

当调用不存在的属性时,python解释器会试图调用__getattr(self,‘score’)来尝试获得属性,这样就有机会返沪score的值。

  • getattr()方法可以返回函数:
class Student(object):

    def __getattr__(self,attr):
        if attr=='age':
            return lambda:25
        raise AttributeError('\'Student\' object has no attribute \'%s\' % attr)
            
# using method
s.age()
  • 其中__getattr__默认返回的就是None,要让class只响应特定的几个属性,就要按照约定抛出AttributeError的错误
  • 这把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段,作用:可以针对完全动态的情况作调用
  • 利用完全动态的__getattr__,可以写出一个链式调用
class Chain(object):

    def __init__(self,path=''):
        self._path=path
        
    def __getattr__(self,path):
        return Chain('%s%s' % (self._path,path))
        
    def __str__(self):
        return self._path
        
    __repr__ = __str__
    
## using
>>>Chain().status.user.timeline.list---->
/status/user/timeline/list

这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!

这里其实没太看懂。。。

call

一个对象实例可以有自己的属性和方法,调用实例方法时,用instance.method()来调用。

  • 定义__call__()方法,就可以直接对实例进行调用:
class Student(object):
    def __init__(self,name):
        self.name = name
        
    def __call__(self):
        print('My name is %s.' % self.name)
        
# using method
s=Student('a')
s()   # self参数不要传入
--->My name is a
  • call()还可以定义参数
  • 使用Callable()函数,我们就可以判断一个对象是否是“可调用”对象

小结

Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类。
更多定制方法见<a href=“https://docs.python.org/3/reference/datamodel.html#special-method-names”>https://docs.python.org/3/reference/datamodel.html#special-method-names</a>

使用枚举类

  • Enum类可以为枚举类型定义一个类型,然后每个常量都是class的一个唯一实例。
from enum import Enum

#  获取了Month类型的枚举类
Month = Enum(
'Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# using 可以直接使用Month.Jan来禁用一个常量,或者枚举它的所有成员
for name,member in Month.__members__.items():
    print(name,'=>',member,',',member.value)

这个里边最后显示出来的member是第一层括号里的Month.第二层括号里的月份

  • 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
from enum import Enum,nuique

@unique
class Weekday(Enum):
    
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique装饰器可以帮助我们检查保证没有重复值。

  • 访问这些枚举类型
day1 = Weekday.Mon
print(day1)
print(Weekday.Tue)
--->Weekday.Mon
print(Weekday['Tue'])
 print(Weekday.Tue.value)

print(Weekday(1))

exercise

把Student的gender属性改造为枚举类型,可以避免使用字符串:

from enum import Enum,unique
class Gender(Enum):   #相当于从Enum中派生出自定义类
    Male = 0
    Female = 1
    
class Student(object):
    def __init__(self,name,gender):
        self.name = name
        self.gender = gender
# testing
>>> bart = Student('Bart', Gender.Male)
>>> if bart.gender == Gender.Male:
    print('测试通过!')
else:
    print('测试失败!')

测试通过!
>>> bart.gender
<Gender.Male: 0>
>>> Gender.Male
<Gender.Male: 0>

小结

  • Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较

使用元类

type()

  • 动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
  • type()函数可以查看一个类型或变量的类型,一个class的类型是type,其实例的类是该class
  • 使用type()函数可以创建class的方法,而无需通过class xx(object)的方法
def fn(self,name='world'):     #先定义函数,这个函数就是类里边的方法
    print('Hello,%s.' % name)
    
# using
Hello = type('Hello',(object,),dict(hello=fn))   #创建Hello,类是前面被赋值的那个Hello而不是括号里的,括号里那个我也不知道干嘛的
  • 要创建一个class对象,type()函数依次传入三个参数:
  1. class的名称
  2. 继承的父类集合,注意python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上

用法:type(classname,object,sttr_dict)
classname:要创建的class的名称
object:父类的元祖,就是要继承哪些父类,可以为空
sttr_dict:类的属性(名称和值)
如:

# method 1
class A(object):
    name = 'john'
# method 2
A = type('A',(),{'name':'john'})   # 动态创建了一个A类
print(A())   #创建了该类的实例
print(A.name)   #输出类A类的属性john
#  创建B类并继承A类
B=type('B',(A,),{})
print(B.name)    #输出结果是John,因为继承A类

type()函数允许动态创建类====动态语言本身支持运行期动态创建类

metaclass

  • 除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
  • metaclass,直译为元类:
    当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。但如果想要创建出类,那就先定义metaclass,然后创建类,最后创建实例
  • new()方法接收到的参数依次是:
  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

该用户没有用户名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值