资讯专栏INFORMATION COLUMN

python学习笔记-魔术方法,让自定义类更像内置类型

changfeng1050 / 2396人阅读

摘要:的魔术方法是中那些预定义的像类型的函数。使用的魔术方法的最大优势在于提供了简单的方法让对象可以表现得像内置类型一样。廖雪峰老师教程里写的是方法,不知道为啥。

Python的魔术方法是Python中那些预定义的像__XXX__类型的函数。
使用Python的魔术方法的最大优势在于python提供了简单的方法让对象可以表现得像内置类型一样。

__str__函数

__str__函数用于处理打印实例本身的时候的输出内容。如果没有覆写该函数,则默认输出一个对象名称和内存地址。
例如:

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

输出:<__main__.Student object at 0x0000000002A929E8>.
那么我们如何让输出的结果可读性更高一点呢?我们可以覆写__str__函数。例如

>>> class Student(object):
...     def __init__(self, name):
...             self._name = name
...     def __str__(self):
...             return  "I"m a student, named %s" % self._name
...
>>> print Student("Charlie")

输出结果就是:I"m a student, named Charlie.
我们将str()函数作用于该对象的时候,其实是调用了该对象的__str__函数。

_repr_ 函数

__repr__也是将对象序列化,但是__repr__更多的是给python编译器看的。__str__更多的是可读性(readable)。
我们将repr()函数作用于摸某一个对象的时候,调用的其实就是该函数的__repr__函数。

repr()成对的是eval()函数。eval()函数是将序列化后的对象重新转为对象。前提是该对象实现了__repr__函数。

上面这一段话基于自己的理解,不知道对错。

>>> item = [1,2,3]
>>> repr(item)
"[1, 2, 3]"
>>> other_item = eval(repr(item))
>>> other_item[1]
2
__iter__函数

我们经常对list或者tuple使用for...in...来迭代。那是list继承自Iterable。Iterable实现了__iter__函数。

要想将一个自定义的对象变成一个可迭代的对象,那么必须要实现两个方法:__iter__next.

__iter__函数返回一个对象。迭代的时候则会不断地调用next函数拿到下一个值,直到捕获到StopIteration停止。
廖雪峰老师教程里写的是__next__方法,不知道为啥。

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 > 10000:
            raise StopIteration
        return self.a


for i in Fib():
    print i
__getitem__函数

上面通过实现__iter__函数实现对象的迭代。
那么如何实现对象按下标取出元素呢。
这是通过实现对象的__getitem__方法。
我们来举一个?子。我们新建了一个类MyList,我们要办它实现普通list的一些功能,比如(1)根据下标获取值;(2)正数顺序单步长切片 (3)任意步长切片

class MyList(object):
    def __init__(self, *args):
        self.numbers = args

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


my_list = MyList(1, 2, 3, 4, 6, 5, 3)
print my_list[2]

当然,上面实现了根据下标获取值。但是这还不够。我们还需要实现切片功能。例如my_list[1:3].
我们对对象进行切片操作的时候,调用的气势也是__getitem__函数。此时,该函数获取到的并不是int对象,而是slice对象。
例如下面的代码

class MyList(object):
    def __init__(self, *args):
        self.numbers = args

    def __getitem__(self, item):
        if isinstance(item, int):
            return self.numbers[item]
        elif isinstance(item, slice):
            # 写习惯了其他语言,差点忘记了三元运算符的格式了,吼吼吼。
            # 下面句三元运算符的意思是,若为空,则为切片从0开始。
            start = item.start if item.start is not None else 0
            # 下面句三元运算符的意思是,若为空,则为切片到最末端结束。
            stop = item.stop if item.stop is not None else len(self.numbers)
            return self.numbers[start:stop]


my_list = MyList(1, 2, 3, 4, 6, 5, 3)
print my_list[2:5]

上面的代码终于实现了切片功能,但是还没考虑负数呢。那么我们加一把劲再来改一下。代码如下:

class MyList(object):
    def __init__(self, *args):
        self.numbers = args

    def __getitem__(self, item):
        if isinstance(item, int):
            return self.numbers[item]
        elif isinstance(item, slice):
            start = item.start if item.start is not None else 0
            stop = item.stop if item.stop is not None else len(self.numbers)

            length = len(self.numbers)
            start = length + start + 1 if start < 0 else start
            stop = length + stop + 1 if stop < 0 else stop
            return self.numbers[start:stop]

