Python面向对象高级编程

面向对象高级编程

使用__slots__

Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称,只允许对Student实例添加name和age属性。

就相当于java中的类中预定义属性。这样添加别的属性就会报错。

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score' # 不允许添加新的属性

当然,这种限制对继承的子类不起作用。很好理解,本身子类就是拓宽使用方向的,因此限制的操作肯定不会起效。

使用@property

为了对防止属性被任意修改,可以通过在方法里加入限制的方式进行修改。

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 integer!')
        if value < 0 or value > 100: # 检查范围
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

该调用方法略显复杂,没有直接用属性这么直接简单。

Python内置的@property装饰器就是负责把一个方法变成属性调用。

用法:

class Student(object):

    @property
    def score(self): # 对想要修饰的属性加一个@property装饰器,此处相当于把执行score=property(score)
        return self._score

    @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

加完@property装饰器,则默认该属性为只读属性,只有在设置setter方法之后,才会将该属性设置为可写属性。

这些装饰器都是写在类内部的,相当于给把属性变成了函数,同时通过多态修饰它。而由于属性已经成为了函数,在函数内部的调用应当加下划线。

注意:属性的方法名不要和实例变量重名

例如:

class Student(object):

    # 方法名称和实例变量均为birth:
    @property
    def birth(self):
        return self.birth

这种就是错误的。

这是因为调用s.birth时,首先转换为方法调用,在执行return self.birth时,又视为访问self的属性,于是又转换为方法调用,造成无限递归,最终导致栈溢出报错RecursionError

多重继承

一个子类可以有多个父类,因此可以拥有多个父类的功能。而,java只有单继承,因此,java需要通过接口弥补缺陷。

class Animal(object):
    pass

# 大类:
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

# 大类
class Runnable(object):
    def run(self):
        print('Running...')

class Flyable(object):
    def fly(self):
        print('Flying...')

#可以继承多个父类,狗狗既是动物又能跑
class Dog(Mammal, Runnable):
    pass

如果继承的多个父类中拥有相同的方法,则按照从继承所有父类的子类到object的原则,对所有的类进行拓扑排序,优先调用离子类最近的方法。如果在拓扑排序中同级,则按最左原则判断拓扑远近。

例如:class B(A1, A2, A3), 则视为A1最近,拓扑顺序为:B,A1,A2,A3

定制类

是一些python设定好的用来实现特殊功能的函数。

__str__

用来设定print()函数打印实例时的返回值。

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)

__repr__

设定控制台打印变量的返回值

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

__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 # 返回下一个值

实现一个next函数和一个iter对象。

一定要设置StopIteration()错误,因为这个是退出迭代的条件。

>>> for n in Fib():
...     print(n)
...
1
1
2
3
5
...
46368
75025 # 到设定的迭代条件之后退出

__getitem__

for in 循环只是按循序取元素,如果想要按照下标取元素,类似element[5],还是要靠getitem方法。

作用:使实例可以靠f[n]的方式来访问。

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()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101

注:getitem方法的实质是给[]运算符赋予意义。但是,[]运算符所接受的对象除了int型的下标,还可能是slice型的切片参数变量。因此,完整版的getitem函数定义还是需要判断接受的数据类型,对其分别处理。

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是切片,返回切出来的iterator
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

当然,还可以加上对步长、对复数的处理。这样才是完整的对[]运算符的定义。

与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。

总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的鸭子类型,不需要强制继承某个接口。

__getattr__

作用:正常来说,访问一个实例没有定义的属性或者函数,会返回none。getarr()函数的作用是当不存在这个属性或函数的时候,返回一个默认值。

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr): # 注意,参数是一个attr,即属性名
        if attr=='score':
            return 99

当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

返回值为函数时:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25

此时,调用方法为:

>>> s.age()
25

注:只要加上了getattr方法,所有的不存在的值都不会抛出异常而是会返回none,因此需要进行处理。

这是因为,getattr方法不止会处理定义过在getattr()中默认值的属性,而是会处理所有没有在实例中找到的属性,并返回none。因此,如果想让属性不存在时抛出错误,我们需要进行设置:

class Student(object):

    def __getattr__(self, attr):
        if attr=='age':
            return lambda: 25
        raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)

__call__

实例本身也可以当作一个函数被调用。因为不管是实例还是函数,其本质都是指向一段代码的变量。call()函数就是完成这样的功能。

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

    def __call__(self):
        print('My name is %s.' % self.name)

call()函数中定义了,当函数其变量本身调用时的效果。

>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
class Chain(object):
    def __init__(self, path=''):
       self.__path = path

   def __getattr__(self, path):
       return Chain('%s/%s' % (self.__path, path))

   def __call__(self, path):
       return Chain('%s/%s' % (self.__path, path))

   def __str__(self):
       return self.__path

   __repr__ = __str__

   print(Chain().users('michael').repos) # /users/michael/repos

Step 1:
Chain() #实例化
Step 2:
Chain().users
由于没有给实例传入初始化对应属性的具体信息,从而自动调用__getattr__()函数,从而有:
Chain().users = Chain(’\users’) #这是重建实例
Step 3:
Chain().users(‘michael’)
Chain().users(‘michael’) = Chain(’\users’)(‘michael’) # 这是对实例直接调用,相当于调用普通函数一样
关键就在这步,上面的朋友没有说明晰(并不是说你们不懂),这一步返回的是Chain(’\users\michael’),再一次重建实例,覆盖掉Chain(’\users’),
#记 renew = Chain(’\users\michael’), 此时新实例的属性renew.__path = \users\michael;
Step 4:
Chain().users(‘michael’).repos
这一步是查询renew实例的属性repos,由于没有这一属性,就会执行__getattr__()函数,再一次返回新的实例Chain(’\users\michael\repos’)并且覆盖点之前的实例,
这里记 trinew =Chain(’\users\michael\repos’),不要忘了,一单定义了一个新的实例,就会执行__init__方法;
Step 5:
print(Chain().users(‘michael’).repos) = print(trinew)

由于我们定义了__str__()方法,那么打印的时候就会调用此方法,据此方法的定义,打印回来的是trinew的__path属性,即——\users\michael\repos 。至此,我们也把所有定义的有特殊用的方法都用上了,完毕。

枚举类

把一类成员从1开始编号。比如,在month中,从一月编号到十二月。

用法:导入Enum(从字面也可以理解,Enum即使数字化)模块后,使用其中的enum方法。

enum(枚举类名,(该枚举类的需要依次编号的元素))

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

这样,枚举类的值就不能改变了。

其实,一个枚举类就是所有元素的mappingproxy(即不可变键值对)的集合。只不过是自动编号,而且可以通过__members__方法对其进行一些处理。

可以利用@unique来检查是否有重复值

from enum import Enum, unique

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

这样如果在枚举的时候,会自动检测里面的数据是否重复。也就是,所有元素的编号必须不同。

使用元类

type()

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建

type()函数的作用:

  • 查看类或者实例的类型
  • 创建一个新的类(实际上class的创建也是运行时通过type()函数创建的)

查看类型:

>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'> # Hello是class,也就是一种类型
>>> print(type(h))
<class 'hello.Hello'> # 对象h是来自hello模块的Hello类型

创建类:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class,利用dictj
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

metaclass

metaclass是一个class创建时的基石。meta-这个词根,对应着汉语中的”元“。因此,metaclass可以翻译成基类或者是元类。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值