
这是我的Telegram频道@pythonetc中有关Python和编程的一些新技巧和窍门。
←
以前的出版物如果类的实例没有具有给定名称的属性,则它将尝试访问具有相同名称的类属性。
>>> class A: ... x = 2 ... >>> Ax 2 >>> A().x 2
实例具有类不具有的属性或具有不同值的属性是非常简单的:
>>> class A: ... x = 2 ... def __init__(self): ... self.x = 3 ... self.y = 4 ... >>> A().x 3 >>> Ax 2 >>> A().y 4 >>> Ay AttributeError: type object 'A' has no attribute 'y'
如果不是那么简单,那么,如果您希望实例的行为就像它没有属性一样,尽管该类具有该属性。 为此,您必须创建不允许实例访问访问的自定义描述符:
class ClassOnlyDescriptor: def __init__(self, value): self._value = value self._name = None
另请参阅Django
classonlymethod
装饰器的工作方式:
https :
//github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6在类主体中声明的函数看不到类范围。 这是有道理的,因为类作用域仅在类创建期间存在。
>>> class A: ... x = 2 ... def f(): ... print(x) ... f() ... [...] NameError: name 'x' is not defined
通常这不是问题:方法在类内部声明只是成为方法,然后被调用:
>>> class A: ... x = 2 ... def f(self): ... print(self.x) ... >>> >>> >>> A().f() 2
令人惊讶的是,理解也是如此。 它们具有自己的作用域,也无法访问类作用域。 这对于生成器理解确实很有意义:它们在类创建完成之后评估表达式。
>>> class A: ... x = 2 ... y = [x for _ in range(5)] ... [...] NameError: name 'x' is not defined
然而,理解无法获得
self
。 使它起作用的唯一方法是添加一个范围(是的,这很丑):
>>> class A: ... x = 2 ... y = (lambda x=x: [x for _ in range(5)])() ... >>> Ay [2, 2, 2, 2, 2]
在Python中,
None
等于
None
因此看起来您可以使用
==
检查
None
:
ES_TAILS = ('s', 'x', 'z', 'ch', 'sh') def make_plural(word, exceptions=None): if exceptions == None:
这是错误的做法。
None
一个确实等于
None
,但这不是唯一的事情。 自定义对象也可能等于“
None
:
>>> class A: ... def __eq__(self, other): ... return True ... >>> A() == None True >>> A() is None False
与
None
比较的唯一正确方法是使用
is None
。
Python浮点数可以具有NaN值。 您可以使用
math.nan
获得一个。
nan
不等于任何事物,包括自身:
>>> math.nan == math.nan False
此外,NaN对象不是唯一的,您可以从不同来源获得几个不同的NaN对象:
>>> float('nan') nan >>> float('nan') is float('nan') False
这意味着您通常不能将NaN用作字典键:
>>> d = {} >>> d[float('nan')] = 1 >>> d[float('nan')] = 2 >>> d {nan: 1, nan: 2}
typing
允许您定义生成器的类型。 您还可以指定产生什么类型,可以将什么类型发送到生成器以及返回什么类型。
Generator[int, None, bool]
是生成整数,返回布尔值且不支持
g.send()
。
这是稍微复杂的示例。
chain_while
从其他生成器
chain_while
,直到其中一个生成器返回某些信号,该信号根据
condition
函数停止运行:
from typing import Generator, Callable, Iterable, TypeVar Y = TypeVar('Y') S = TypeVar('S') R = TypeVar('R') def chain_while( iterables: Iterable[Generator[Y, S, R]], condition: Callable[[R], bool], ) -> Generator[Y, S, None]: for it in iterables: result = yield from it if not condition(result): break def r(x: int) -> Generator[int, None, bool]: yield from range(x) return x % 2 == 1 print(list(chain_while( [ r(5), r(4), r(3), ], lambda x: x is True, )))
注释工厂方法并不像看起来那样简单。 紧迫的要求是使用这样的东西:
class A: @classmethod def create(cls) -> 'A': return cls()
但是,这不是正确的事情。 问题是,
create
不返回
A
,它返回的是cl实例
A
或其任何后代。 看下面的代码:
class A: @classmethod def create(cls) -> 'A': return cls() class B(A): @classmethod def create(cls) -> 'B': return super().create()
mypy检查结果为
error: Incompatible return value type (got "A", expected "B")
。 同样,问题出在
super().create()
,对
super().create()
进行注释以返回
A
而在这种情况下显然返回
B
您可以通过用TypeVar注释cls来解决此问题:
AType = TypeVar('AType') BType = TypeVar('BType') class A: @classmethod def create(cls: Type[AType]) -> AType: return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create()
现在,
create
返回
cls
类的实例。 但是,此注释过于宽松,我们丢失了
cls
是
A
的子类型的信息:
AType = TypeVar('AType') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls()
错误是
"Type[AType]" has no attribute "DATA"
。
要解决此问题,必须使用
AType
的
bound
参数将
TypeVar
明确定义为
A
的子类型:
AType = TypeVar('AType', bound='A') BType = TypeVar('BType', bound='B') class A: DATA = 42 @classmethod def create(cls: Type[AType]) -> AType: print(cls.DATA) return cls() class B(A): @classmethod def create(cls: Type[BType]) -> BType: return super().create()