Python基础——面向对象编程

时间:2021-09-12 10:08:55

1. __slots__

正常情况下当我们定义了一个class,然后创建了一个instance之后,我们可以给该实例绑定任何属性和方法:

from types import MethodType


class Student:
    pass


# 创建一个Student实例
stu1 = Student()
# 给实例动态绑定一个属性
stu1.name = 'yxh'


def set_age(self, age):
    # 定义一个函数
    self.age = age


# 给实例动态绑定一个方法
stu1.set_age = MethodType(set_age, stu1)
print('his name: {}'.format(stu1.name))
# print(stu1.age)
# AttributeError: 'Student' object has no attribute 'age'
# 错误原因:在没有调用stu1.set_age之前,stu1是没有age属性的
stu1.set_age(17)
print('his age: {}'.format(stu1.age))

考虑一种情况,出于某种原因,我们怎样才能限制实例的属性?比如说只允许对Student实例添加name、age属性,不允许再多余添加其他属性:在定义类时,定义一个特殊的__slots__属性:

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


stu = Student()
stu.name = 'yxh'     # 绑定name属性
stu.age = 17    # 绑定age属性
stu.gf = 'sdy'      # 尝试绑定多余的gf属性
# AttributeError: 'Student' object has no attribute 'gf'


def set_age(self, age):
    self.age = age


stu.set_age = MethodType(set_age, stu)
# AttributeError: 'Student' object has no attribute 'set_age'

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

2. @property

其主要用处是更为方便的调用getter和setter,@property的使用比较简单,直接上代码:

class Student:
    def __init__(self):
        self.__age = 0

    @property
    def get_age(self):
        print('get_age')
        return self.__age

    @get_age.setter
    def get_age(self, age):
        if not isinstance(age, int):
            raise ValueError('age must be an integer.')
        if age < 0 or age > 120:
            raise ValueError('age must between 0 - 120')
        print('nimeifude')
        self.__age = age


stu = Student()
stu.get_age = 20    # 其本质是stu.set_age(20)
print(stu.get_age)    # 其本质是print(stu.get_age())

3. 利用python的class中的一些特殊用途的函数定制类

__len__

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

    def __len__(self):
        return len(self.__name)


stu1 = Student('yxh', 18)
stu2 = Student('sundy', 17)
print(len(stu1))
print(len(stu2))

__str__

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

    def __str__(self):
        return 'Student object (name: {})'.format(self.__name)


print(Student('yxh', 18))
# Student object (name: yxh)

__iter__:如果一个类想被用于for...in循环,类似于list、tuple,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后for循环就会不断调用该迭代对象的__next__()方法,拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

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

    def __iter__(self):
        return self

    def __next__(self):
        if len(self.__score) == 0:
            raise StopIteration()
        score = self.__score.pop()
        return score


for score in Student('yxh', [90, 97, 93, 91, 89, 81, 87]):
    print(score)

__getitem__按照下标取出某个元素;实现简单的切片功能

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

    def __getitem__(self, item):
        return self.__score[item]


print(Student('yxh', [90, 97, 93, 91, 89, 81, 87])[3])
class Fibonacci:
    def __getitem__(self, item):
        a, b = 1, 1
        for i in range(item):
            a, b = b, a + b
        return a


for i in range(17):
    print(Fibonacci()[i])
class Fibonacci:
    def __getitem__(self, item):
        if isinstance(item, int):
            a, b = 1, 1
            for i in range(item):
                a, b = b, a + b
            return a
        if isinstance(item, slice):
            start = item.start
            stop = item.stop
            print(start, stop)
            if start is None:
                start = 0
            if stop is None:
                stop = 7
            a, b = 1, 1
            fib_lst = []
            for i in range(stop):
                if i >= start:
                    fib_lst.append(a)
                a, b = b, a + b
            return fib_lst


print(Fibonacci()[3:])

__getattr__:正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。

class Student(object):

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

    def __getattr__(self, 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__,已有的属性,不会在__getattr__中查找

__call__

一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本身上调用呢?在Python中,答案是肯定的。任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。

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

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

调用方式如下:

>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例。

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

4. 枚举类

直接上代码:

>>> from enum import Enum
>>> Week = Enum('Week', ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))
>>> for name, member in Week.__members__.items():
...     print(name, '->', member, ':', member.value)
...
Sunday -> Week.Sunday : 1
Monday -> Week.Monday : 2
Tuesday -> Week.Tuesday : 3
Wednesday -> Week.Wednesday : 4
Thursday -> Week.Thursday : 5
Friday -> Week.Friday : 6
Saturday -> Week.Saturday : 7

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

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
@unique装饰器可以帮助我们检查保证没有重复值。
访问这些枚举类型可以有若干种方法:
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon

5. 元类

Supplement Later