python中的self&描述符__set__和__get__&简单总结

1. python中的self用法总结

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
  • __init__方法的第一参数永远是self,表示创建的类实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
  • 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器会自己把实例变量传进去。
  • 和普通数相比,在类中定义函数只有一点不同,就是第一参数永远是类的本身实例变量self,并且调用时,不用传递该参数。除此之外,类的方法(函数)和普通函数没啥区别,你既可以用默认参数、可变参数或者关键字参数(*args是可变参数,args接收的是一个tuple,**kw是关键字参数,kw接收的是一个dict)
  • 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
  • 需要注意的是,在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名
  • 有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

下面是self的一些详细用法:

(1)self代表类的实例,而非类

In [19]: class Test:
    ...:     def ppr(self):
    ...:         print(self)
    ...:         print(self.__class__)
    ...:  

In [20]: t = Test()

In [21]: t.ppr()
<__main__.Test instance at 0x000000000D3D4E88>
__main__.Test

self代表的是类的实例。而self.__class__则指向类。
注意:把self换成this,结果也一样,但Python中最好用约定俗成的self。

(2)self可以不写吗
在Python解释器的内部,当我们调用t.ppr()时,实际上Python解释成Test.ppr(t),也就是把self替换成了类的实例。

In [22]: class Test:
    ...:     def ppr():
    ...:         print(self)
    ...:     

In [23]: t=Test()

In [24]: t.ppr()
Traceback (most recent call last):

  File "<ipython-input-24-178e9dc94925>", line 1, in <module>
    t.ppr()

TypeError: ppr() takes no arguments (1 given)

这里实际上已经部分说明了self在定义时不可以省略。

当然,如果我们的定义和调用时均不传类实例是可以的,这就是类方法。

>>> class Test:
    def ppr():
        print(__class__)


>>> Test.ppr()
<class '__main__.Test'>
>>> 

但这里要注意下:对于python 2.7版本来说会报错,必须先创建对象。

In [31]: class Test:
    ...:     def ppr():
    ...:         print('i am here')
    ...:         

In [32]: Test.ppr()
Traceback (most recent call last):

  File "<ipython-input-32-f7a5b2f2ccd9>", line 1, in <module>
    Test.ppr()

TypeError: unbound method ppr() must be called with Test instance as first argument (got nothing instead)

(3)在继承时,传入的是哪个实例,就是那个传入的实例,而不是指定义了self的类的实例

>>> class Parent:
    def pprt(self):print(self)


>>> class Parent:
    def pprt(self):
        print(self)


>>> class Child(Parent):
    def cprt(self):
        print(self)


>>> c=Child()
>>> c.cprt()
<__main__.Child object at 0x00000244B8769860>
>>> c.pprt()
<__main__.Child object at 0x00000244B8769860>
>>> p = Parent()
>>> p.pprt()
<__main__.Parent object at 0x00000244B8769A58>
>>> 

(4)在描述符类中,self指的是描述符类的实例

class Desc2:
    def __get__(self, ins, cls):
        print ('self in Desc: %s ' % self )
        print (self, ins, cls)

class Test2:
    x = Desc2() # 而该属性是描述符属性,为Desc类的实例
    def prt(self):
        print ('self in Test: %s' % self)

t2 = Test2()
t2.prt()
t2.x # 这里是调用类属性
Test2.x # 注意和上面一行的区别

运行结果:

self in Test: <__main__.Test2 object at 0x00000239CE269128>
self in Desc: <__main__.Desc2 object at 0x00000239CE2690B8> 
<__main__.Desc2 object at 0x00000239CE2690B8> <__main__.Test2 object at 0x00000239CE269128> <class '__main__.Test2'>
self in Desc: <__main__.Desc2 object at 0x00000239CE2690B8> 
<__main__.Desc2 object at 0x00000239CE2690B8> None <class '__main__.Test2'>

由于在很多时候描述符类中仍然需要知道调用该描述符的实例是谁,所以在描述符类中存在第二个参数ins,用来表示调用它的类实例,所以t.x时可以看到第三行中的运行结果中第二项为<__main__.Test2 object at 0x00000239CE269128>

2. 描述符__set____get__ 的用法

可以先参考下:https://blog.csdn.net/lilong117194/article/details/80111803

上面有疑问的地方应该是__get__(self, ins, cls),下面总结一下:
首先说下python中存在的几种方法:对象方法、静态方法、类方法等,归属权分别为obj、cls、cls
其实可以从他们的参数中就可以看的出来,静态方法使用@staticmethod来修饰,可以通过类或类的实例对象来调用而已。

