零基础学习Python(四)

发布于:2024-07-26 ⋅ 阅读:(100) ⋅ 点赞:(0)

1. __getitem__、__setitem__、__iter__、__next__魔法方法

__index__方法是对象被作为索引访问时调用的魔法方法,那么当对象要进行索引访问时,调用什么魔法方法呢?答案是__getitem__魔法方法。

class C:
    def __getitem__(self, index):
        print(index)

当索引位切片时,打印的是一个内置函数slice,也就是说,slice函数相当于一个切片:

使用[slice(2, 6)]进行索引访问,等同于[2:6],左闭右开。类似的,s[7:]相当于s[slice(7, None)],s[::4]相当于s[slice(None, None, 4)]。

根据索引或者切片进行赋值的操作会调用__setitem__魔法方法:

class D:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]

    def __setitem__(self, index, value):
        self.data[index] = value

如果通过for循环获取数据,那么也会调用__getitem__方法:

class D:
   def  __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index] * 2
d = D([1, 2, 3, 4, 5])
for i in d:
    print(i, end=' ')

上述例子中for循环最终会调用到__getitem__魔法方法,其实并不是一定的,前提是对象没有__iter__魔法方法和__next__魔法方法。在Python中,如果定义了__iter__魔法方法,则认为是可迭代对象,如果一个可迭代对象定义了__next__魔法方法,则认为是一个迭代器。比如列表,是一个可迭代对象,但因为没有__next__方法,所以不是迭代器。for循环所做的事,就是将对象传给内置函数iter()获取一个迭代器,然后再去调用迭代器的__next__方法实现遍历。例如以下for循环:

x = [1, 2, 3, 4, 5]
for i in x:
    print(i, end=' ')

等价于:

_ = iter(x)
while True:
    try:
        i = _.__next__() 
    except StopIteration:
        break
    print(i, end=' ')

自定义一个迭代器,根据传入的起始位置和终止位置,计算区间内的数乘以2:

class Double:
    def __init__(self, start, stop)
        self.value = start - 1
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value * 2

2. __contains__、__bool__、__len__魔法方法

使用运算符in或者not in就会触发__contains__魔法方法:

class C:
    def __init__(self, data):
        self.data = data

    def __contains__(self, item):
        print("嗨~")
        return item in self.data

如果没有__contains__魔法方法,使用了in或者not in运算符,那么Python就会去触发__iter__和__next__魔法方法:

class C:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        print("Iter", end='->')
        self.i = 0
        return self

    def __next__(self):
        if self.i == len(self.data):
            raise StopIteration
        print("Next", end='->')
        item = self.data[self.i]
        self.i += 1
        return item

使用bool函数会触发__bool__魔法方法:

class D:
    def __bool__(self):
        print("Bool")
        return True

如果没有实现__bool__魔法方法,则会去调用__len__魔法方法:

class D:
    def __init__(self, data):
        self.data = data

    def __len__(self):
        print("Len")
        return len(self.data)

如果不想要某个魔法方法不生效,直接赋值为None,比如__contains__赋值为None,使用in或者not in运算符就会报错,而不是去调用__iter__和__next__魔法方法:

class C:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        print("Iter", end='->')
        self.i = 0
        return self

    def __next__(self):
        if self.i == len(self.data):
            raise StopIteration
        print("Next", end='->')
        item = self.data[self.i]
        self.i += 1
        return item
    
    def __contains__ = None

3. __str__、__repr__魔法方法

__str__魔法方法和__repr__魔法方法分别对应str()和repr()这两个函数,虽然在大多数情况下他们得到的结果是一样的,但是本质上str的结果是给人看的,repr的结果是给程序看的:

可以看到对于字符串,repr函数的结果会包一层引号,为什么会包一层引号,就是为了给eval函数解析用的。将str函数的结果传给eval函数,会报错,因为程序会去寻找FishC这个变量,因为没找到所以报错,而repr的结果传给eval函数,程序会认为'FishC'是一个字符串,从而正确识别。

所以eval函数被认为是repr的反函数,即传一个参数给repr函数,然后再将结果传给eval函数,最终会得到参数本身:

