Рубрики
Без рубрики

Динамическая природа питона и языковой дизайн

Несколько мыслей о динамически напечатанной парадигме, реализованной в Python. Tagged с Python, Typling, Languages.

Люди склонны думать, что Python динамически напечатан язык, потому что вы можете сделать это:

x = 10
x = "Hello"

Ну, вы можете выполнить аналогичное задание в ржавчине, которая является статически напечатанным языком.

let mut x = 10;
let x = "Hello";

По общему признанию, я обманул здесь. Эта концепция называется тени, и ржавчина не позволит вам сделать это:

let mut x = 10;
x = "Hello";

С другой стороны, затенение – это именно то, что делает Python при назначении имени объекту.

В Java или C# вы можете написать что -то вроде:

 public class Example{

     public static void main(String []args){
        Object x = 10;
        System.out.println(x == 10); // error!


         x = "Hello";
        // This is fine as we cast Object to String.
        System.out.println(
            ((String)x).equals("Hello"));
     }
}

С внешней точки зрения, это напоминает Python, пока вы не поймете, что вам не нужно выполнять какое -либо кастинг.

Разница появляется, как только мы пытаемся предпринять конкретное действие:

x = 10
assert x == 10
x = "Hello"
assert x == "Hello"

Эти линии в Python вполне хороши.

Не так в Java, как Java требует от нас типа X, что поддерживает конкретное сравнение, прежде чем мы его выполним. Таким образом, мы можем продолжать уверен, что все в порядке, если мы не столкнемся с страхом NullReferenceException во время выполнения.

    public class Example{

        public static void main(String []args){
            String x = null;
            System.out.println(x.equals("Hello"));
            // NullReferenceException

        }
    }

По сути, Java должна знать, какой тип заранее, в то время как Python не очень заботится. Но поскольку Python сильно напечатан, вы можете полагаться на переводчика, чтобы потерпеть неудачу, пока Java счастливо компонент.

В Python

x = "Hello"
y = None

# x + y raises an exception.

Пока в Java:

public class Example{

    public static void main(String []args){
        String x = null;
        System.out.println( x + "hello");

    }
}

// Prints "nullhello"

Такое поведение является реликвием печально известного Ошибка миллиардов долларов Анкет

Я называю это своей ошибкой в миллиард долларов. Это было изобретение нулевой ссылки в 1965 году. В то время я проектировал первую систему комплексной типа для ссылок на объектно -ориентированном языке (Algol W). Моя цель состояла в том, чтобы гарантировать, что все использование ссылок должно быть абсолютно безопасным, с проверкой, выполненной автоматически компилятором. Но я не смог устоять перед искушением поставить нулевую ссылку, просто потому, что его было так легко реализовать. Это привело к бесчисленным ошибкам, уязвимостям и сбоям системы, которые, вероятно, вызвали миллиард долларов боли и повреждения за последние сорок лет.

Современные реализации Java или C# предлагают способы избежать этого. Вот почему вы не должны пренебрегать дизайном языка. Люди, которые разработали первую версию C#, не исключая очевидных, а затем и известных недостатков в Java, теперь провозглашены для современного C#, что, по общему признанию, довольно хорошо, потому что, помимо прочего, они каким-то образом исправляли свои первоначальные ошибки. Чтобы быть справедливым, Java колебалась намного дольше, чтобы улучшить, несмотря на справедливую долю критики.

Например, ржавчина не страдает, поскольку это довольно недавний язык программирования. С другой стороны, в Котлине (еще один недавний язык), который имеет дело со старой кодовой базой Java, и совместимость – это вещь, вы также можете столкнуться с проблемой, несмотря на тот факт, что чистый котлин уничтожает проблему благодаря своей замыслу.

Давайте закончим с примерами из двух других языков:

JavaScript

"hello" + undefined
// ends with "helloundefined"

PHP


 $x = 10;
 $y = null;
 $x + $y; // Is this obviously 10?
 $0 == null; // This is 1, meaning true

//What exactly is this expression? I have no idea
//echo failed to give me any information.
echo 0===null;

// At least, php7 gave me a warning when I tried this
"hello" + null;

Но вернемся к Python. По праву вы можете спросить, почему кастинг там не происходит.

Теоретически, вы можете охранять свой код следующим образом:

if isinstance(y, int):
    y + y

На практике чаще встречается:

try:
    y + y
except TypeError:
    pass #or whatever you'd like to do

Философия Python утверждает, что легче просить прощения, чем просить разрешения.

Что это значит для вас?

Рассмотрим следующее:

from unittest import TestCase

test = TestCase()

class Animal:
    name = "Doggie"

class Person:
    name = "Hubert"

def get_name(x):
    return x.name

test.assertEqual(get_name(Animal()), "Doggie")
test.assertEqual(get_name(Person()), "Hubert")