class A(object):
      def foo1(self):
          print ("Hello",self)
      @staticmethod
      def foo2():
          print ("hello")
      @classmethod
      def foo3(cls):
          print ("hello",cls)  # cls就是类A本身 
a = A()
a.foo1()

A.foo1(a)   #这里传入实例a,相当于普通方法的self

A.foo2()    #这里,由于静态方法没有参数,故可以不传东西

A.foo3()    #这里,由于是类方法,因此,它的第一个参数为类本身。

print (A)     #可以看到,直接输入A,与上面那种调用返回同样的信息

结果:

Hello <__main__.A object at 0x0000016D1774B860>
Hello <__main__.A object at 0x0000016D1774B860>
hello
hello <class '__main__.A'>
<class '__main__.A'>

Python中,对象的方法也是也可以认为是属性,所以下面所说的属性包含方法在内。
看一个实例:

class T(object):
    name = 'name'
    def hello(self):
        print ("hello")
t = T()
print ('dir(t):',dir(t))
print ('t.__class__:',t.__class__)
print ('t.__dict__:',t.__dict__) 
print ('T.__dict__:',dict(T.__dict__)) # T.__dict__并没有直接返回dict对象,这里转换为字典

print ('t.name:',t.name)
print ('T.name:',T.name)
print ('T.dict[‘name’]:',T.__dict__['name'])

print ('T.hello:',T.hello) # 是个unbound method
print ("T.__dict__['hello']:",T.__dict__['hello']) # 是个function
print ('t.hello:',t.hello) # 是个bound method

运行结果:

dir(t): ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'hello', 'name']
t.__class__: <class '__main__.T'>
t.__dict__: {}
T.__dict__: {'name': 'name', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'T' objects>, '__module__': '__main__', 'hello': <function T.hello at 0x000002BB9572B400>, '__dict__': <attribute '__dict__' of 'T' objects>}
t.name: name
T.name: name
T.dict[‘name’]: name
T.hello: <function T.hello at 0x000002BB9572B400>
T.__dict__['hello']: <function T.hello at 0x000002BB9572B400>
t.hello: <bound method T.hello of <__main__.T object at 0x000002BB97B49860>>

注意:

  • 有些内建类型,如list和string,它们没有dict属性,随意没办法在它们上面附加自定义属性
  • t.__dict__: {}:到现在为止t.dict是一个空的字典,因为我们并没有在t上自定义任何属性,它的有效属性hello和name都是从T得到的。

t.name的查找过程:

  • 首先,Python判断name属性是否是个自动产生的属性,如果是自动产生的属性,就按特别的方法找到这个属性,当然,这里的name不是自动产生的属性,而是我们自己定义的,Python于是到t的dict中寻找。还是没找到。
  • 接着,Python找到了t所属的类T,搜索T.dict,找到后返回name的值:字符串‘name’。如果在T.dict中还没有找到,Python会接着到T的父类(如果T有父类的话)的dict中继续查找。

t.hello

  • 方法在类的dict中是以函数的形式存在的
  • 关于unbound和bound可以这样:方法是要从实例调用的,如果从类中访问,如T.hello,hello没有和任何实例发生联系,也就是没绑定(unbound)到任何实例上,所以是个unbound,对t.hello的访问方式,hello和t发生了联系,因此是bound。

下面再看一个例子:


class Descriptor(object):
    def __get__(self, obj, type=None):
            return 'get', self, obj, type
    def __set__(self, obj, val):
        print ('set', self, obj, val)
    def __delete__(self, obj):
        print ('delete', self, obj)


class T(object):
    # d是T的类属性,作为Descriptor的实例,它有get等方法,是一个描述符
    d = Descriptor() 


t = T()# T是一个类,t是它的一个实例,d是T的一个descriptor属性
print (' t.d:', t.d) # t.d,返回的实际是d.__get__(t, T)
print (' T.d:', T.d) # T.d,返回的实际是d.__get__(None, T),所以obj的位置为None
t.d = 'hello' # 在实例上对descriptor设置值,在__set__方法中print语句输出的。
print (' t.d:', t.d) # 可见,调用了Python调用了__set__方法,并没有改变t.d的值
T.d = 'hello' # 没有调用__set__方法
print (' T.d:', T.d)
# t.d的值也变了,这可以理解,按我们上面说的属性查找策略,t.d是从T.__dict__中得到的T.__dict__['d']的值是'hello',t.d当然也是'hello'
print (' t.d:', t.d)