my_list = MyList(1, 2, 3, 4, 6, 5, 3)
print my_list[1:-1]

哇塞,写完了,棒棒棒

_getattar_

在调用某一个对象不存在的属性或者方法的时候,会抛出一个一个AttributeError错误。
但是如果我们实现了类中的魔术方法__getattar__,那么在调用不存在的属性或者方法的时候,就会调用该魔术方法。

class Apple(object):
    def __getattr__(self, item):
        if item == "attar1":
            return "print"
        if item == "method1":
            return lambda x: "hello %s" % x


apple = Apple()
print apple.attar1
print apple.method1

__getattar__函数一个重要的适用场景就是实现链式调用。例如我们在调用某一个api的时候:

GET users/articles/index

那么我们就希望我们的代码可以实现`Api.users.articles.index这么调用。
思考一下,要实现链式调用,最重要的就是每一个调用都是返回一个实例~~。

# coding=utf-8
class Api(object):
    def __init__(self, path=""):
        self._path = path

    def __getattr__(self, name):
        return Api("%s/%s" % (self._path, name))

    # 定义一个Post方法来发送请求
    def post(self):
        print self._path


api = Api()
api.user.articles.index.post()

廖雪峰在他的教程中给我们出了一个题目:
例如调用github的api:users/:user/repos一样,中间的user名需要动态替换。
我们希望能api.users("charlie").repos这么调用。那么代码该如何实现呢?这可能需要用到另一个方法__call__

_call_ 函数

一个对象既有属性,又有方法。我们在调用一个实例的方法的时候,我们可以使用instance.method()的形式调用。
其实也可以将实例本身看成一个函数用来调用,我们需要做的就是实现__call__函数本身。

class Apple(object):
    def __call__(self, *args, **kwargs):
        return args


apple = Apple()
print apple("yes", "no")

此时我们再来看一下上面提到的实现api.users("charlie").repos链式调用的方法。

# coding=utf-8
class Api(object):
    def __init__(self, path=""):
        self._path = path

    def __getattr__(self, name):
        return Api("%s/%s" % (self._path, name))

    def __call__(self, args):
        self._path = "%s/%s" % (self._path, args)
        return Api(self._path)

    # 定义一个Post方法来发送请求
    def post(self):
        print self._path


api = Api()
api.users("Charlie").index.post()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38334.html

相关文章

  • 聊聊技术写作的个人体会

    摘要:由此看来,的官方文档就把当成内置函数,这个认识错误是有根源的等到的时候,官方把错误改正过来了,然而改得并不彻底。使用进行判断,结果为的才是内置函数。 showImg(https://segmentfault.com/img/bVbm3Bu?w=5184&h=3456);有群友问过,是什么原因使我开始写技术公众号,又是什么动力让我坚持写的。 在我看来,写作是一件不能敷衍的事,通过写作来学...

    madthumb 评论0 收藏0
  • Python学习之路28-符合Python风格的对象

    摘要:本篇继续学习之路,实现更多的特殊方法以让自定义类的行为跟真正的对象一样。之所以要让向量不可变,是因为我们在计算向量的哈希值时需要用到和的哈希值,如果这两个值可变,那向量的哈希值就能随时变化,这将不是一个可散列的对象。 《流畅的Python》笔记。本篇是面向对象惯用方法的第二篇。前一篇讲的是内置对象的结构和行为,本篇则是自定义对象。本篇继续Python学习之路20,实现更多的特殊方法以让...

    Eric 评论0 收藏0
  • es6学习笔记-Symbol_v1.0_byKL

    摘要:它是语言的第七种数据类型前六种是布尔值字符串数值对象。为了防止冲突这就是引入的原因。指向了这个内部方法调用了返回对象的属性等于一个布尔值,表示该对象使用时,是否可以展开。数组的默认行为是可以展开返回对象的属性,指向当前对象的构造函数。 es6学习笔记-Symbol_v1.0 基本抄了一次内容,有很多只是知道其然并不知其所以然,不过也算是加深了一次印象,另外每段代码我都有手动执行过. E...

    Lowky 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<