文章目录

  • Python中的类属性
  • 类属性和实例属性名字冲突怎么办
  • Python中方法也是属性
  • Python中定义类方法



Python中的类属性

类是模板,而实例则是根据类创建的对象。

绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。

定义类属性可以直接在 class 中定义:

class Person(object):
    address = 'Earth'
    def __init__(self, name):
        self.name = name

因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问:

print Person.address
# => Earth

对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:

p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth

由于Python是动态语言,类属性也是可以动态添加和修改的:

Person.address = 'China'  # 修改 类属性
print p1.address
# => 'China'
print p2.address
# => 'China'

因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了。

例子:

# 给 Person 类添加一个类属性 count,每创建一个实例,count 属性就加 1,这样就可以统计出一共创建了多少个 Person 的实例。
class Person(object):
  countAttribut = 0
  def __init__(self, name):
    self.name = name
    Person.countAttribut = Person.countAttribut + 1

p1 = Person(name="z")
p2 = Person(name="y")
print(p1.countAttribut)  # 2
print(p2.countAttribut)  # 2

类属性和实例属性名字冲突怎么办

修改类属性会导致所有实例访问到的类属性全部都受影响,但是,如果在实例变量上修改类属性会发生什么问题呢?

class Person(object):
    address = 'Earth'
    def __init__(self, name):
        self.name = name

p1 = Person('Bob')
p2 = Person('Alice')

print('Person.address:', Person.address)  # Person.address: Earth

p1.address = 'China'
print('p1.address:', p1.address)  # p1.address: China

print('Person.address:', Person.address)  # Person.address: Earth
print('p2.address:', p2.address)  # p2.address: Earth

在设置了 p1.address = ‘China’ 后,p1访问 address 变成了 ‘China’,但是,Person.address和p2.address仍然是’Earch’。

原因是 p1.address = 'China’并没有改变 Person 的 address,而是给 p1这个实例绑定了实例属性address ,对p1来说,它有一个实例属性address(值是’China’),而它所属的类Person也有一个类属性address,所以:

访问 p1.address ,优先查找实例属性,返回’China’。

访问 p2.address ,p2没有实例属性address,但是有类属性address,因此返回’Earth’。

可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问

当我们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 'Earth’了:

del p1.address
print p1.address
# => Earth

可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。


Python中方法也是属性

我们在 class 中定义的实例方法其实也是属性,它实际上是一个函数对象:

class Person(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        return 'A'

p1 = Person('Bob', 90)
print p1.get_grade
# => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
print p1.get_grade()
# => A

也就是说,p1.get_grade 返回的是一个函数对象,但这个函数是一个绑定到实例的函数,p1.get_grade() 才是方法调用。

因为方法也是一个属性,所以,它也可以动态地添加到实例上,只是需要用 types.MethodType() 把一个函数变为一个方法:

import types

def fun_getGrade(self):
    if self.score >= 90:
        return 'A'
    if self.score >= 60:
        return 'B'
    return 'C'

class Person(object):
    address = 'Earth'
    def __init__(self, name, score):
        self.name = name
        self.score = score

p1 = Person('Bob', 90)
p1.get_grade = types.MethodType(fun_getGrade, p1)
print(p1.get_grade())  # A

p2 = Person('Alice',80)
print(p2.get_grade())
# AttributeError: 'Person' object has no attribute 'get_grade'
# 因为p2实例并没有绑定get_grade

给一个实例动态添加方法并不常见,直接在class中定义要更直观。

由于属性可以是普通的值对象,如 str,int 等,也可以是方法,还可以是函数,以下代码 p1.get_grade 为什么是函数而不是方法:

class Person(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
        self.get_grade = lambda: 'A'

p1 = Person('Bob', 90)
print(p1.get_grade)  # <function Person.__init__.<locals>.<lambda> at 0x00000123C4E33E18>
print(p1.get_grade())  # A

原因:直接把 lambda 函数赋值给 self.get_grade 和绑定方法有所不同,函数调用不需要传入 self,但是方法调用需要传入 self


Python中定义类方法

和属性类似,方法也分为 类方法实例方法

在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身

在class中如何定义 类方法

class Person(object):
    count = 0
    def __init__(self, name):
        self.name = name
        Person.count = Person.count +1

    @classmethod
    def num(cls):
        return cls.count

print(Person.num())  # 0
p1 = Person('ZYP')  
p2 = Person('ZYP2')
print(Person.num())  # 2

通过装饰器 @classmethod,该方法将绑定到 Person 类上,而非类的实例。

类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.count 实际上相当于 Person.count。

因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。

例子:

# 将类属性 count 改为私有属性__count,则外部无法读取__score,但可以通过一个类方法获取。
class Person(object):
    __count = 0

    def __init__(self, name):
        self.name = name
        Person.__count = Person.__count +1

    @classmethod
    def num(cls):
        return cls.__count

print(Person.num())
p1 = Person('ZYP')
p2 = Person('ZYP2')
print(Person.num())