运行结果:

t.d: ('get', <__main__.Descriptor object at 0x000001F403DF9860>, <__main__.T object at 0x000001F403E5B6D8>, <class '__main__.T'>)
 T.d: ('get', <__main__.Descriptor object at 0x000001F403DF9860>, None, <class '__main__.T'>)
set <__main__.Descriptor object at 0x000001F403DF9860> <__main__.T object at 0x000001F403E5B6D8> hello
 t.d: ('get', <__main__.Descriptor object at 0x000001F403DF9860>, <__main__.T object at 0x000001F403E5B6D8>, <class '__main__.T'>)
 T.d: hello
 t.d: hello

这里需要说明几点:

  • self当然不用说,指的是当前Descriptor类的实例。obj值拥有属性的对象。这应该不难理解,前面已经说了,descriptor是对象的稍微有点特殊的属性,这里的obj就是拥有它的对象。要注意的是,如果是直接用类访问descriptor(descriptor是个属性,直接用类访问descriptor就是直接用类访问类的属性),obj是None,此时type就是类本身。
  • 读取属性时,如T.d,返回的是d.get(None, T)的结果,t.d返回的是d.get(t, T)的结果。
  • 设置属性时,t.d = value,实际上调用d.set(t, value)
  • T.d = value,这是真正的赋值,T.d的值从此变成value。删除属性和设置属性类似。

data descriptor和non-data descriptor:
象上面的d,同时具有get和set方法,这样的descriptor叫做data descriptor,如果只有get方法,则叫做non-data descriptor。容易想到,由于non-data descriptor没有set方法,所以在通过实例对属性赋值时,会直接赋值。

属性查找策略
1.如果attr是一个Python自动产生的属性,找到!(优先级非常高!)
2.查找obj.class.dict,如果attr存在并且是data descriptor,返回data descriptor的get方法的结果,如果没有继续在obj.class的父类以及祖先类中寻找data descriptor
3.在obj.dict中查找,这一步分两种情况,第一种情况是obj是一个普通实例,找到就直接返回,找不到进行下一步。第二种情况是obj是一个类,依次在obj和它的父类、祖先类的dict中查找,如果找到一个descriptor就返回descriptor的get方法的结果,否则直接返回attr。如果没有找到,进行下一步。
4.在obj.class.dict中查找,如果找到了一个descriptor(插一句:这里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的get方法的结果。如果找到一个普通属性,直接返回属性值。如果没找到,进行下一步。
5.很不幸,Python终于受不了。在这一步,它raise AttributeError

到这里已经差不多了,但还是有些不是理解的很透彻,以后慢慢再学习。

参考:https://blog.csdn.net/u014015972/article/details/51168268

  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python ,`__set__` 是一个特殊方法,用于定义属性的设置行为。它通常与 `__get__` 方法一起使用,以创建一个描述符(descriptor)。描述符是一种用于控制属性访问的高级特性。 `__set__` 方法定义了属性在被赋值时的行为。当我们给一个对象的属性赋值时,实际上是调用了这个属性的 `__set__` 方法。该方法接受三个参数:`self`(描述符自身实例),`instance`(拥有该属性的对象实例)和 `value`(要赋予属性的值)。 通过 `instance` 参数,我们可以在 `__set__` 方法获取到拥有该属性的对象实例。这样我们就能够在赋值操作发生时对对象实例进行操作,或者执行其他自定义逻辑。 下面是一个简单的示例,展示了如何使用 `__set__` 方法: ```python class Temperature: def __init__(self, celsius): self._celsius = celsius def __get__(self, instance, owner): return self._celsius def __set__(self, instance, value): if value < -273.15: raise ValueError("Temperature cannot be below absolute zero.") self._celsius = value class City: temperature = Temperature(25) my_city = City() print(my_city.temperature) # 输出: 25 my_city.temperature = 30 print(my_city.temperature) # 输出: 30 my_city.temperature = -300 # 抛出 ValueError 异常 ``` 在上面的示例,`Temperature` 类是一个描述符,它定义了温度属性的行为。当给温度属性赋值时,会调用 `__set__` 方法,检查温度是否低于绝对零度。如果低于绝对零度,则会抛出 `ValueError` 异常。否则,将新值保存到 `_celsius` 属性。 `City` 类的 `temperature` 属性使用了 `Temperature` 描述符。在创建 `City` 对象后,我们可以通过该对象访问和修改 `temperature` 属性的值。在修改属性值时,会调用 `Temperature` 描述符的 `__set__` 方法,进行相应的检查和操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值