guxh的python笔记六:类的属性
1,私有属性
class Foo: def __init__(self, x):
self.x = x
类的属性在实例化之后是可以更改的:
f = Foo(1)
print(f.x) # 1
f.x = 2
print(f.x) # 2
如果想禁止访问属性,即让属性私有,可以用“双下划线” 或者“单下划线”:
class Foo: def __init__(self, x, y):
self._x = x
self.__y = y def __repr__(self):
return 'f._x = {}, f.__y = {}'.format(self._x, self.__y)
区别是“双下划线”解释器会对属性就行“名称更改”,而“单下划线”不会有更改,只是约定成俗。
但是知道规则还是可以轻松访问:
f = Foo(1, 1)
f._x = 2
f._Foo__y = 2
print(f) # f._x = 2, f.__y = 2
所以如果只是为了属性私有,用“单下划线”和“双下划线”没有什么区别。
但“双下划线”在继承时可以避免基类属性被子类的属性覆盖,即想实现对子类的隐藏时,可以用双下划线。
2,只读属性
为类的方法添加@property,但不实现该属性对应property类的setter方法,就可以让该方法变成只读属性。
2.1,让实例化时的属性变成只读(私有属性 + @property)
私有属性 + property可以让属性变的只读:
class Foo: def __init__(self, x):
self._x = x @property
def x(self):
return self._x
此时对Foo进行实例化后,可以访问对象的属性x,但无法给x赋值:
f = Foo(1)
print(f.x) # 1
f.x = 2 # AttributeError: can't set attribute
因为实例化时的x存在私有属性self._x中,当然可以用1中提到的访问私有属性的方法去修改,但这被认为是粗略的行为:
f = Foo(1)
f._x= 2
print(f.x) # 2
如果__init__函数还是用x属性赋值,会有什么后果呢?
class Foo:
def __init__(self, x):
self.x = x @property
def x(self):
return 123 f = Foo(1) # AttributeError: can't set attribute
self.x会无法完成赋值,因为没有对应的x.setter方法。
2.2,把函数变属性 (@property)
把函数变属性,可以实现根据需要完成计算,从而无需在实例化时就完成计算。
class Circle:
def __init__(self, radius):
self.radius = radius @property
def perimeter(self):
print('computing') # 每次调用都会打印
return 2 * math.pi * self.radius c = Circle(10)
print(c.perimeter) # ‘computing’ , 62.83185307179586
print(c.perimeter) # ‘computing’ , 62.83185307179586
但存在问题是如果多次调用,就会多次计算。
如果在__init__函数中增加一条self.p = self.perimeter,然后访问实例的p属性,就不会多次计算,但这样又变成实例化时就完成了计算。
标准的做法是利用描述符将计算结果缓存起来:
class lazyproperty:
def __init__(self, func):
self.func = func def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance) # 计算被装饰函数的运算结果
setattr(instance, self.func.__name__, value) # 将运算结果缓存,保存为func的name
return value # 返回运算结果 class Circle:
def __init__(self, radius):
self.radius = radius @lazyproperty
def perimeter(self):
print('computing') # 每次调用都会打印
return 2 * math.pi * self.radius c = Circle(10)
print(c.perimeter) # ‘computing’ , 62.83185307179586
print(c.perimeter) # 62.83185307179586
3,属性管理
3.1,属性管理 :init
可以通过__init__完成属性管理:
class Foo: def __init__(self, x):
if x < 0:
raise ValueError('x must be >= 0')
self.x = x
看看有什么效果?
f = Foo(-1) # ValueError: x must be >= 0
f = Foo(1)
print(f.x) # 1
f.x = -1
print(f.x) # -1
发现实例化时能对属性进行管理,但是实例化后的对属性的操作就无法管理了。
3.2,属性管理:property
对属性的操作有:get,set,del。
property只是完成了get操作,剩下的get / del分配由setter / delettr完成,组合起来就可以实现对属性的管理:
class Foo: def __init__(self, x):
self.x = x @property
def x(self):
return self._x @x.setter
def x(self, val):
if val < 0:
raise ValueError('x must be >= 0')
self._x = val @x.deleter
def x(self):
del self.__dict__['_x']
再看看对x属性的访问情况:
f = Foo(1)
print(f.x) # 1
f.x = 2
print(f.x) # 2
f = Foo(-1) # ValueError: x must be >= 0
f.x = -1 # ValueError: x must be >= 0
del f.x
print(f.x) # AttributeError: 'Foo' object has no attribute '_x'
如果__init__中使用self._x = x会出现什么后果?
class Foo: def __init__(self, x):
self._x = x
...... f = Foo(-1)
print(c.x) # -1
f.x = -1 # ValueError: x must be >= 0
self.x = x在实例化时会触发set,如果没有set就无法完成赋值(参考2.只读属性中的例子)。
而self._x = x在实例化时绕过了set,也就是不会对实例化时的参数做检查。
另外,del可以不定义,del f.x会触发AttributeError: can't delete attribute,这里pass演示,del f.x没什么效果。
3.3,属性管理:property工厂函数
虽然内置的property经常用作装饰器,实际上是个类,python中函数和类可以互换,因为都是可调用对象。
property类完整的构造方法:property(fget=None, fset=None, fdel=None, doc=None)
下面不用property装饰器,而用经典形式property:
class Foo: def __init__(self, x):
self.x = x def qty_get(self):
return self._x # 如果return self.x,变无限递归,因为self.x就是从qty_get拿值的 def qty_set(self, val):
if val >= 0:
self._x = val
else:
raise ValueError('value must be >= 0') x = property(qty_get, qty_set) # 经典形式property
属性很多时,通过@property, setter代码会就大量增加,可以利用property工厂函数实现管理:
def quantity(attr): def qty_get(instance):
return instance.__dict__[attr] def qty_set(instance, val):
if val >= 0:
instance.__dict__[attr] = val
else:
raise ValueError('value must be >= 0') return property(qty_get, qty_set) class Foo:
x = quantity('x') def __init__(self, x):
self.x = x
可以接受参数,并且使用了getattr和setattr(避免使用__dict__)的工厂函数:
def typed_property(name, expected_type):
storage_name = '_' + name # 这里如果不换个名字,后面就不能用getattr和setattr,只能用__dict__,否则无限递归 def prop_get(instance):
return getattr(instance, storage_name) def prop_set(instance, value):
if not isinstance(value, expected_type):
raise TypeError('{} must be a {}'.format(name, expected_type))
setattr(instance, storage_name, value) return property(prop_get, prop_set) class Person:
name = typed_property('name', str)
def __init__(self, name):
self.name = name p = Person(123)
3.4,属性管理:描述符
描述符是个实现了特定协议的类,即实现了一些特殊方法的类,这些特殊方法包括__get__,__set__,__delete__。
之前使用的property类实现了完整的描述符协议。这里自定义描述符相当于抛弃了property,自己去定义实现一个类似property的类。
可以只实现一部分协议,大多数描述符只实现了__get__和__set__。
设置属性可以通过setattr(instance, name, value)或者instance.__dict__[name] = value,但需要注意有时用setattr会无限递归,有时些场景不适用dict,例如使用了__slots__或者描述符的类。
版本一:描述符声明时需要传入名称(不建议用)
class Quantity: def __init__(self, name):
self.name = name def __set__(self, instance, value):
if value >= 0:
instance.__dict__[self.name] = value # self是描述符Quantity的实例,instance是Foo的实例,所以需要对instance设置
# setattr(instance, self.name, value) # 这里如果使用setattr会无限递归
else:
raise ValueError('value must be >= 0') class Foo:
x = Quantity('x') def __init__(self, x):
self.x = x
版本二:描述符声明时无需传入名称
声明描述符时,需要明确指明Quantity实例的名称,不仅麻烦,还可能造成覆盖错误,可以对描述符进行改造,支持不需要输入名称。
class Quantity:
__counter = 0 def __init__(self):
cls = self.__class__ # 等效于type(self),获取类的引用
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1 def __get__(self, instance, value):
if instance is None:
return self # 通过类访问返回时,返回自身
else:
return getattr(instance, self.storage_name) def __set__(self, instance, value):
if value >= 0:
setattr(instance, self.storage_name, value) # 这里如果使用setattr不会无限递归,因为托管属性和存储属性名称不同
else:
raise ValueError('value must be >= 0') class Foo:
x = Quantity()
y = Quantity() def __init__(self, x, y):
self.x = x
self.y = y
版本三:多种属性管理时,有基类的描述符,可以实现描述符的参数输入
x属性和y属性的管理中,有很多是相同的,例如建立存储属性_counter#0,get访问属性等等,差异部分只是对输入值的验证。
可以提取相同部分作为基类,差异部分对输入值的验证由子类实现。
class Descriptor: # 基类
__counter = 0 # 类变量会被所有实例共享 def __init__(self, **opts): # 因为MaxSized需要输入输入键值对,基类需要支持
cls = self.__class__
self.name = '_{}#{}'.format(cls.__name__, cls.__counter)
cls.__counter += 1
for key, value in opts.items():
setattr(self, key, value) def __get__(self, instance, value):
if instance is None:
return self # 通过类访问返回时,返回自身
else:
return getattr(instance, self.name) def __set__(self, instance, value):
setattr(instance, self.name, value) # 也可以instance.__dict__[self.storage_name] = value class Typed(Descriptor): # 基类2:实现type验证
expected_type = type(None) def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError('Expected ' + str(self.expected_type))
super().__set__(instance, value) class Unsigned(Descriptor): # 基类3:实现数值验证
def __set__(self, instance, value):
if value < 0:
raise ValueError('Expected >= 0')
super().__set__(instance, value) class MaxSized(Descriptor): # 基类4:实现str长度验证
def __init__(self, **opts):
if 'size' not in opts:
raise TypeError('Missing size option')
super().__init__(**opts) def __set__(self, instance, value):
if len(value) >= self.size:
raise ValueError('size must be < ' + str(self.size))
super().__set__(instance, value) class Inter(Typed):
expected_type = int class String(Typed):
expected_type = str class UnsignedInteger(Inter, Unsigned): # 整型 + >=0
pass class SizedString(String, MaxSized): # string + 最大长度8
pass class Foo:
x = UnsignedInteger()
y = SizedString(size=8) def __init__(self, x, y):
self.x = x
self.y = y
对Foo实例化:
f1 = Foo(-1, 'a') # ValueError: value must be >= 0
f2 = Foo(1, 'abcdefghi') # ValueError: size must be < 8
描述符总结:
Foo:托管类。把描述符的实例声明为类属性的类(即x = Quantity())。
Foo的x:托管属性,Foo的类属性,Quantity描述符类的实例。由描述符实例处理的公开属性。
Quantity:描述符类。实现了描述符的类。
Quantity的storeage_name:存储属性。
覆盖型与非覆盖型描述符:
待补充
4,property的本质
4.1,property是个类
propery本质上含有装饰器方法的类
如下例,Foo多了个x属性,并且x属性是个property类:property(fget, fset, fdel)
class FooNoPro:
def __init__(self, x):
self.x = x class Foo:
def __init__(self, x):
self._x = x @property
def x(self):
return self._x >>>set(dir(Foo)) - set(dir(FooNoPro))
{‘x'}
>>>type(Foo.x)
<class 'property'>
4.2,property装饰过程
装饰前,x是个Foo类的方法:
class Foo:
def __init__(self, x):
self._x = x def x(self):
return self._x >>>s = Foo(10)
>>>dir(s) # 实例s包括了属性_x和方法x
[......, '_x', 'x']
>>>Foo.x # x是个Foo类的方法
<function Foo.x at 0x005174B0>
>>>s.x # 访问实例s.x,返回的也是Foo类的x方法
<bound method Foo.x of <__main__.Foo object at 0x008CEE90>>
使用猴子补丁,装饰x方法:
Foo.x = property(Foo.x) # 打猴子补丁,相当于@property装饰,property类实例化时第一个参数是fget=Foo.x
装饰后,Foo.x变成了property类的实例,再次访问s.x返回的是10而不是函数地址了:
>>>type(Foo.x)
type(Foo.x)
>>>s.x
10
为什么访问s.x能拿到10?
property背后实现了__get__,调用时s.x相当于调用:
>>>Foo.x.__get__(s, Foo) # 相当于s.x
10
5,动态存取属性
setattr:设置属性时会触发,类的实例化时也会触发
getattr:当访问不存在的属性时会触发,访问已经存在的属性时不会触发
class Foo: def __init__(self, x):
self.x = x def __setattr__(self, key, value):
self.__dict__[key] = value + 1 # 如果调用self.key = value + 1 会无限递归 def __getattr__(self, item):
return 'not valid attribute'
调用属性:
f = Foo(1)
f.y = 5
print(f.x) # 2
print(f.y) # 6
print(f.z) # not valid attribute
注意与动态存取分量的区别:__getitem__,__setitem__
6,处理属性的内置函数和特殊方法
6.1,处理属性的内置函数
dir:列出对象大多数属性,静态属性只会列出__dict__属性键值对中的键
vars:获取对象的__dict__属性键值对,如果定义了__slots__元组形式保存属性,则无法处理该对象,此时可以靠dir可列出
getattr:获取对象属性
hasattr:判断对象有无属性
setattr:设置对象的属性
delattr:删除对象的属性
上述get, has, set, del可以动态的实现属性的增删改查。
6.2,处理属性的特殊方法
__delattr__:删除属性,通过del触发
__dir__:供dir调用
__getattr__:获取指定属性失败后,getattr和hasattr可能会触发
__getattribute__: 获取指定属性时总会调用,点号 / getattr / hasattr会触发
__setattr__:设置指定属性时总会调用,点号 / setattr会触发
guxh的python笔记六:类的属性的更多相关文章
- guxh的python笔记一:数据类型
1,基本概念 1.1,数据类型 基本数据类型:字符串,数字,布尔等 引用数据类型:相对不可变(元组),可变(列表,字典,集合等) 基本数据类型存放实际值,引用数据类型存放对象的地址(即引用) ==:判 ...
- Python 面向对象之一 类与属性
Python 面向对象之 类与属性 今天接触了一下面向对象,发现面向对象和之前理解的简直就是天壤之别,在学Linux的时候,一切皆文件,现在学面向对象了,so,一切皆对象. 之前不是一直在学的用面向函 ...
- guxh的python笔记五:面向对象
1,面向对象编程思想 类:一类具有相同属性的抽象 属性(静态属性):实例变量.类变量.私有属性 方法(动态属性):构造函数.析构函数(默认就有).函数.私有函数 对象/实例:类经过实例化后,就是对象/ ...
- guxh的python笔记三:装饰器
1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...
- 小甲鱼Python笔记(类)
类和对象 类的构造方法 def __init__(): 1 class People: 2 def __init__(self,name): 3 self.name = name 注意:在构造方法中的 ...
- Python笔记_类
1.类 1.1 类的定义 # 类的定义 class 类名: pass # pass是空语句,不具有任何意义,仅为了保持程序结构完整性 # 创建对象 对象名 = 类名() 1.2 成员变量 # 成员变量 ...
- guxh的python笔记八:特殊方法
1,类的特殊方法 新建一个类,本章内容中的特殊方法如果不创建类或新增方法,默认使用的就是下面的类: class Foo: """this is Foo"&q ...
- guxh的python笔记七:抽象基类
1,鸭子类型和白鹅类型 1.1,白鹅类型 白鹅类型对接口有明确定义,比如不可变序列(Sequence),需要实现__contains__,__iter__,__len__,__getitem__,__ ...
- python 面向对象六 类属性和实例属性
一.实例属性 Python是动态语言,根据类创建的实例可以任意绑定属性. >>> class Student(object): ... def __init__(self, name ...
随机推荐
- React Router API文档
React Router API文档 一.<BrowserRouter> 使用HTML5历史记录API(pushState,replaceState和popstate事件)的<Rou ...
- linux sftp远程上传文件
1.打开xshell 点击“新建文件传输”,如下图: 中间可能会出现下面的提示框,直接关掉即可: 2.切换到远程你要传输文件的目的地 命令:cd 你的路径 3.切换到本地文件所在目录 命令:lcd ...
- Struts2 学习
Struts2简介 1.概念:轻量级的MVC框架,主要解决了请求分发的问题,重心在控制层和表现层.低侵入性,与业务代码的耦合度很低.Struts2实现了MVC,并提供了一系列API,采用模式化方式简化 ...
- Spring 学习教程(二): IOC/DI+AOP
1. IOC / DI Spring是一个基于IOC和AOP的结构J2EE系统的框架 IOC 反转控制 是Spring的基础,Inversion Of Control 简单说就是创建对象由以前的程序员 ...
- java之beanutils使用
介绍 BeanUtils是Apache Commons组件的成员之一, 主要用于简化JavaBean封装数据的操作. 点击下载依赖 jar 包 使用 有如下 javabean : package ...
- 【LeetCode每天一题】Spiral Matrix II(螺旋数组II)
Given a positive integer n, generate a square matrix filled with elements from 1 to n2 in spiral ord ...
- python中的双冒号作用
Python序列切片地址可以写为[开始:结束:步长],其中的开始和结束可以省略. 1. range(n)生成[0,n)区间整数 2. 开始start省略时,默认从第0项开始 3. 结尾省略的时候,默认 ...
- windows将文件夹映射为虚拟磁盘
subst X: e:123 将e盘下的123文件夹映射为x盘,123的容量即x盘容量 subst X: /t 删除映射的x盘
- 查询oracle数据字典,并对应出hive的数据类型
SQL开始 select t2.owner||'.'||t2.TABLE_NAME 源表名, 'dl_{0}_seq.'||'tt_{1}_'||lower(t2.table_name) hive表名 ...
- linq 表分组后关联查询
测试linq,获取有教师名额的学校.比如学校有5个教师名额,teacher数量没超过5个,发现有空额 var query = (from teacher in _repositoryTeacher.T ...