如果没有定义__str__魔法方法,调用str函数时就会去触发__repr__魔法方法:

class C:
    def __repr__(self):
        return 'I love FishC'

但如果实现了__str__魔法方法,但是没有实现__repr__魔法方法,调用repr函数时,是不会触发__str__魔法方法的:

class C:
    def __str__(self):
        return 'I love FishC'

如果只定义了__str__魔法方法,没有定义__repr__魔法方法,打印对象列表,是不会触发到__str__魔法方法的,打印的结果是对象的地址:

但是反过来,如果只定义了__repr__魔法方法,没有定义__str__魔法方法,打印对象列表,则可以触发__repr__魔法方法:

使用__str__方法和__repr__方法实现对象在不同场景下的不同显示效果:

class C:
    def __init__(self, data):
        self.data = data

    def __str__(self):
        return f'data = {self.data}'
   
    def __repr__(self):
        return f'C({self.data})'

    def __add__(self, other):
        self.data += other

4. property函数和装饰器

使用property函数,可以将变量由另一个变量全权代理:

class C:
    def __init__(self):
        self._x = 250

    def getx(self):
        return self._x
   
    def setx(self, x):
        self._x = x

    def delx(self):
        del self._x

    x = property(getx, setx, delx)

property函数的参数是函数,所以property经常被当做装饰器来使用,如果一个get函数使用property函数来修饰,则可以将函数名等价于变量名来使用:

class E:
    def __init__(self):
        self._x = 250

    @property
    def x(self):
        return self._x

这样也就实现了只读的对象属性。为了使对象可写可删,还需实现对象名.setter和deleter方法:

class E:
    def __init__(self):
        self._x = 250

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

5. 描述符以及用描述符实现property函数

描述符定义:如果实现了__get__、__set__、__delete__这三个方法其中任意一个或者多个,那么这个类就可以称为描述符。描述符可不是管理本类对象的属性,是用来管理其他类的属性,类似于property函数:

class D:
    def __get__(self, instance, owner):
        print(f"get~\nself -> {self}\ninstance -> {instance}\nowner -> {owner}")

    def __set__(self, instance, value):
        print(f"set~\nself -> {self}\ninstance -> {instance}\nvalue -> {value}")

    def __delete__(self, instance):
        print(f"delete~\nself -> {self}\ninstance -> {instance}")

class C:
    x = D()

可以看到,self参数对应的是描述符类的对象,instance参数对应的是被描述符所拦截的属性所在的类的对象,owne参数对应的是描述符所拦截的属性所在的类。使用描述符实现上述property函数的代码"x = property(getx, setx, delx)":

class D:
    def __get__(self, instance, owner):
        return instance._x

    def __set__(self, instance, value):
        instance._x = value

    def __delete__(self, instance):
        del instance._x

class C:
    def __init(self, x=250):
        self._x = x
    x = D()

 

使用描述符实现property函数(这里定义位MyProperty,功能与property一样):

class MyProperty:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
    
    def __get__(self, instance, owner):
        return self.fget(instance)

    def __set__(self, instance, value):
        self.fset(instance, value)

    def __delete__(self, instance):
        self.fdel(instance)

class C:
    def __init(self, x=250):
        self._x = x

    def getx(self):
        return self._x

    def setx(self, value):
        self._x = value
    
    def delx(self):
        del self._x

    x = MyProperty(getx, setx, delx)

 

实现装饰器功能:

class MyProperty:
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
    
    def __get__(self, instance, owner):
        return self.fget(instance)

    def __set__(self, instance, value):
        self.fset(instance, value)

    def __delete__(self, instance):
        self.fdel(instance)

    def getter(self, func):
        self.fget = func
        return self

    def setter(self, func):
        self.fset = func
        return self

    def deleter(self, func):
        self.fdel = func
        return self

class D:
    def __init(self, x=250):
        self._x = x

    @MyProperty
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value
    
    @x.deleter
    def x(self):
        del self._x

 


网站公告

今日签到

点亮在社区的每一天
去签到