Python核心编程笔记--动态属性

时间:2021-07-18 15:16:24

一、动态语言与静态语言

1.1 静态语言特点:

a. 在定义变量时需要指定变量的类型,根据指定的类型来确定变量所占的内存空间

b. 需要经过编译才能运行

c. 在代码编译后,运行过程不能对代码进行操作

d. 常见的静态语言:C、C++、Java等

1.2 动态语言的特点:

a. 不需要经过编译,而是由解释器程序来解释执行代码

b. 在代码运行过程中,可以动态地对代码进行操作

c. 常见的动态语言:Python、PHP、Ruby、JavaScript等

1.3 优缺点比较:

a. 静态语言会声明变量类型,可以帮助计算机在执行代码前来发现更多潜在的错误,但这样会需要使用者进行更多的思考和编码。反之动态语言则不需要,它很灵活

b. 静态语言在编译后,执行速度一般是大于动态语言的

c. 静态语言结构比较规范,能很方便调试,但需要与大量类型相关的代码。而动态语言不需要与大量类型相关的代码,但如果编写不规范,不容易调试

二、Python的动态添加

2.1 动态添加属性

首先,我们定义一个简单的Person类,它所有姓名和年龄两个属性。并声明一个它的实例对象p。给p动态添加一个属性sex。代码如下所示:

class Person(object):
def __init__(self, name, age):
self.name = name #姓名
self.age = age #年龄 p = Person('Demon', 18)
print(p.name) #Demon
print(p.age) # p.sex = 'M' #对对象p动态添加属性sex,并赋值为M
print(p.sex) #M p2 = Person('Semon', 18) #声明另一个对象p2
# print(p2.sex) #报错:AttributeError: 'Person' object has no attribute 'sex'

从上面的代码可以看出,我们可以直接通过 “对象.新变量名 = 新变量值” 的形式来给对象动态添加一个属性(通过dir(对象名)可以查看当前拥有的属性)。同时当我们重新创建一个新的对象p2,想要调用刚刚动态添加的属性sex时,发现报错了。这说明我们动态创建的属性是只针对当前对象而言的,并不会影响其他对象的使用。这也称之为给对象动态添加属性

那么,如果我们想要动态添加一个属性,这个属性对所有对象都能生效应该怎么做呢?即我们需要给类动态添加属性,代码如下所示:

class Person(object):
def __init__(self, name, age):
self.name = name #姓名
self.age = age #年龄 p = Person('Demon', 18) #声明一个对象p
p2 = Person('Semon', 18) #声明另一个对象p2 Person.addr = 'SS' #给类动态添加属性
print(p.addr) # SS
print(p2.addr) # SS

2.2 __slots__的使用

简单来说,__slots__的作用就是规定该类能使用的属性是哪些。

import types
class Person(object):
def __init__(self, name, age):
# self.name = name #姓名,在init中定义了name,会报错:AttributeError: 'Person' object has no attribute 'name'
self.age = age #年龄 __slots__ = ('age', 'sex') #而在__slots__中没有定义name,则在init中去使用name会报错 p = Person('Demon', 18) #声明一个对象p # print(p.name) # AttributeError: 'Person' object has no attribute 'name' def eat(self):
print('----%s is eating' % self.name) # p.eat = types.MethodType(eat, p) #AttributeError: 'Person' object has no attribute 'eat'

官方文档中规定__slots__可接受一个字符串、可迭代的类型比如列表,元组等。而且父类中定义的__slots__,在子类中是不生效的。而解决办法就是在子类中自己定义自己的__slots__。示例代码如下:

import types
class Person(object):
__slots__ = 'name' class Student(Person):
#__slots__ = ['age'] # 只用定义 age , name 会默认合并进来
#解决办法是子类自己定义
pass p = Person()
p.name = 'Demon'
#p.age = 18 #AttributeError: 'Person' object has no attribute 'age' s = Student()
s.name = 'Demon'
s.age = 18 #子类可以添加

2.3. 动态添加方法

首先,我们从上面已知的属性动态添加的方法进行分析,其实在python中,类并没有方法的概念,它有的都是属性,当我们调用比如p.eat(),其实是先通过p.eat找到一个调用,然后通过(),来执行调用。为了方便我们一般称p.eat()这种形式的称为调用方法(具体后面会再写笔记进行介绍)。既然是这样,我们会想当然的认为可以通过上面介绍的动态添加属性的方式来添加方法,我这样想,也就这样做了,代码如下所示:

