python的魔法方法2

python的魔法方法2

getattr

我们先看下面的代码

class A:
    def __init__(self, age):
        self.age = age


o = A(18)
print(o.age)
print("============")
print(o.score)

结果

18
============
Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in <module>
    print(o.score)
AttributeError: 'A' object has no attribute 'score'

我们发现当我们使用o.age的时候,实际上是在尝试access这个对象的一个属性,但是当这个属性在这个对象不存在的时候,就报错了,AttributeError,告诉咱们,没有这个属性啊。
那attr就是帮我们来做这个默认值的,也就是说,我们访问这个对象不存在的属性,我们不想让他报错了,想让他返回其他东西。

class A:
    def __init__(self, age):
        self.age = age

    def __getattr__(self, name):
        print(f"getting:{name}")
        raise AttributeError


o = A(18)
print(o.age)
print("============")
print(o.score)

结果

18
============
getting:score
Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
    print(o.score)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 7, in __getattr__
    raise AttributeError
AttributeError

首先呢,我们定义了attr方法,使其当访问这个对象不存在的属性的时候打印了getting:不存在属性的string,然后又手动抛出了一个AttributeError。当我们使用o.score来访问一个o对象不存在的属性的时候,就会请求到attr上,然后attr会将o.score的score最为string参数传递给attr函数。
比如说,当我们希望有人访问了这个对象不存在的属性的时候不报错,而是返回一个None,当然,这并不是很合理的行为,我们只是用来做演示。

class A:
    def __init__(self, age):
        self.age = age

    def __getattr__(self, name):
        print(f"getting:{name}")
        return None

o = A(18)
print(o.age)
print("============")
print(o.score)

结果

18
============
getting:score
None

getattribute

这个函数和getattr很像哈,甚至一个就是另一个的缩写,所以python在函数命名上确实有的地方不是很合理,上面的那个attr函数是只有在这个属性不存在的时候才会被调用,而getattribute这个函数,是只要你尝试去读取这个对象的属性,它都会被调用

class A:
    def __init__(self, age):
        self.age = age

    def __getattribute__(self, name):
        print(f"getting:{name}")
        return name

o = A(18)
print(o.age)
print("============")
print(o.score)

结果

getting:age
age
============
getting:score
score

我们发现不管这个属性有没有,attribute函数都被调用了,当然现在这个attribute函数没什么意义对吧,那我们就写一个稍微有意义的例子

class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __getattribute__(self, name):
        if name == 'data':
            self.counter += 1
        return super().__getattribute__(name)


o = A()
print(o.data)
print(o.data)
print(o.counter)

结果

abc
abc
2

从结果上来看,这个data确实被访问了两遍,但是大家一定要特别注意这一行

 return super().__getattribute__(name)

当你使用getattribute的时候,如果你想使用它的默认行为,一定要像上面那么些,因为这里如果不小心的话,就会产生一个无限递归,比如刚刚我们学了getattr这个函数对吧,我默认返回一个getattr不就可以了么

class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __getattribute__(self, name):
        if name == 'data':
            self.counter += 1
        return getattr(self, name)


o = A()
print(o.data)
print(o.data)
print(o.counter)

这样的话就会产生一个无限递归

Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
    print(o.data)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 8, in __getattribute__
    self.counter += 1
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded

因为getattr函数又会反过来执行getattribute函数,同时我们也要注意到还有一个递归的地方

self.counter += 1

你要读取self.counter对不对,当你读取他的时候,就调用了getattribute,
比如说你心血来潮的说,想要数一下这个对象里所有的属性被读取的次数

class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __getattribute__(self, name):
        self.counter += 1
        return super().__getattribute__(name)

o = A()
print(o.data)
print(o.data)
print(o.counter)

结果

/Users/yinzhang/PycharmProjects/MyTest/venv/bin/python /Users/yinzhang/PycharmProjects/MyTest/test1.py 
Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 13, in <module>
    print(o.data)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 8, in __getattribute__
    self.counter += 1
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 9, in __getattribute__
    return getattr(self, name)
  [Previous line repeated 496 more times]
RecursionError: maximum recursion depth exceeded

Process finished with exit code 1

又无限递归了,因为你每一次+counter的时候,就要读一次counter,然后又要+counter,就一直递归下去了。

setattr

刚刚说完get了,现在就要说set了,当我们尝试去写一个属性的时候,就要用到setattr,他有两个参数,一个key一个是value,当我们做self.data="abc"的时候,data就作为了一个string传给了key,然后abc就是这个value,同样的,我们会使用

super().__setattr__(self,key,value)

来完成他的默认行为,里面和get的一样,都是pass,什么都没操作,当然你也可以用

object.__setattr__(self,key,value)

其实get的也可以

class A:
    def __init__(self):
        self.data = 'abc' # 这里就会调用第一次__setattr__ ,打印出了:set data
        self.counter = 0  # 这里会调用第二次__setattr__,打印出了:set counter

    def __setattr__(self, key, value):
        print(f"set {key}")
        super().__setattr__(key, value)


o = A()
print(o.data) # 打印出了abc
o.data = 'def'  # 这里就会调用第三次__setattr__ ,打印出了:set data
print(o.data) # 打印出了data新的值:def
o.mess = 'xyz' # 这里就会调用第四次__setattr__ ,打印出了:set mess ,哪怕对象没有这个属性呢
print(o.mess) # 打印出了mess的值: xyz

结果

set data
set counter
abc
set data
def
set mess
xyz

delattr

新增有了,查询有了,删除这就来了,这个delattr和之前我们提到的del是不一样的,当一个object消亡的时候并不会调用delattr的,只有当尝试删除一个object的属性的时候才会被调用。

class A:
    def __init__(self):
        self.data = 'abc'
        self.counter = 0

    def __delattr__(self, item):
        print(f"del {item}")
        super().__delattr__(item)


o = A()
print("=============")
del o.data # 使用del关键词删除某一个对象的属性
print(o.data) # 再打印就会报错了

结果

=============
del data
Traceback (most recent call last):
  File "/Users/yinzhang/PycharmProjects/MyTest/test1.py", line 14, in <module>
    print(o.data) # 再打印就会报错了
AttributeError: 'A' object has no attribute 'data'

dir

我们知道我们有一个挺长用的内置方法,叫dir,它可以帮我们打印出这个object可以access的一些属性,打印出来

class A:
    def __init__(self):
        self.data = 'abc'


o = A()
print(dir(o))

结果

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'data']

除了那些内置的魔法函数,还有我们自己定义的,比如说data,我们可以在一个class里面通过定义一个dir魔法函数,来改变内置的dir的结果,python官方要求dir方法必须返回一个sequence[序列],那最常见的sequence就是list,比如我们自己定义一下

class A:
    def __init__(self):
        self.data = 'abc'

    def __dir__(self):
        return []


o = A()
print(dir(o)) 

打印的结果就是一个空的list:[]
那我们写一个有意义的,我们拿到正常的dir,然后我们去掉所有下划线开头的属性,只留下我们自己定义的属性

class A:
    def __init__(self):
        self.data = 'abc'

    def __dir__(self):
        lst = super().__dir__()
        return [el for el in lst if not el.startswith("_")]


o = A()
print(dir(o))

结果

['data']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值