Вопрос Подсказки типов данных в Python

Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
Столкнулся с проблемой при использовании подсказок типов данных

Файл с классом данных и функцией
test.py:
from dataclasses import dataclass

@dataclass
class Person:
    fname: str

def print_hello(name: Person.fname) -> None:
    print(f"hello {name}")

Файл через который я запускаю функцию
main.py:
from test import print_hello, Person

person = Person("Tom")

print_hello(person.fname)

При запуске получаю ошибку
Код:
    def print_hello(name: Person.fname) -> None:
                          ^^^^^^^^^^^^
AttributeError: type object 'Person' has no attribute 'fname'

Почему я получаю ошибку и как это можно исправить?
 
PoC Life
Пользователь
Статус
Оффлайн
Регистрация
22 Авг 2022
Сообщения
347
Реакции[?]
48
Поинты[?]
38K
Столкнулся с проблемой при использовании подсказок типов данных

Файл с классом данных и функцией
test.py:
from dataclasses import dataclass

@dataclass
class Person:
    fname: str

def print_hello(name: Person.fname) -> None:
    print(f"hello {name}")

Файл через который я запускаю функцию
main.py:
from test import print_hello, Person

person = Person("Tom")

print_hello(person.fname)

При запуске получаю ошибку
Код:
    def print_hello(name: Person.fname) -> None:
                          ^^^^^^^^^^^^
AttributeError: type object 'Person' has no attribute 'fname'

Почему я получаю ошибку и как это можно исправить?
def print_hello(person: Person) -> None:
print(f"hello {person.fname}")
 
Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
Главный модератор
Главный Модератор
Статус
Оффлайн
Регистрация
13 Фев 2018
Сообщения
1,094
Реакции[?]
801
Поинты[?]
146K
Это сработает, но в чём проблема моего кода?
Почему просто Person он видит, а Person.fname нет?
Потому что надо понимать как работает аннотация типов в пайтоне.
Ты не можешь передавать атрибут класса в качестве аргумента, т.к. он является ссылкой на значение с определенным типом, а не непосредственно типом данных, и создаётся лишь после создания объекта класса.

То есть, ты можешь написать как указал TheXSVV или можешь написать так:
Python:
def print_hello(name: str) -> None:
    print(f"hello {name}")
Поскольку тип атрибута класса Person является строкой - это будет работать.
 
Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
Потому что надо понимать как работает аннотация типов в пайтоне.
Ты не можешь передавать атрибут класса в качестве аргумента, т.к. он является ссылкой на значение с определенным типом, а не непосредственно типом данных, и создаётся лишь после создания объекта класса.

То есть, ты можешь написать как указал TheXSVV или можешь написать так:
Python:
def print_hello(name: str) -> None:
    print(f"hello {name}")
Поскольку тип атрибута класса Person является строкой - это будет работать.
Собственно в чём и проблема.
Я либо передаю в функцию целиком Person, что не очень логично если я буду использовать только 1 его атрибут.
Либо меняю dataclass на str, что не очень удобно, особенно если у меня несколько классов данных подобных Person.
Есть какой то выход из этой ситуации?
 
Главный модератор
Главный Модератор
Статус
Оффлайн
Регистрация
13 Фев 2018
Сообщения
1,094
Реакции[?]
801
Поинты[?]
146K
Я либо передаю в функцию целиком Person
передаешь ты не функцию, а объект
Либо меняю dataclass на str, что не очень удобно, особенно если у меня несколько классов данных подобных Person.
Передавай аргументом функции print_hello строку, в чём проблема то?
 
Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
передаешь ты не функцию, а объект

Передавай аргументом функции print_hello строку, в чём проблема то?
Проблема в том что в функцию может быть передан другой класс данных, который так же будет содержать атрибут fname

Например
Python:
@dataclass
class Person:
    fname: str
    
@dataclass
class User:
    fname: str
    age: int
    
@dataclass
class Dog:
    fname: str
    breed: str
То есть под тип str подходит Dog.fname, User.fname и Person.fname, но функция предназначена только для класса данных Person.
Вот я и написал
Python:
def print_hello(name: Person.fname) -> None:
    print(f"hello {name}")
Но это не работает.
Какие ещё есть варианты?
 
Главный модератор
Главный Модератор
Статус
Оффлайн
Регистрация
13 Фев 2018
Сообщения
1,094
Реакции[?]
801
Поинты[?]
146K
Какие ещё есть варианты?
Повторю в третий раз :)
Передавай аргументом строку.
Python:
@dataclass
class Person:
    fname: str
    
@dataclass
class User:
    fname: str
    age: int
    
@dataclass
class Dog:
    fname: str
    breed: str

def print_hello(name: str) -> None:
    print(f"hello {name}")
 
Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
Повторю в третий раз :)
Передавай аргументом строку.
Python:
@dataclass
class Person:
    fname: str
  
@dataclass
class User:
    fname: str
    age: int
  
@dataclass
class Dog:
    fname: str
    breed: str

def print_hello(name: str) -> None:
    print(f"hello {name}")
Ты не понял.
Цель не допустить того чтобы в функцию попал класс данных Dog вместо Person.
 
Начинающий
Статус
Оффлайн
Регистрация
21 Мар 2021
Сообщения
95
Реакции[?]
29
Поинты[?]
7K
Аннотации типов должны указывать на Python типы, а не на поля классов.
В твоём случае аннотации ни к чему.