Конечно, знаменитый уток на практике. На статически напечатанном языке вам нужно будет создать и реализовать интерфейс для достижения аналогичного результата. (См. Я не использовал здесь наследование . Если вам нужен хороший пример, я предлагаю изучить, как они реализовали композиция в котлине, например Здесь .. Ржавчина не хватает наследства полностью, реализуя [признаки] ( https://en.wikipedia.org/wiki/trait_(computer_programming) вместо.

Без сомнения, интерфейсы передают ваше намерение более безопасно и предсказуемо, но, честно говоря, беспокоятся о них каждый раз – это хлопот.

Теперь вам может быть любопытно, что происходит Когда get_name Получает тип, не содержащий имя поля. Давайте проверим это.

class Anything:
    pass

with test.assertRaises(AttributeError) as _:
    get_name(Anything())

try:
    test.assertIsNotNone(Anything().name)
except AttributeError:
    print("The name is not None for sure!")
The name is not None for sure!

По сути, мы получили исключение и подтвердили экспериментом, что поле имя не Нет Анкет Почему я это сделал? Чтобы продемонстрировать обеспечение безопасности Python по сравнению с другим основным динамическим языком на типике, JavaScript.

let anything = {};
let get_name = (x)=>x.name;
get_name(anything.name)
//undefined

Это важное различие в дизайне этих двух языков. Если вы пренебрегаете, чтобы осмотреть свой объект на поле имя , Python терпит неудачу, если этого не присутствует, в то время как JavaScript просто продолжает работать-какое поведение вместе со своим слабым характером и странными правилами принуждения может привести к абсолютно непредсказуемым результатам и труднодоступным ошибкам.

Теперь давайте вернемся к динамической природе Python. В объявлении класса Что угодно Мы опустили поле имя . Но мы можем сделать это.

car = Anything()
car.name = "Škoda"
test.assertEqual(get_name(car), "Škoda")

Фу, теперь это становится сложным. По умолчанию мы можем добавить поле во время выполнения, как мы хотим. Вы помните, как я упоминал интерфейсы? Как справится с статическим языком в реализации после богоподобного класса, который пытается справиться с разнообразием реального мира?

from typing import Optional
class Car:
    def __init__(self, **kwargs):
        self.__dict__ = kwargs

    def is_truck(self)->bool:
        return hasattr(self, "maximum_load")

    def get_maximal_number_of_persons(self)->Optional[int]:
        try:
            return self.persons
        except AttributeError:
            return None

    def get_name_of_all_fields(self):
        return list(self.__dict__.keys())

    def __str__(self):
        return f"Car: {self.name if hasattr(self, 'name') else 'Uknown'}"

skoda = {
    "name": "Škoda",
    "persons": 5,
    "type": "Fabia",
}

tatra = {
    "name": "Tatra",
    "maximum_load": 1000,
    "fuel": "diesel",
}

skoda_car = Car(**skoda)
tatra_truck = Car(**tatra)

test.assertEqual(get_name(skoda_car), "Škoda")
test.assertFalse(skoda_car.is_truck())
test.assertEqual(skoda_car.type, "Fabia")
test.assertIsNotNone(skoda_car.persons)
test.assertTrue(hasattr(tatra_truck, "fuel"))

Программисты в статически напечатанных языках кричат от боли. Какой ужас! Это абсолютно непредсказуемо! Это то, что слово динамический означает постоянное изменение.

На самом деле, в частности, не весь код в Python использует свою динамичность, поэтому вам не нужно так сильно беспокоиться. Чтобы принести больше порядка в динамическом хаосе, Python в третьей версии охватывает дополнительную статическую набор, которая становится лучше с каждой итерацией.

Но вернемся к нашему динамичному бизнесу. Рано или поздно каждый начинающий Pythonista сталкивается с полностью построением в методе или функции подписи, которая читается: (*args, ** kwargs)

Например:

def kill_them_all(*args, **kwargs):
    return f"I killed {args} and also {kwargs}"

kill_them_all("dogs", "people", "bacteria", numbers=(1,2,3.4), function=print, any_object=object())
"I killed ('dogs', 'people', 'bacteria') and also {'numbers': (1, 2, 3.4), 'function': , 'any_object': }"

Эта легкая функция не знает по милости. Он принимает все, без каких -либо отношений с числами, типами или целью; В некотором смысле, вы только что потрясли руку окончательному хищнику Pythonic Jungle.

Почему доброжелательный диктатор создает такое ужасное существо, не бессмысленно глотать куски кода?

Почему действительно? Вы можете осмотреть args и Kwargs или даже лучше, вы можете подтолкнуть их к другому голодному горлу. Вот!

def print_only_cars(*args):
    if all(isinstance(obj, Car) for obj in args):
        print(*args)
    else:
        print("I refuse to print:", *args)

print_only_cars(1,2,3)
print(skoda_car, tatra_truck, Car())

I refuse to print: 1 2 3
Car: Škoda Car: Tatra Car: Uknown

Надеюсь, я продемонстрировал несколько аспектов динамического поведения Python. Какими бы ни были сторонники утверждения статического типирования, динамическое типирование не уступает, на самом деле, статическое типирование – это лишь подмножество упругого характера динамического набора.

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

Более того, будучи суперсетом статического набора, динамически напечатанных языков, а именно Python с аннотациями типа или TypeScript для JavaScript, получают выгоды от обоих миров.

Языковой дизайн имеет значение. Некоторые языки, даже если вы не согласны с тем, как они реализованы, придерживайтесь внутренней согласованности, в то время как другие – просто набор случайных правил; правила, которые их создатель нашел удобным в то время он/Она создавала язык и который, в конце концов, делает такие языки очень опасными местами для жизни и кодирования.

Там нет идеального языка. Все они ошибочны или неоптимальны, если вы предпочитаете. Тем не менее, некоторые более безопасны, чем другие. Если вы можете, выберите лучшие.

Оригинал: “https://dev.to/hanpari/dynamic-nature-of-python-and-language-design-4bhm”