Python 20 面向对象编程-类和对象的属性

Python 20 面向对象编程-类和对象的属性

旧博客Python系列

在了解了类和对象的关系之后,来了解一下类和对象的属性,以及针对属性的操作.

建立类并且实例化2个对象:

class Data():
    '''这是一个测试用数据类型'''
    default_type = 'int'
    default_value = 42
    default_name = 'essense'

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type

    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

data1 = Data('first_data',629,'int')
data2 = Data('second_data','709','str')

访问类的数据属性:

print(Data.default_name,id(Data.default_name))
print(data1.default_name,id(Data.default_name))
print(data2.default_name,id(Data.default_name))
结果完全相同,类的数据属性可以被所有的对象共享,内存地址也是同一个,说明只存放一份,在类里.
# 显示一下方法的地址看看:
print(Data.show_obj,id(Data.show_obj))
print(data1.show_obj,id(data1.show_obj))
print(data2.show_obj,id(data2.show_obj))
# <function Data.show_obj at 0x0000020DCB586158> 2258269397336
# <bound method Data.show_obj of <__main__.Data object at 0x0000020DCB57EB70>> 2258264734728
# <bound method Data.show_obj of <__main__.Data object at 0x0000020DCB57EBE0>> 2258264734728
# 类的函数属性就不是静态存放的,而是绑定给对象进行使用的.这里发现地址并不相同,这是因为python的id还不是真正的内存地址,但实际上,方法在内存中存放是肯定是只有一份的.

属性的命名,一般数据类型的属性,命名规则就是这是什么东西,而方法的命名一般是动作+客体的方式来命名.

类属性的增删改查

对象的属性一律用 对象名.属性名进行访问.如果属性是方法,就像执行函数一样.

# 修改类的数据属性:
Data.default_name = 'jenny'
print(data1.default_name) # jenny
print(data2.default_name) # jenny
# 增加类的数据属性:
Data.new_name = 'cony'
print(data1.new_name)  # cony
print(data2.new_name)  # cony
# 成功向类内添加了一个数据属性
# 删除类的数据属性
del Data.new_name
print(data1.new_name)
print(data2.new_name)
# AttributeError: 'Data' object has no attribute 'new_name'

直接修改类的数据属性会影响所有使用类的数据属性的对象,所以直接修改类的数据属性并不推荐使用,而且这么做也很容易会破坏类的结构.

# 增加方法
def get_data(self):
    return  self.value

Data.get = get_data

print(data1.get())
# 629

增加方法可以自定义函数然后将给函数赋一个类的属性名.注意自定义的函数第一个位置参数不管名称是什么,赋给类的方法名之后,第一个位置会传入self,所以要注意参数的个数,必须显式的在最前边保留一个给self的位置.其他修改删除操作与数据属性相同.

对象=实例属性的增删改查

对象和类类似,可以增删改查数据属性,注意,对象的数据属性只能在自己的范围内被引用,类的空间里没有实例的数据属性.
# 查看对象的属性
print(data1.value)    # 629
print(data2.show_obj)
# <bound method Data.show_obj of <__main__.Data object at 0x000001D5330DEB70>>
可见类的方法和对象的方法显示不同,类就显示一个函数,而对象显示的是一个绑定的类方法.
# 增加数据属性
data1.new_value = 709
print(data1.new_value) #709

# 增加函数属性
def get_data(self):
    return self.value

data1.get = get_data

print(data1.get())
# 运行后报错TypeError: get_data() missing 1 required positional argument: 'self',由于增加的方法并不是通过class定义的,所以不会自动传入参数.这个时候的方法就相当于一个普通函数.
# 一般不推荐直接给对象增加方法.

修改和删除对象的属性与类类似,不再详述.这里注意的是既然有属性字典,是不是可以通过直接操作属性字典的方式增加属性呢?

# 修改属性字典
data1.__dict__['name'] = '1st_data'
print(data1.name)  #1st_data
# 可以成功操作,但不推荐使用此种方法修改属性.

HINT:如果有时候有很多变量要使用,又不想定义太多的全局变量,可以单独定义一个和若干个类,相当于一个作用域,需要保持独立的变量可以放入类内,类似C语言里的结构体.

类属性和对象属性的关系

在知道了基本的操作属性的基础上,对象和类的属性之间有什么关系呢?
data1.default_name = 'cony'
print(data1.default_name)  # cony
print(Data.default_name)   # essense
按照属性查找的优先顺序,有同名的属性,对象的属性会被优先查找到,没有才会返回类的属性,所以这个例子里对象的default_name和类的default_name是独立的.这也符合类的任职,即属于同一类的个体在具体属性上会有差异.这个寻找的过程最多不超出类的范围,所以即使设置同名全局变量也没有用途.

HINT:函数一定要与功能相关,不要把输入和输出与逻辑耦合在一起,涉及到输入输出的内容,一般不要写到逻辑函数内.输入输出一般是在外层去写.

HINT:注意下面的例子:

name_fa = 'minko'