Данный пример будет работать так, как ты ожидал:

Python:
from dataclasses import dataclass


@dataclass
class Person:
    fname: str


def print_hello(name: str) -> None:
    print(f"hello {name}")


if __name__ == "__main__":
    person = Person("Tom")
    print_hello(person.fname)
 
Начинающий
Статус
Оффлайн
Регистрация
21 Мар 2021
Сообщения
95
Реакции[?]
29
Поинты[?]
7K
В таком случае ты можешь использовать isinstance для проверки типов и(или) использовать рефлексию для извлечения типов аннотаций:

Python:
from dataclasses import dataclass


@dataclass
class Person:
    fname: str


def print_hello_1(person: Person) -> None:

    # Если person имеет тип отличный от `Person`
    if not isinstance(person, Person):
        raise Exception("Invalid argument type!")

    print(f"Hello, {person.fname} #1")


def print_hello_2(person: Person) -> None:

    # Если person имеет тип отличный от `Person`
    if not isinstance(person, print_hello_2.__annotations__["person"]):
        raise Exception("Invalid argument type!")

    print(f"Hello, {person.fname} #2")


person = Person("Tom")
print_hello_1(person)
print_hello_2(person)
Иными методами ты не сможешь реализовать задуманное.
Python - динамический язык который по своей природе не умеет сам проверять типы.

Можешь на базе того, что я написал выше сделать декоратор для проверки типов на соответствие, но это сильно отразится на производительности.

Что-то вроде этого:
Python:
@typed
def sum_nums(a: int, b: int) -> int:
    ...
Не думаю, что тебе стоит волноваться, что можно передать объект другого типа.
Если тебе нужна типовая-защита - используй другой язык.
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
9 Июн 2018
Сообщения
31
Реакции[?]
2
Поинты[?]
1K
Иными методами ты не сможешь реализовать задуманное.
Python - динамический язык который по своей природе не умеет сам проверять типы.

Можешь на базе того, что я написал выше сделать декоратор для проверки типов на соответствие, но это сильно отразится на производительности.

Что-то вроде этого:
Python:
@typed
def sum_nums(a: int, b: int) -> int:
    ...
Не думаю, что тебе стоит волноваться, что можно передать объект другого типа.
Если тебе нужна типовая-защита - используй другой язык.
Благодарю
 
Эксперт
Статус
Оффлайн
Регистрация
29 Мар 2021
Сообщения
1,601
Реакции[?]
606
Поинты[?]
46K
Ты не понял.
Цель не допустить того чтобы в функцию попал класс данных Dog вместо Person.
если ты хочешь на уровне функции узнать атрибут какого класса переданный стринг - это невозможно, и ни один тайп чекер тебя не спасёт.

хочешь тайпхинты - оперируй над Person, как тебе уже сказали, e.g.:
Python:
def check_person(person: Person) -> str:
    def operate_on_person_attr(name: str) -> str:
        return name.tolower()

    return operate_on_person_attr(person.name)
умеет, трюки через isinstance и встроенная гулялка inspect, но не на уровне тайпчекера и уж точно это не удобно
Если тебе нужна типовая-защита - используй другой язык.
совет дерьма, если хочешь типизировать питон - типизируй питон, современный тулинг с синтаксисом позволяет
 
Начинающий
Статус
Оффлайн
Регистрация
21 Мар 2021
Сообщения
95
Реакции[?]
29
Поинты[?]
7K
если ты хочешь на уровне функции узнать атрибут какого класса переданный стринг - это невозможно, и ни один тайп чекер тебя не спасёт.

хочешь тайпхинты - оперируй над Person, как тебе уже сказали, e.g.:
Python:
def check_person(person: Person) -> str:
    def operate_on_person_attr(name: str) -> str:
        return name.tolower()

    return operate_on_person_attr(person.name)
умеет, трюки через isinstance и встроенная гулялка inspect, но не на уровне тайпчекера и уж точно это не удобно

совет дерьма, если хочешь типизировать питон - типизируй питон, современный тулинг с синтаксисом позволяет
Современный туллинг позволяет реализовывать это на уровне рефлексии и различных проверок в рантайме, что уже не сильно целесообразно. Для подобных случаев есть более пригодные компилируемые языки, которые поддерживают интерфейсы или generic' и.

Из всех популярных Python фреймаорков и библиотек проверку аннотаций осуществляет только pydantic и fastapi. Зачем пытаться отвёрткой забить гвоздь?
 
Эксперт
Статус
Оффлайн
Регистрация
29 Мар 2021
Сообщения
1,601
Реакции[?]
606
Поинты[?]
46K
Современный туллинг позволяет реализовывать это на уровне рефлексии и различных проверок в рантайме, что уже не сильно целесообразно.
Кто сказал? Питон никогда и не основывался на типизации, типизацию добавляют только для DX. Pydantic абузит typing.Annotated, который имеет (имел?) отдельное поведение при парсинге типа, и был виден в vars() класса. Рефлексии как таковой в питоне нет, это всё ещё враппер на враппере погоняет враппером.


проверку аннотаций
Типизация как контракт - это парадигма компилируемых языков. Типизация как "о, я нажимаю на точку и моя IDE сразу знает какие атрибуты и методы лежат в объекте" - это чисто для языков с дактайпингом (питон, тайпскрипт).

Типизированный питон > не типизированный питон.
 
Сверху Снизу