>>> class Foo:
... def __init__(self):
... self.__bar = 'abc'
...
>>> foo = Foo()
>>> foo.__bar
Traceback (most recent call last):
File "" , line 1, in <module>
AttributeError: 'Foo' object has no attribute '__bar'
>>> foo._Foo__bar
'abc'
可以看到,当我们使用__{var} 定义私有属性时,python解释器只是重命名为包含类名的别名_{class}__{var}
私有属性主要用途:在父类中定义一个不容易被子类重写的受保护属性
日常使用单下划线前缀即可表示私有属性
接上例
>>> foo.__dict__
{'_Foo__bar': 'abc'}
>>> Foo.__dict__
mappingproxy({'__module__': '__main__', '__init__': <function Foo.__init__ at 0x7f5699375670>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
虽然setattr 可以和 __dict__.update 效果相同,但是明显后者更简单
>>> d = {"a":"A", "b":"B"}
>>> foo.__dict__
{'_Foo__bar': 'abc'}
>>> foo.__dict__.update(d)
>>> foo.__dict__
{'_Foo__bar': 'abc', 'a': 'A', 'b': 'B'}
>>> setattr(foo, "c", "C")
>>> foo.__dict__
{'_Foo__bar': 'abc', 'a': 'A', 'b': 'B', 'c': 'C'}
而且两者也有不同之处
>>> class Person:
... def __init__(self, name, age):
... self.name = name
... self.age = age
... def __setattr__(self, name, value):
... if name == "age" and value < 0:
... raise ValueError("age < 0")
... super().__setattr__(name, value)
...
>>> p = Person("Tom", 1)
>>> p.age = -1
Traceback (most recent call last):
File "" , line 1, in <module>
File "" , line 7, in __setattr__
ValueError: age < 0
>>> p.__dict__["age"] = -1
>>> p.age
-1
可以看到,__dict__ 可以绕过 __setattr__ 实现一些tricky的操作
>>> class Duck:
... def __init__(self, color):
... self.color = color
... def quack(self):
... print(f"Hi, I'm a {self.color} duck!")
...
>>> Duck.quack()
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: quack() missing 1 required positional argument: 'self'
可以看到,一般情况下类里面的方法,必须实例化之后才能调用
@classmethod 就可以解决
>>> class Duck:
... def __init__(self, color):
... self.color = color
... def quack(self):
... print(f"Hi, I'm a {self.color} duck!")
... @classmethod
... def create_random_duck(cls):
... import random
... color = random.choice(["yellow", "red", "white"])
... return cls(color=color)
...
>>> d = Duck.create_random_duck()
>>> d.quack()
Hi, I'm a red duck!
如果发现某个方法不需要使用当前实例里任何内容,则可以使用@staticmethod
>>> class Cat:
... def __init__(self, name):
... self.name = name
... def say(self):
... sound = self.get_sound()
... print(f"{self.name}: {sound}...")
... @staticmethod
... def get_sound():
... return "Meow Meow"
...
>>> c = Cat("Tom")
>>> c.say()
Tom: Meow Meow...
# 直接用类调用也可以
>>> Cat.get_sound()
'Meow Meow'
可以发现静态方法可以拿到外面当做普通函数:
在类里,属性和方法职责不同:属性代表状态(instance.attr访问), 方法代表行为(instance.method()调用)。
@property 模糊了两者界限,使用它,可以把方法通过属性的方式暴露出来
>>> import os
>>> class FilePath:
... def __init__(self, path):
... self.path = path
... def get_basename(self):
... return self.path.split(os.sep)[-1]
...
# 转化成属性
>>> class FilePath:
... def __init__(self, path):
... self.path = path
... @property
... def basename(self):
... return self.path.split(os.sep)[-1]
...
>>> p = FilePath("/tmp/foo.py")
# 好比有个self.basename = self.path.split(os.sep)[-1]
>>> p.basename
'foo.py'
除了定义属性的读取,@property还可以定义写入和删除逻辑
>>> class FilePath:
... def __init__(self, path):
... self.path = path
... @property
... def basename(self):
... return self.path.rsplit(os.sep, 1)[-1]
... @basename.setter
... def basename(self, name):
... new_path = self.path.rsplit(os.sep, 1)[:-1] + [name]
... self.path = os.sep.join(new_path)
... @basename.deleter
... def basename(self):
... raise RuntimeError("cant delete basename")
...
>>> p = FilePath("/tmp/foo.py")
>>> p.basename
'foo.py'
>>> p.basename = 'bar.py'
>>> p.path
'/tmp/bar.py'
>>> del p.basename
Traceback (most recent call last):
File "" , line 1, in <module>
File "" , line 13, in basename
RuntimeError: cant delete basename
假设想操作某个对象,不需要判断它是否属于某种类型,直接判断它是不是有你需要的某个属性或者方法。或者更激进点,直接尝试调用所需的属性或方法(失败了,就让它报错好了)。这就是鸭子类型