class Data():
    '''这是一个测试用数据类型'''

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type
        print(name_fa)  # 这个地方会引用全局变量

    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

data1 = Data('first_data',629,'int')
# 会显示minko,表明init函数的执行过程没有去类内寻找作用域.
name_fa = 'minko'

class Data():
    '''这是一个测试用数据类型'''
    name_fa = 'minko in class'  #在类内增加一个同名参数

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type
        print(name_fa)

    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

data1 = Data('first_data',629,'int') # 依然显示minko
print(Data.name_fa)  # 显示minko in class
print(data1.name_fa) # 显示minko in class

这个例子说明了只要通过对象.属性的方法去访问属性,会去找类内的属性,也就是类的作用域内.在定义类的属性的时候采用普通方式写的变量,遵循普通的标准.所以init函数的country根本不从类内去寻找,即使是被写在了类定义之内和同名变量之后,类内的作用域对于直接写的变量没有任何影响.

静态属性 静态方法 类方法

之前是统称所有的数据属性和函数属性都叫属性,然后单独叫函数属性为方法.再来看其他描述属性的术语. 这里有一篇文章提到了属性的划分.

静态属性

静态属性就是通过一个class 语法内建的装饰器,来讲函数属性加()调用改成直接通过函数名调用.显然,这对一点定需要传参数的方法是无效的.但对隐藏内部实现细节,取数据的时候很有效.
# @property装饰器
class Data():
    '''这是一个测试用数据类型'''
    name_fa = 'minko in class'

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type

    @property
    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

data1 = Data('first_data',629,'int')

data1.show_obj() # 会报错TypeError: 'NoneType' object is not callable

data1.show_obj #可以执行,这样看起来就很像数据属性.这也可以让类的使用方法变的统一.如果这里修改show_obj的方法为return 一个值,则使用起来和普通的数据属性没有什么区别,这就隐藏了其中的细节,用户在调用的时候就像是在取一个数据属性.

类方法

看如下示例:
class Data():
    '''这是一个测试用数据类型'''
    tag_name = 'minko in class'

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type

    @property
    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

    def test(self):
        print(""From test is {}"".format(self.name))

# 如果用类本身调用类方法,就像上边的test方法一样
Data.test() # 会报错TypeError: test() missing 1 required positional argument: 'self'

说明漏了一个参数,实际上,这个参数只能传入对象,也就是说test这种形式的函数,在使用的时候一定是与对象绑定的,类是无法调用这个方法的.
但是很多情况下,类本身也需要展示一些信息或者进行一些动作,这个时候就不能采取加self参数的方法定义,而需要只和类绑定的方法.
这个时候就需要@classmethod装饰器,定义一个专门供类使用的方法.上述代码修改如下:

class Data():
    '''这是一个测试用数据类型'''
    tag_name = 'minko in class'

    def __init__(self, name, value, type):
        self.name = name
        self.value = value
        self.type = type

    @property
    def show_obj(self):
        print(""{}'s value is {}, type is {}."".format(self.name, self.value, self.type))

    @classmethod
    def test(cls,x): #写好@classmethod之后,一打括号会自动补一个cls参数,表示类
        print(""From test is {} and {}"".format(cls.tag_name,x))

Data.test(10)  #不需要传参数,自动传入当前的类. # From test is minko in class and 10

设置好类方法之后,会发现类方法里只能访问类的属性,无法访问实例的属性(不会自动传入实例self).
如果用实例来调用类方法,结果依然只和类有关.一般根本不用实例去调用类方法.

静态方法

直接看示例,加了@staticmethod装饰器:
class Data():
    '''这是一个测试用数据类型'''
    tag_name = 'minko in class'
def __init__(self, name, value, type):
    self.name = name
    self.value = value
    self.type = type

@property
def show_obj(self):
    print(
        ""{}'s value is {}, type is {}."".format(
            self.name,
            self.value,
            self.type))

@classmethod
def test(cls, x):
    print(""From test is {} and {}"".format(cls.tag_name, x))

@staticmethod
def test_static(a,b,c):
    print('正在处理数据:',a,b,c)

data1 = Data('first_data', 629, 'int')

Data.test_static(1,2,3) # 正在处理数据 1 2 3

data1.test_static(1,2,3) # 正在处理数据 1 2 3


静态方法不跟类绑定也不和具体的实例绑定.由于默认参数没有self和cls,所以不会自动传入self和cls,所以和类与实例都没关系(自行编写然后将对象传入不属于此种情况).如果将@static去掉,则是普通方法,自动与对象绑定,如果用实例去调用,自动会传实例进去,这个函数就达不到原来的目的了.静态方法名义上由类管理,实际上可操作的内容与类和对象没有关系,相当于一个工具,无论类还是对象来调用,都只根据显式传入的参数进行处理,不会自动传入self或者cls(也就是不绑定).

概括总结:
静态属性是一种包装.
普通方法与对象绑定.
类方法只与类绑定.
静态方法不与类也不与对象绑定.
所谓绑定,可以理解为自行传参数.

LICENSED UNDER CC BY-NC-SA 4.0
Comment