class Person(object):
def __init__(self, name, age):
self.name = name #姓名
self.age = age #年龄 p = Person('Demon', 18) #声明一个对象p
p2 = Person('Semon', 18) #声明另一个对象p2 Person.addr = 'SS' #给类动态添加属性
print(p.addr) # SS
print(p2.addr) # SS def eat1():
print("----eat no self method-----") def eat2(self):
print("----eat has self method---- %s" % self.name) p.eat = eat1 #给对象动态指定一个方法
p.eat() # ----eat no self method-----
# p2.eat() # AttributeError: 'Person' object has no attribute 'eat' p.eat = eat2
# p.eat() #TypeError: eat2() missing 1 required positional argument: 'self' Person.eat = eat1
Person.eat() #----eat no self method-----
# p2.eat() # TypeError: eat1() takes 0 positional arguments but 1 was given Person.eat = eat2 #给类动态指定一个方法
p2.eat() #----eat has self method---- Semon

可以看到,通过对象.的方法动态添加方法,应该说只能实现部分的效果,就上述代码的运行情况,我们可以总结如下:

a. 如果添加一个不带self参数的方法,可以直接通过 实例.方法名 的方式添加,也可以通过 类名.方法名添加

b. 如果添加一个带self参数的方法,则只能通过类名.方法名来添加

而从另一个角度,我们都知道,在Python中,类的方法可以分为以下三种:

实例方法(instance method):被类实例调用,第一个参数默认是self

静态方法(static method):可以被类和实例调用,没有默认参数

类方法(class method):可以被类和实例调用,第一个参数默认是类,一般用cls变量名

从之前的测试结果,不带参数的方法,可以通过.的方式来添加,类比如静态方法,我们可以猜测静态方法的动态添加也是通过 . 的方式,测试代码如下:

import types
class Person(object):
pass @staticmethod
def test_static():
print('aa') Person.xxxx = test_static # 测试返回值,不一定要命名为固定的名称,可以是任意的 Person.xxxx() # .后的名称与上面一致即可

  同样,从我们预测的代码结论里,用类名.可以添加带self参数的方法,而类方法是默认带类对象的方法,我们也尝试用.的方式来添加,测试代码如下:

import types
class Person(object):
num = 0
pass @classmethod
def test_class(cls):
print(cls.num) # Person.xxxx = test_class # 测试返回值,不一定要命名为固定的名称,可以是任意的 Person.xxxx() # .后的名称与上面一致即可

综上两个测试代码可以得出,静态方法和类方法的动态添加和属性的动态添加是一样的实现方式

而显然唯一不能通过.的方式进行动态添加的就是针对某个对象的实例方法。原因也很简单,实例方法必须第一个参数是实例对象,而方法是在类之后定义了,通过实例对象. 的方式去添加的时候,第一个参数找不着,所以会报错。解决方法是使用types模块中的MethodType(function, instance)。我们可以在IPython3中使用help来查看MethodType的使用文档:

import types

help(types.MethodType)

'''
class method(object)
| method(function, instance)
|
| Create a bound instance method object.
|
| Methods defined here:
|
| __call__(self, /, *args, **kwargs)
| Call self as a function.
|
| __delattr__(self, name, /)
| Implement delattr(self, name).
'''

  可以看出,MethodType接受两个参数,第一个参数是需要绑定的函数名,第一个是实例对象。示例代码如下:

import types
class Person(object):
def __init__(self, name, age):
self.name = name #姓名
self.age = age #年龄 p = Person('Demon', 18) #声明一个对象p def eat(self):
print('----%s is eating' % self.name) p.eat = types.MethodType(eat, p)
p.eat()

  这样我们就完成了实例方法的动态添加。注意使用MethodType需要定义返回,返回的是什么,就用什么来调用。

三、总结

最后总结结论如下:

1. 动态添加实例属性:使用 对象名.新变量名 的方式

2. 动态添加类属性:使用 类.新变量名 的方式

3. 限制类的属性:__slots__

4. 静态方法与类方法:使用 类.新方法 的方式

5. 实例方法:

  如果只对某个对象生效:使用types模块中的对象名.新方法名 = MethodType(function, 对象名) 的方式

  如果对所有对象生效:使用 类.方法名 的方式